diff --git a/alembic/versions/20240611-164629_add_flight_plan_description_a3e1f1a37899.py b/alembic/versions/20240611-164629_add_flight_plan_description_a3e1f1a37899.py new file mode 100644 index 0000000..533387a --- /dev/null +++ b/alembic/versions/20240611-164629_add_flight_plan_description_a3e1f1a37899.py @@ -0,0 +1,28 @@ +"""add flight_plan.description + +Revision ID: a3e1f1a37899 +Revises: 8a67af3909aa +Create Date: 2024-06-11 16:46:29.786830 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a3e1f1a37899' +down_revision = '8a67af3909aa' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('flight_plan', sa.Column('description', sa.Text(), server_default='', nullable=False)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('flight_plan', 'description') + # ### end Alembic commands ### diff --git a/alembic/versions/20240624-160801_add_track_to_flight_f9e1e8a01683.py b/alembic/versions/20240624-160801_add_track_to_flight_f9e1e8a01683.py new file mode 100644 index 0000000..ea6fc5f --- /dev/null +++ b/alembic/versions/20240624-160801_add_track_to_flight_f9e1e8a01683.py @@ -0,0 +1,54 @@ +"""add track to flight + +Revision ID: f9e1e8a01683 +Revises: a3e1f1a37899 +Create Date: 2024-06-24 16:08:01.084570 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = 'f9e1e8a01683' +down_revision = 'a3e1f1a37899' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('track', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('bounds', mysql.JSON(), nullable=False), + sa.Column('created_by_id', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), + sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('track_point', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('timestamp', sa.DateTime(), nullable=False), + sa.Column('track_id', sa.Integer(), nullable=False), + sa.Column('gps_latitude', sa.Float(), nullable=False), + sa.Column('gps_longitude', sa.Float(), nullable=False), + sa.Column('elevation', sa.Float(), nullable=True), + sa.ForeignKeyConstraint(['track_id'], ['track.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.add_column('flight', sa.Column('track_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'flight', 'track', ['track_id'], ['id']) + op.add_column('flight_plan', sa.Column('track_id', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'flight_plan', 'track', ['track_id'], ['id']) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'flight_plan', type_='foreignkey') + op.drop_column('flight_plan', 'track_id') + op.drop_constraint(None, 'flight', type_='foreignkey') + op.drop_column('flight', 'track_id') + op.drop_table('track_point') + op.drop_table('track') + # ### end Alembic commands ### diff --git a/src/database/models.py b/src/database/models.py index 0d90a74..af21ecb 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -74,7 +74,6 @@ copilot_has_photo = Table( Column("photo_id", ForeignKey("photo.id"), primary_key=True), ) - airport_has_reporting_point = Table( "airport_has_reporting_point", BaseModel.metadata, @@ -105,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') + 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) aircraft_id: Mapped[int] = mapped_column(Integer, ForeignKey("aircraft.id"), nullable=True) @@ -130,6 +130,7 @@ class FlightPlanMarker(BaseModel): 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): __tablename__ = "airport" diff --git a/src/graphql_schema/entities/airport.py b/src/graphql_schema/entities/airport.py index bad2fa5..6221b0c 100644 --- a/src/graphql_schema/entities/airport.py +++ b/src/graphql_schema/entities/airport.py @@ -11,7 +11,7 @@ from graphql_schema.entities.types.types import Airport class AirportQueries: @strawberry.field() @error_logging - @authenticated_user_only() + # @authenticated_user_only() async def airports(root, info) -> List[Airport]: return await BaseQueryResolver(Airport, models.Airport).get_list(info.context.user_id) diff --git a/src/graphql_schema/entities/flight_plan.py b/src/graphql_schema/entities/flight_plan.py index 6a754d7..a720def 100644 --- a/src/graphql_schema/entities/flight_plan.py +++ b/src/graphql_schema/entities/flight_plan.py @@ -1,11 +1,10 @@ -from typing import List +from typing import List, Optional import strawberry from strawberry.types import Info -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 BaseQueryResolver -from graphql_schema.entities.resolvers.flight_plan import FlightPlanMutationResolver +from decorators.error_logging import error_logging +from graphql_schema.entities.helpers.detail import get_detail_filters +from graphql_schema.entities.resolvers.flight_plan import FlightPlanMutationResolver, FlightPlanQueryResolver from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput, EditFlightPlanInput from graphql_schema.entities.types.types import FlightPlan @@ -16,15 +15,26 @@ class FlightPlanQueries: @error_logging @authenticated_user_only() async def flight_plans(root, info: Info) -> List[FlightPlan]: - return await BaseQueryResolver(FlightPlan, models.FlightPlan).get_list(info.context.user_id) + return await FlightPlanQueryResolver().get_list(info.context.user_id) @strawberry.field() @error_logging @allow_public - async def flight_plan(root, info: Info, id: int) -> FlightPlan: - return await BaseQueryResolver(FlightPlan, models.FlightPlan).get_one( + async def flight_plan( + root, + info: Info, + id: int, + username: Optional[str] = None, + public: Optional[bool] = False + ) -> FlightPlan: + filter_params = get_detail_filters(id) + if username: + filter_params['username'] = username + + return await FlightPlanQueryResolver().get_one( user_id=info.context.user_id, - object_id=id + only_public=public, + **filter_params ) diff --git a/src/graphql_schema/entities/helpers/detail.py b/src/graphql_schema/entities/helpers/detail.py index ddd4fc9..bb5f85e 100644 --- a/src/graphql_schema/entities/helpers/detail.py +++ b/src/graphql_schema/entities/helpers/detail.py @@ -2,7 +2,7 @@ from typing import Optional from graphql import GraphQLError -def get_detail_filters(id: Optional[int], url_slug: Optional[str]) -> dict: +def get_detail_filters(id: Optional[int] = None, url_slug: Optional[str] = None) -> dict: filter_params = {} if id: filter_params['object_id'] = id diff --git a/src/graphql_schema/entities/resolvers/flight.py b/src/graphql_schema/entities/resolvers/flight.py index 73cd47e..0c25323 100644 --- a/src/graphql_schema/entities/resolvers/flight.py +++ b/src/graphql_schema/entities/resolvers/flight.py @@ -28,17 +28,11 @@ class FlightQueryResolver(BaseQueryResolver): def get_query( self, user_id: Optional[int] = None, - object_id: Optional[int] = None, - url_slug: Optional[str] = None, only_public: Optional[bool] = False, *args, **kwargs ): - filters = {} - if object_id: - filters['object_id'] = object_id - if url_slug: - filters['url_slug'] = url_slug + filters = {key: kwargs[key] for key in ("object_id", "url_slug") if key in kwargs} query = super().get_query( user_id, diff --git a/src/graphql_schema/entities/resolvers/flight_plan.py b/src/graphql_schema/entities/resolvers/flight_plan.py index 71f3f17..f64eb13 100644 --- a/src/graphql_schema/entities/resolvers/flight_plan.py +++ b/src/graphql_schema/entities/resolvers/flight_plan.py @@ -1,19 +1,48 @@ import asyncio - +from typing import Optional from sqlalchemy import delete, select from sqlalchemy.dialects.mysql import insert - from database import models from database.models import flight_plan_has_copilot from database.transaction import get_session from graphql_schema.entities.helpers.combobox import handle_combobox_save -from graphql_schema.entities.resolvers.base import BaseMutationResolver +from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQueryResolver from graphql_schema.entities.resolvers.flight import handle_aircraft_save from graphql_schema.entities.types.mutation_input import CreateFlightPlanInput, EditFlightPlanInput from graphql_schema.entities.types.types import FlightPlan from utils.str_utils import random_str +class FlightPlanQueryResolver(BaseQueryResolver): + def __init__(self): + super().__init__(graphql_type=FlightPlan, model=models.FlightPlan) + + def get_query( + self, + user_id: Optional[int] = None, + only_public: Optional[bool] = False, + object_id: Optional[int] = None, + *args, + **kwargs + ): + filters = {"object_id": object_id} if object_id else {} + + query = super().get_query( + user_id, + **filters, + order_by=[models.FlightPlan.planned_takeoff_datetime.desc(), models.FlightPlan.id.desc()], + only_public=only_public, + only_my=not only_public + ) + if kwargs.get('username'): + query = ( + query.join(models.FlightPlan.created_by) + .filter(models.User.public_username == kwargs['username']) + ) + + return query + + class FlightPlanMutationResolver(BaseMutationResolver): def __init__(self): super().__init__(graphql_type=FlightPlan, model=models.FlightPlan) @@ -100,4 +129,4 @@ def evenly_spaced_elements(lst: list, count: int) -> list: if count > len(lst): return lst interval = (len(lst) - 1) / (count - 1) if count > 1 else 0 - return [lst[int(round(i * interval))] for i in range(count)] + return [lst[int(round(i * interval))] for i in range(count)]