diff --git a/alembic/versions/20240324-215512_make_markers_in_flight_planner_separate__de70fc471699.py b/alembic/versions/20240324-215512_make_markers_in_flight_planner_separate__de70fc471699.py new file mode 100644 index 0000000..440452a --- /dev/null +++ b/alembic/versions/20240324-215512_make_markers_in_flight_planner_separate__de70fc471699.py @@ -0,0 +1,38 @@ +"""make markers in flight planner separate table + +Revision ID: de70fc471699 +Revises: 7a3738a805c5 +Create Date: 2024-03-24 21:55:12.307588 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = 'de70fc471699' +down_revision = '7a3738a805c5' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('flight_plan_marker', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('flight_plan_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=128), nullable=False), + sa.Column('gps_latitude', sa.Float(), nullable=True), + sa.Column('gps_longitude', sa.Float(), nullable=True), + sa.ForeignKeyConstraint(['flight_plan_id'], ['flight_plan.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.drop_column('flight_plan', 'markers') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('flight_plan', sa.Column('markers', mysql.LONGTEXT(charset='utf8mb4', collation='utf8mb4_bin'), nullable=False)) + op.drop_table('flight_plan_marker') + # ### end Alembic commands ### diff --git a/alembic/versions/20240324-220035_add_type_and_position_to_flight_plan__70152c47cb49.py b/alembic/versions/20240324-220035_add_type_and_position_to_flight_plan__70152c47cb49.py new file mode 100644 index 0000000..e19bdba --- /dev/null +++ b/alembic/versions/20240324-220035_add_type_and_position_to_flight_plan__70152c47cb49.py @@ -0,0 +1,42 @@ +"""add type and position to flight_plan_marker + +Revision ID: 70152c47cb49 +Revises: de70fc471699 +Create Date: 2024-03-24 22:00:35.234920 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '70152c47cb49' +down_revision = 'de70fc471699' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('flight_plan_marker', sa.Column('type', sa.String(length=64), nullable=False)) + op.add_column('flight_plan_marker', sa.Column('position', sa.Integer(), nullable=False)) + op.alter_column('flight_plan_marker', 'gps_latitude', + existing_type=mysql.FLOAT(), + nullable=False) + op.alter_column('flight_plan_marker', 'gps_longitude', + existing_type=mysql.FLOAT(), + nullable=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('flight_plan_marker', 'gps_longitude', + existing_type=mysql.FLOAT(), + nullable=True) + op.alter_column('flight_plan_marker', 'gps_latitude', + existing_type=mysql.FLOAT(), + nullable=True) + op.drop_column('flight_plan_marker', 'position') + op.drop_column('flight_plan_marker', 'type') + # ### end Alembic commands ### diff --git a/src/database/models.py b/src/database/models.py index 6092594..60bacfe 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -86,7 +86,6 @@ class FlightPlan(BaseModel): id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(128), nullable=False) - markers: Mapped[dict] = mapped_column(JSON()) is_public: Mapped[bool] = mapped_column(Boolean, server_default='0') created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id')) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) @@ -95,6 +94,18 @@ class FlightPlan(BaseModel): created_by: Mapped['User'] = relationship() +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')) + 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) + + class Airport(BaseModel): __tablename__ = "airport" diff --git a/src/graphql_schema/dataloaders/multi_models.py b/src/graphql_schema/dataloaders/multi_models.py index f3f7479..875a4b9 100644 --- a/src/graphql_schema/dataloaders/multi_models.py +++ b/src/graphql_schema/dataloaders/multi_models.py @@ -150,3 +150,12 @@ photos_aircraft_dataloader = DataLoader( ).load, cache=False ) + +flight_plan_markers_dataloader = DataLoader( + load_fn=MultiModelsDataloader( + models.FlightPlanMarker, + relationship_column=models.FlightPlanMarker.flight_plan_id, + order_by=[models.FlightPlanMarker.position, models.FlightPlanMarker.id] + ).load, + cache=False +) diff --git a/src/graphql_schema/entities/flight_plan.py b/src/graphql_schema/entities/flight_plan.py index 278e33a..53fd228 100644 --- a/src/graphql_schema/entities/flight_plan.py +++ b/src/graphql_schema/entities/flight_plan.py @@ -6,6 +6,7 @@ from database import models from decorators.error_logging import error_logging from decorators.endpoints import authenticated_user_only, allow_public from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQueryResolver +from graphql_schema.entities.resolvers.flight_plan import FlightPlanMutationResolver from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput, EditFlightPlanInput from graphql_schema.entities.types.types import FlightPlan @@ -21,14 +22,9 @@ class FlightPlanQueries: @strawberry.field() @error_logging @allow_public - async def flight_plan( - root, info: Info, - id: Optional[int] = None, - public: Optional[bool] = False - ) -> FlightPlan: + async def flight_plan(root, info: Info, id: int) -> FlightPlan: return await BaseQueryResolver(FlightPlan, models.FlightPlan).get_one( user_id=info.context.user_id, - only_public=public, object_id=id ) @@ -39,10 +35,10 @@ class FlightPlanMutation: @error_logging @authenticated_user_only() async def create_flight_plan(root, info, input: CreateFlightPlanInput) -> FlightPlan: - return await BaseMutationResolver(FlightPlan, models.FlightPlan).create(info.context, data=input) + return await FlightPlanMutationResolver().create(info.context, data=input) @strawberry.mutation @error_logging @authenticated_user_only() async def edit_flight_plan(root, info, id: int, input: EditFlightPlanInput) -> FlightPlan: - return await BaseMutationResolver(FlightPlan, models.FlightPlan).update(id, input, info.context.user_id) + return await FlightPlanMutationResolver().update(id, input, info.context.user_id) diff --git a/src/graphql_schema/entities/resolvers/flight_plan.py b/src/graphql_schema/entities/resolvers/flight_plan.py new file mode 100644 index 0000000..c64cfe3 --- /dev/null +++ b/src/graphql_schema/entities/resolvers/flight_plan.py @@ -0,0 +1,30 @@ +from database import models +from database.transaction import get_session +from graphql_schema.entities.resolvers.base import BaseMutationResolver +from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput +from graphql_schema.entities.types.types import FlightPlan + + +class FlightPlanMutationResolver(BaseMutationResolver): + def __init__(self): + super().__init__(graphql_type=FlightPlan, model=models.FlightPlan) + + async def create(self, context, data: CreateFlightPlanInput) -> FlightPlan: + input_data = data.to_dict() + input_data['created_by_id'] = context.user_id + + async with get_session() as db: + flight_plan = await self._do_create(db, data=input_data) + + position = 0 + for marker in data.markers: + await models.FlightPlanMarker.create(db, data={ + "position": position, + "flight_plan_id": flight_plan.id, + "type": marker.type, + "name": marker.name, + "gps_latitude": marker.gps_latitude, + "gps_longitude": marker.gps_longitude + }) + + return flight_plan diff --git a/src/graphql_schema/entities/types/mutation_input.py b/src/graphql_schema/entities/types/mutation_input.py index f3a82fc..02e7afa 100644 --- a/src/graphql_schema/entities/types/mutation_input.py +++ b/src/graphql_schema/entities/types/mutation_input.py @@ -14,11 +14,9 @@ class ComboboxInput: name: str -@strawberry.input() -class FlightPlanMarkerInput: - name: str - gps_latitude: float - gps_longitude: float +@strawberry_sqlalchemy_input(model=models.FlightPlanMarker, exclude_fields=["id", "flight_plan_id"]) +class FlightPlanMarkerInput(BaseGraphqlInputType): + position: Optional[int] = 0 @strawberry_sqlalchemy_input(model=models.Copilot, exclude_fields=["id"]) @@ -30,6 +28,11 @@ class CreateCopilotInput(BaseGraphqlInputType): class CreateFlightPlanInput(BaseGraphqlInputType): markers: list[FlightPlanMarkerInput] + def to_dict(self) -> dict: + data = super().to_dict() + data['markers'] = [marker.to_dict() for marker in self.markers] + return data + @strawberry_sqlalchemy_input(model=models.FlightPlan, exclude_fields=["id", "markers"], all_optional=True) class EditFlightPlanInput(BaseGraphqlInputType): diff --git a/src/graphql_schema/entities/types/types.py b/src/graphql_schema/entities/types/types.py index fd6a58a..7fb9610 100644 --- a/src/graphql_schema/entities/types/types.py +++ b/src/graphql_schema/entities/types/types.py @@ -11,7 +11,7 @@ from graphql_schema.dataloaders.multi_models import ( photos_dataloader, flights_by_aircraft_dataloader, users_in_organization_dataloader, aircrafts_from_organization_dataloader, user_organizations_dataloader, flights_by_event_dataloader, flights_by_copilot_dataloader, public_flights_by_event_dataloader, public_flights_by_copilot_dataloader, - photo_copilots_dataloader, photos_aircraft_dataloader, copilots_in_photo_dataloader + photo_copilots_dataloader, photos_aircraft_dataloader, copilots_in_photo_dataloader, flight_plan_markers_dataloader ) from graphql_schema.dataloaders.single_model import ( poi_dataloader, poi_type_dataloader, event_dataloader, aircraft_dataloader, airport_dataloader, @@ -180,16 +180,16 @@ class Flight: ) -@strawberry.type +@strawberry_sqlalchemy_type(model=models.FlightPlanMarker) class FlightPlanMarker: - name: str - gps_latitude: float - gps_longitude: float + pass -@strawberry_sqlalchemy_type(models.FlightPlan, exclude_fields=["markers"]) +@strawberry_sqlalchemy_type(model=models.FlightPlan) class FlightPlan: - markers: list[FlightPlanMarker] = strawberry.field() + markers: list[FlightPlanMarker] = strawberry.field( + resolver=lambda root: flight_plan_markers_dataloader.load(root.id) + ) @strawberry_sqlalchemy_type(models.Copilot)