diff --git a/alembic/versions/20240627-170919_add_flight_flight_plan_id_fk_989152b7184a.py b/alembic/versions/20240627-170919_add_flight_flight_plan_id_fk_989152b7184a.py new file mode 100644 index 0000000..ac281a0 --- /dev/null +++ b/alembic/versions/20240627-170919_add_flight_flight_plan_id_fk_989152b7184a.py @@ -0,0 +1,30 @@ +"""add flight.flight_plan_id fk + +Revision ID: 989152b7184a +Revises: f9e1e8a01683 +Create Date: 2024-06-27 17:09:19.933094 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '989152b7184a' +down_revision = 'f9e1e8a01683' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('flight', sa.Column('flight_plan_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'flight', 'flight_plan', ['flight_plan_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'flight', type_='foreignkey') + op.drop_column('flight', 'flight_plan_id') + # ### end Alembic commands ### diff --git a/alembic/versions/20240630-002708_add_poi_and_airport_id_to_flight_plan__8cc01c03e980.py b/alembic/versions/20240630-002708_add_poi_and_airport_id_to_flight_plan__8cc01c03e980.py new file mode 100644 index 0000000..adcc21c --- /dev/null +++ b/alembic/versions/20240630-002708_add_poi_and_airport_id_to_flight_plan__8cc01c03e980.py @@ -0,0 +1,40 @@ +"""Add poi and airport id to flight plan track marker + +Revision ID: 8cc01c03e980 +Revises: 989152b7184a +Create Date: 2024-06-30 00:27:08.976891 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '8cc01c03e980' +down_revision = '989152b7184a' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('airport', 'openaip_id', + existing_type=mysql.VARCHAR(length=32), + nullable=True) + op.add_column('flight_plan_marker', sa.Column('airport_id', sa.Integer(), nullable=True)) + op.add_column('flight_plan_marker', sa.Column('point_of_interest_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'flight_plan_marker', 'point_of_interest', ['point_of_interest_id'], ['id']) + op.create_foreign_key(None, 'flight_plan_marker', 'airport', ['airport_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'flight_plan_marker', type_='foreignkey') + op.drop_constraint(None, 'flight_plan_marker', type_='foreignkey') + op.drop_column('flight_plan_marker', 'point_of_interest_id') + op.drop_column('flight_plan_marker', 'airport_id') + op.alter_column('airport', 'openaip_id', + existing_type=mysql.VARCHAR(length=32), + nullable=False) + # ### end Alembic commands ### diff --git a/src/database/models.py b/src/database/models.py index af21ecb..a90ab8b 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -104,6 +104,7 @@ class FlightPlan(BaseModel): is_default_name: Mapped[bool] = mapped_column(Boolean, server_default='0') is_public: Mapped[bool] = mapped_column(Boolean, server_default='0') is_editable_by_copilot: Mapped[bool] = mapped_column(Boolean, server_default='0') + track_id: Mapped[int] = mapped_column(Integer, ForeignKey('track.id'), nullable=True) description: Mapped[str] = mapped_column(Text, nullable=False, server_default="") planned_speed: Mapped[int] = mapped_column(Integer, nullable=False, default=150) planned_takeoff_datetime: Mapped[datetime] = mapped_column(DateTime, nullable=True) @@ -114,21 +115,44 @@ class FlightPlan(BaseModel): copilots: Mapped[List['Copilot']] = relationship(secondary=flight_plan_has_copilot) aircraft: Mapped['Aircraft'] = relationship() - markers: Mapped['FlightPlanMarker'] = relationship() + markers: Mapped[List['FlightPlanMarker']] = relationship() created_by: Mapped['User'] = relationship() +class Track(BaseModel): + __tablename__ = "track" + + id: Mapped[int] = mapped_column(primary_key=True) + bounds: Mapped[list[tuple[float, float]]] = mapped_column(JSON()) + created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id')) + created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) + created_by: Mapped['User'] = relationship() + + +class TrackPoint(BaseModel): + __tablename__ = "track_point" + + id: Mapped[int] = mapped_column(primary_key=True) + timestamp: Mapped[datetime] = mapped_column(DateTime, nullable=False) + track_id: Mapped[id] = mapped_column(Integer, ForeignKey('track.id')) + gps_latitude: Mapped[float] = mapped_column(Float, nullable=False) + gps_longitude: Mapped[float] = mapped_column(Float, nullable=False) + elevation: Mapped[float] = mapped_column(Float, nullable=True) + + class FlightPlanMarker(BaseModel): __tablename__ = "flight_plan_marker" id: Mapped[int] = mapped_column(primary_key=True) flight_plan_id: Mapped[int] = mapped_column(Integer, ForeignKey('flight_plan.id')) + airport_id: Mapped[int] = mapped_column(Integer, ForeignKey("airport.id"), nullable=True) + point_of_interest_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest.id"), nullable=True) + weather_info_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True) name: Mapped[str] = mapped_column(String(128), nullable=False) type: Mapped[str] = mapped_column(String(64), nullable=False) position: Mapped[int] = mapped_column(Integer, nullable=False, default=0) gps_latitude: Mapped[float] = mapped_column(Float, nullable=False) gps_longitude: Mapped[float] = mapped_column(Float, nullable=False) - weather_info_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True) class Airport(BaseModel): @@ -136,7 +160,7 @@ class Airport(BaseModel): id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(128), nullable=False) - openaip_id: Mapped[str] = mapped_column(String(32), nullable=False, unique=True) + openaip_id: Mapped[str] = mapped_column(String(32), nullable=True, unique=True) icao_code: Mapped[str] = mapped_column(String(8), nullable=False) gps_latitude: Mapped[float] = mapped_column(Float, nullable=True) gps_longitude: Mapped[float] = mapped_column(Float, nullable=True) @@ -385,8 +409,10 @@ class Flight(BaseModel): landing_datetime: Mapped[datetime] = mapped_column(DateTime, nullable=False) duration_total: Mapped[int] = mapped_column(Integer, nullable=True) duration_pic: Mapped[int] = mapped_column(Integer, nullable=True) - gpx_track_filename: Mapped[str] = mapped_column(String(128), nullable=True) - has_terrain_elevation: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) + flight_plan_id: Mapped[int] = mapped_column(Integer, ForeignKey('flight_plan.id'), nullable=True) + track_id: Mapped[int] = mapped_column(Integer, ForeignKey('track.id'), nullable=True) + gpx_track_filename: Mapped[str] = mapped_column(String(128), nullable=True) # deprecated + has_terrain_elevation: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) # deprecated aircraft_id: Mapped[int] = mapped_column(Integer, ForeignKey('aircraft.id')) takeoff_weather_info_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True) landing_weather_info_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True) diff --git a/src/graphql_schema/entities/resolvers/flight.py b/src/graphql_schema/entities/resolvers/flight.py index 0c25323..e40fb9d 100644 --- a/src/graphql_schema/entities/resolvers/flight.py +++ b/src/graphql_schema/entities/resolvers/flight.py @@ -1,4 +1,6 @@ import asyncio +import random +import uuid from typing import List, Optional from sqlalchemy import delete, insert, select, func, text from sqlalchemy.ext.asyncio import AsyncSession @@ -142,12 +144,15 @@ class FlightMutationResolver(BaseMutationResolver): data.update({ "aircraft_id": aircraft_id, "has_terrain_elevation": False, - "name": "", + "name": input.name or '', "description": "", "created_by_id": context.user_id }) flight = await self._do_create(db, data) + if input.track is not None: + await handle_track_edit(db=db, flight_id=flight.id, track=input.track, user_id=user_id) + context.background_tasks.add_task( download_weather_for_flight, flight_id=flight.id, airport_id=flight.takeoff_airport_id, date_time=flight.takeoff_datetime, @@ -256,14 +261,23 @@ async def handle_track_edit(db: AsyncSession, flight_id: int, track: List[TrackI if item.point_of_interest: poi_id = await handle_combobox_save( - db, models.PointOfInterest, item.point_of_interest, user_id, extra_data={"description": ""} + db, models.PointOfInterest, item.point_of_interest, user_id, extra_data={ + "description": "", + 'gps_latitude': item.gps_latitude, + 'gps_longitude': item.gps_longitude + } ) if item.airport: airport_id = await handle_combobox_save( db, models.Airport, item.airport, user_id, name_column="icao_code", - extra_data={"name": item.airport.name} + extra_data={ + "name": item.airport.name, + 'gps_latitude': item.gps_latitude, + 'gps_longitude': item.gps_longitude, + 'openaip_id': f'custom-{random.randint(0, 999999)}' + } ) await models.FlightTrack.create( diff --git a/src/graphql_schema/entities/resolvers/flight_plan.py b/src/graphql_schema/entities/resolvers/flight_plan.py index f64eb13..4b69c67 100644 --- a/src/graphql_schema/entities/resolvers/flight_plan.py +++ b/src/graphql_schema/entities/resolvers/flight_plan.py @@ -52,9 +52,18 @@ class FlightPlanMutationResolver(BaseMutationResolver): position = 0 for marker in markers: + + if marker.type == 'poi': + assert bool(marker.point_of_interest_id) + + if marker.type == 'airport': + assert bool(marker.airport_id) + await models.FlightPlanMarker.create(db, data={ "position": position, "flight_plan_id": flight_plan.id, + "airport_id": marker.airport_id, + "point_of_interest_id": marker.point_of_interest_id, "type": marker.type, "name": marker.name, "gps_latitude": marker.gps_latitude, diff --git a/src/graphql_schema/entities/types/mutation_input.py b/src/graphql_schema/entities/types/mutation_input.py index f6f8f16..f81d7aa 100644 --- a/src/graphql_schema/entities/types/mutation_input.py +++ b/src/graphql_schema/entities/types/mutation_input.py @@ -138,11 +138,14 @@ class EditFlightInput(BaseGraphqlInputType): @strawberry.input() class CreateFlightInput(BaseGraphqlInputType): aircraft: ComboboxInput + flight_plan_id: Optional[int] = None + name: Optional[str] = None gpx_track_file: Optional[Upload] = None # TODO: poresit validaci uploadovaneho souboru! landing_airport: Optional[ComboboxInput] = None takeoff_airport: Optional[ComboboxInput] = None takeoff_datetime: Optional[datetime] = None landing_datetime: Optional[datetime] = None + track: Optional[List[TrackItemInput]] = None @strawberry.input() @@ -150,6 +153,8 @@ class TrackItemInput: point_of_interest: Optional[ComboboxInput] = None airport: Optional[ComboboxInput] = None landing_duration: Optional[int] = None + gps_latitude: Optional[float] = None + gps_longitude: Optional[float] = None @strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['id', 'photo_filename'])