Slugy, webp, hromada zmen
This commit is contained in:
@@ -29,12 +29,25 @@ class AircraftQueries:
|
||||
@strawberry.field()
|
||||
@error_logging
|
||||
@allow_public
|
||||
async def aircraft(root, info, id: int, public: Optional[bool] = False) -> Aircraft:
|
||||
async def aircraft(
|
||||
root, info,
|
||||
id: Optional[int] = None,
|
||||
call_sign: Optional[str] = None,
|
||||
public: Optional[bool] = False
|
||||
) -> Aircraft:
|
||||
filter_params = {}
|
||||
|
||||
if id:
|
||||
filter_params['object_id'] = id
|
||||
|
||||
if call_sign:
|
||||
filter_params['call_sign'] = call_sign
|
||||
|
||||
return await AircraftQueryResolver().get_one(
|
||||
id,
|
||||
user_id=info.context.user_id,
|
||||
organization_ids=info.context.organization_ids,
|
||||
public=public
|
||||
only_public=public,
|
||||
**filter_params
|
||||
)
|
||||
|
||||
|
||||
@@ -44,13 +57,13 @@ class AircraftMutation:
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def create_aircraft(root, info, input: CreateAircraftInput) -> Aircraft:
|
||||
return await AircraftMutationResolver().create_new(input, info.context.user_id)
|
||||
return await AircraftMutationResolver().create(info.context, input)
|
||||
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def edit_aircraft(root, info, id: int, input: EditAircraftInput) -> Aircraft:
|
||||
return await AircraftMutationResolver().edit(id, user_id=info.context.user_id, data=input)
|
||||
return await AircraftMutationResolver().update(id, user_id=info.context.user_id, data=input)
|
||||
|
||||
@strawberry.mutation
|
||||
@authenticated_user_only()
|
||||
|
||||
@@ -19,4 +19,7 @@ class AirportQueries:
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def airport(root, info, id: int) -> Airport:
|
||||
return await BaseQueryResolver(Airport, models.Airport).get_one(id, user_id=info.context.user_id)
|
||||
return await BaseQueryResolver(Airport, models.Airport).get_one(
|
||||
object_id=id,
|
||||
user_id=info.context.user_id
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ 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.helpers.detail import get_detail_filters
|
||||
from graphql_schema.entities.resolvers.base import BaseMutationResolver
|
||||
from graphql_schema.entities.resolvers.copilot import CopilotQueryResolver
|
||||
from graphql_schema.entities.types.mutation_input import CreateCopilotInput, EditCopilotInput
|
||||
@@ -21,11 +22,22 @@ class CopilotQueries:
|
||||
@strawberry.field()
|
||||
@error_logging
|
||||
@allow_public
|
||||
async def copilot(root, info: Info, id: int, pilot_username: Optional[str] = None) -> Copilot:
|
||||
async def copilot(
|
||||
root, info: Info,
|
||||
id: Optional[int] = None,
|
||||
url_slug: Optional[str] = None,
|
||||
pilot_username: Optional[str] = None,
|
||||
public: Optional[bool] = False
|
||||
) -> Copilot:
|
||||
|
||||
filter_params = get_detail_filters(id, url_slug)
|
||||
if pilot_username:
|
||||
filter_params['pilot_username'] = pilot_username
|
||||
|
||||
return await CopilotQueryResolver().get_one(
|
||||
id,
|
||||
user_id=info.context.user_id,
|
||||
pilot_username=pilot_username
|
||||
only_public=public,
|
||||
**filter_params
|
||||
)
|
||||
|
||||
|
||||
@@ -35,10 +47,7 @@ class CopilotMutation:
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def create_copilot(root, info, input: CreateCopilotInput) -> Copilot:
|
||||
return await BaseMutationResolver(Copilot, models.Copilot).create(
|
||||
data=input.to_dict(),
|
||||
user_id=info.context.user_id
|
||||
)
|
||||
return await BaseMutationResolver(Copilot, models.Copilot).create(info.context, data=input)
|
||||
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
|
||||
@@ -3,6 +3,7 @@ import strawberry
|
||||
from database import models
|
||||
from decorators.endpoints import authenticated_user_only, allow_public
|
||||
from decorators.error_logging import error_logging
|
||||
from graphql_schema.entities.helpers.detail import get_detail_filters
|
||||
from graphql_schema.entities.helpers.pagination import PaginationWindow, get_pagination_window
|
||||
from graphql_schema.entities.resolvers.base import BaseMutationResolver
|
||||
from graphql_schema.entities.resolvers.event import EventQueryResolver
|
||||
@@ -40,12 +41,21 @@ class EventQueries:
|
||||
@strawberry.field()
|
||||
@error_logging
|
||||
@allow_public
|
||||
async def event(root, info, id: int, username: Optional[str] = None, public: Optional[bool] = False) -> Event:
|
||||
async def event(
|
||||
root, info,
|
||||
id: Optional[int] = None,
|
||||
url_slug: Optional[str] = None,
|
||||
username: Optional[str] = None,
|
||||
public: Optional[bool] = False
|
||||
) -> Event:
|
||||
filter_params = get_detail_filters(id, url_slug)
|
||||
if username:
|
||||
filter_params['username'] = username
|
||||
|
||||
return await EventQueryResolver().get_one(
|
||||
id,
|
||||
username=username,
|
||||
public=public,
|
||||
user_id=info.context.user_id
|
||||
user_id=info.context.user_id ,
|
||||
** filter_params
|
||||
)
|
||||
|
||||
|
||||
@@ -55,7 +65,7 @@ class EventMutation:
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def create_event(root, info, input: CreateEventInput) -> Event:
|
||||
return await BaseMutationResolver(Event, models.Event).create(input.to_dict(), info.context.user_id)
|
||||
return await BaseMutationResolver(Event, models.Event).create(info.context, input)
|
||||
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
|
||||
@@ -10,6 +10,7 @@ from graphql_schema.entities.resolvers.flight import handle_aircraft_save, Fligh
|
||||
from graphql_schema.entities.types.mutation_input import EditFlightInput, CreateFlightInput
|
||||
from graphql_schema.entities.types.types import Flight
|
||||
from .helpers.combobox import handle_combobox_save
|
||||
from .helpers.detail import get_detail_filters
|
||||
from .helpers.pagination import PaginationWindow, get_pagination_window
|
||||
|
||||
|
||||
@@ -50,12 +51,21 @@ class FlightQueries:
|
||||
@strawberry.field()
|
||||
@error_logging
|
||||
@allow_public
|
||||
async def flight(root, info, id: int, username: Optional[str] = None, public: Optional[bool] = False) -> Flight:
|
||||
async def flight(
|
||||
root, info,
|
||||
id: Optional[int] = None,
|
||||
url_slug: Optional[str] = None,
|
||||
username: Optional[str] = None,
|
||||
public: Optional[bool] = False
|
||||
) -> Flight:
|
||||
filter_params = get_detail_filters(id, url_slug)
|
||||
if username:
|
||||
filter_params['username'] = username
|
||||
|
||||
return await FlightQueryResolver().get_one(
|
||||
id,
|
||||
user_id=info.context.user_id,
|
||||
username=username,
|
||||
public=public
|
||||
only_public=public,
|
||||
**filter_params
|
||||
)
|
||||
|
||||
|
||||
@@ -65,43 +75,7 @@ class FlightMutation:
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def create_flight(self, info, input: CreateFlightInput) -> Flight:
|
||||
data = input.to_dict()
|
||||
user_id = info.context.user_id
|
||||
async with get_session() as db:
|
||||
aircraft_id = await handle_aircraft_save(db, info.context.user_id, input.aircraft)
|
||||
|
||||
# TODO: tohle je blbost, bude to vyrabet dve stejne instance!
|
||||
takeoff_airport_id, landing_airport_id = await asyncio.gather(
|
||||
handle_combobox_save(
|
||||
db, models.Airport, input.takeoff_airport, user_id, name_column="icao_code",
|
||||
extra_data={"name": input.takeoff_airport.name}
|
||||
),
|
||||
handle_combobox_save(
|
||||
db, models.Airport, input.landing_airport, user_id, name_column="icao_code",
|
||||
extra_data={"name": input.landing_airport.name}
|
||||
)
|
||||
)
|
||||
|
||||
data.update({
|
||||
"takeoff_airport_id": takeoff_airport_id,
|
||||
"landing_airport_id": landing_airport_id,
|
||||
"aircraft_id": aircraft_id,
|
||||
"has_terrain_elevation": False,
|
||||
"name": "",
|
||||
"description": ""
|
||||
})
|
||||
flight = await FlightMutationResolver().create(data, info.context.user_id)
|
||||
|
||||
info.context.background_tasks.add_task(
|
||||
download_weather,
|
||||
flight_id=flight.id, airport_id=takeoff_airport_id, date_time=flight.takeoff_datetime, type_="takeoff"
|
||||
)
|
||||
info.context.background_tasks.add_task(
|
||||
download_weather,
|
||||
flight_id=flight.id, airport_id=landing_airport_id, date_time=flight.landing_datetime, type_="landing"
|
||||
)
|
||||
|
||||
return flight
|
||||
return await FlightMutationResolver().create(info.context, input)
|
||||
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
|
||||
@@ -15,10 +15,11 @@ async def handle_combobox_save(
|
||||
if input.id:
|
||||
return input.id
|
||||
else:
|
||||
|
||||
if not extra_data:
|
||||
extra_data = {}
|
||||
|
||||
# TODO: vyhledat podle `name_column` existujici zaznam, pokud nebude, az pak vytvorit
|
||||
|
||||
data = {name_column: input.name, **extra_data}
|
||||
if hasattr(model, "created_by_id"):
|
||||
data["created_by_id"] = user_id
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from typing import Optional
|
||||
from graphql import GraphQLError
|
||||
|
||||
|
||||
def get_detail_filters(id: Optional[int], url_slug: Optional[str]) -> dict:
|
||||
filter_params = {}
|
||||
if id:
|
||||
filter_params['object_id'] = id
|
||||
if url_slug is not None:
|
||||
filter_params['url_slug'] = url_slug
|
||||
|
||||
if not filter_params:
|
||||
raise GraphQLError("You must specifiy either urlSlug or id!")
|
||||
|
||||
return filter_params
|
||||
@@ -19,14 +19,18 @@ class OrganizationQueries:
|
||||
@authenticated_user_only()
|
||||
async def organizations(root, info) -> List[Organization]:
|
||||
return await BaseQueryResolver(Organization, models.Organization).get_list(
|
||||
info.context.user_id, order_by=[models.Organization.name]
|
||||
info.context.user_id,
|
||||
order_by=[models.Organization.name]
|
||||
)
|
||||
|
||||
@strawberry.field()
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def organization(root, info, id: int) -> Organization:
|
||||
return await BaseQueryResolver(Organization, models.Organization).get_one(id, info.context.user_id)
|
||||
return await BaseQueryResolver(Organization, models.Organization).get_one(
|
||||
object_id=id,
|
||||
user_id=info.context.user_id
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
@@ -36,24 +40,17 @@ class OrganizationMutation:
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def create_organization(root, info, input: CreateOrganizationInput) -> Organization:
|
||||
return await BaseMutationResolver(Organization, models.Organization).create(
|
||||
data=input.to_dict(),
|
||||
user_id=info.context.user_id
|
||||
)
|
||||
return await BaseMutationResolver(Organization, models.Organization).create(info.context, data=input)
|
||||
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
@authenticated_user_only()
|
||||
async def edit_organization(root, info, id: int, input: EditOrganizationInput) -> Organization:
|
||||
async with get_session() as db:
|
||||
organization = (await db.scalars(
|
||||
BaseQueryResolver(Organization, models.Organization).get_query(
|
||||
user_id=info.context.user_id, object_id=id
|
||||
)
|
||||
)).one()
|
||||
|
||||
updated_organization = await models.Organization.update(db, obj=organization, data=input.to_dict())
|
||||
return Organization(**updated_organization.as_dict())
|
||||
return await BaseMutationResolver(Organization, models.Organization).update(
|
||||
id,
|
||||
data=input,
|
||||
user_id=info.context.user_id
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
|
||||
@@ -36,7 +36,11 @@ class PhotoQueries:
|
||||
@error_logging
|
||||
@allow_public
|
||||
async def photo(root, info, id: int, public: Optional[bool] = False,) -> Photo:
|
||||
return await BaseQueryResolver(Photo, models.Photo).get_one(id, user_id=info.context.user_id, public=public)
|
||||
return await BaseQueryResolver(Photo, models.Photo).get_one(
|
||||
object_id=id,
|
||||
user_id=info.context.user_id,
|
||||
only_public=public
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
import strawberry
|
||||
from graphql import GraphQLError
|
||||
|
||||
from database import models
|
||||
from decorators.endpoints import authenticated_user_only, allow_public
|
||||
from database.transaction import get_session
|
||||
from decorators.error_logging import error_logging
|
||||
from graphql_schema.entities.helpers.combobox import handle_combobox_save
|
||||
from graphql_schema.entities.helpers.detail import get_detail_filters
|
||||
from graphql_schema.entities.helpers.pagination import get_pagination_window, PaginationWindow
|
||||
from graphql_schema.entities.resolvers.base import BaseQueryResolver, BaseMutationResolver
|
||||
from graphql_schema.entities.types.types import PointOfInterest
|
||||
from graphql_schema.entities.types.mutation_input import CreatePointOfInterestInput, EditPointOfInterestInput
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class PointOfInterestQueries:
|
||||
@strawberry.field()
|
||||
@@ -33,10 +35,18 @@ class PointOfInterestQueries:
|
||||
|
||||
@strawberry.field()
|
||||
@allow_public
|
||||
async def point_of_interest(root, info, id: int, public: bool = False) -> PointOfInterest:
|
||||
async def point_of_interest(
|
||||
root, info,
|
||||
url_slug: Optional[str] = None,
|
||||
id: Optional[int] = None,
|
||||
public: bool = False
|
||||
) -> PointOfInterest:
|
||||
filter_params = get_detail_filters(id, url_slug)
|
||||
|
||||
return await BaseQueryResolver(PointOfInterest, models.PointOfInterest).get_one(
|
||||
id, info.context.user_id,
|
||||
only_public=public
|
||||
user_id=info.context.user_id,
|
||||
only_public=public,
|
||||
**filter_params
|
||||
)
|
||||
|
||||
|
||||
@@ -54,9 +64,10 @@ class PointOfInterestMutation:
|
||||
db, models.PointOfInterestType, input.type, info.context.user_id
|
||||
)
|
||||
|
||||
return await BaseMutationResolver(PointOfInterest, models.PointOfInterest).create(
|
||||
input_data, info.context.user_id
|
||||
)
|
||||
input_data['created_by_id'] = info.context.user_id
|
||||
return await BaseMutationResolver(PointOfInterest, models.PointOfInterest)._do_create(
|
||||
db, input_data
|
||||
)
|
||||
|
||||
@strawberry.mutation
|
||||
@error_logging
|
||||
|
||||
@@ -21,7 +21,8 @@ class PointOfInterestTypeQueries:
|
||||
@authenticated_user_only()
|
||||
async def point_of_interest_type(root, info, id: int) -> PointOfInterestType:
|
||||
return await BaseQueryResolver(PointOfInterestType, models.PointOfInterestType).get_one(
|
||||
id, info.context.user_id
|
||||
object_id=id,
|
||||
user_id=info.context.user_id
|
||||
)
|
||||
|
||||
#
|
||||
|
||||
@@ -21,10 +21,17 @@ class AircraftQueryResolver(BaseQueryResolver):
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
call_sign = kwargs.get("call_sign")
|
||||
filters = {}
|
||||
if object_id:
|
||||
filters['object_id'] = object_id
|
||||
if call_sign:
|
||||
filters['call_sign'] = call_sign
|
||||
|
||||
query = super().get_query(
|
||||
order_by=[models.Aircraft.id.desc()],
|
||||
object_id=object_id,
|
||||
user_id=user_id if not organization_ids else None
|
||||
user_id=user_id if not organization_ids else None,
|
||||
**filters
|
||||
)
|
||||
if organization_ids:
|
||||
query = (
|
||||
@@ -43,20 +50,22 @@ class AircraftMutationResolver(BaseMutationResolver):
|
||||
def __init__(self):
|
||||
super().__init__(graphql_type=Aircraft, model=models.Aircraft)
|
||||
|
||||
async def create_new(self, data: CreateAircraftInput, user_id: int) -> Aircraft:
|
||||
async def create(self, context, data: CreateAircraftInput) -> Aircraft:
|
||||
input_data = data.to_dict()
|
||||
if data.organization:
|
||||
async with get_session() as db:
|
||||
|
||||
async with get_session() as db:
|
||||
input_data['created_by_id'] = context.user_id
|
||||
if data.organization:
|
||||
input_data['organization_id'] = await handle_combobox_save(
|
||||
db,
|
||||
models.Organization,
|
||||
input=data.organization,
|
||||
user_id=user_id,
|
||||
user_id=context.user_id,
|
||||
)
|
||||
|
||||
return await self.create(data=input_data, user_id=user_id)
|
||||
return await self._do_create(db, data=input_data)
|
||||
|
||||
async def edit(self, id: int, user_id: int, data: EditAircraftInput) -> Aircraft:
|
||||
async def update(self, id: int, user_id: int, data: EditAircraftInput) -> Aircraft:
|
||||
update_data = data.to_dict()
|
||||
async with get_session() as db:
|
||||
if data.organization:
|
||||
@@ -66,5 +75,5 @@ class AircraftMutationResolver(BaseMutationResolver):
|
||||
input=data.organization,
|
||||
user_id=user_id,
|
||||
)
|
||||
aircraft = await models.Aircraft.update(db, id=id, data=update_data)
|
||||
return Aircraft(**aircraft.as_dict())
|
||||
|
||||
return await self._do_update(db, id, update_data)
|
||||
|
||||
@@ -33,7 +33,6 @@ class BaseQueryResolver(BaseResolver):
|
||||
object_id: Optional[int] = None,
|
||||
order_by: Optional[list] = None,
|
||||
only_public: Optional[bool] = False,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
query = self.query_builder.get_simple_query(created_by_id=user_id, order_by=order_by, only_public=only_public)
|
||||
@@ -52,8 +51,8 @@ class BaseQueryResolver(BaseResolver):
|
||||
query = self.get_query(user_id, **kwargs)
|
||||
return await self._get_list(query)
|
||||
|
||||
async def get_one(self, id: int, user_id: Optional[int] = None, **kwargs) -> GQL_TYPE:
|
||||
query = self.get_query(user_id, object_id=id, **kwargs)
|
||||
async def get_one(self, user_id: Optional[int] = None, **kwargs) -> GQL_TYPE:
|
||||
query = self.get_query(user_id, **kwargs)
|
||||
return await self._get_one(query)
|
||||
|
||||
|
||||
@@ -62,21 +61,31 @@ class BaseMutationResolver(BaseResolver):
|
||||
query = self.query_builder.get_simple_query(created_by_id=created_by_id).filter(self.model.id == id)
|
||||
return (await db.scalars(query)).one()
|
||||
|
||||
async def _do_create(self, data) -> GQL_TYPE:
|
||||
async with get_session() as db:
|
||||
model = await self.model.create(db, data=data)
|
||||
return self.graphql_type(**model.as_dict())
|
||||
async def _do_create(self, db: AsyncSession, data: dict) -> GQL_TYPE:
|
||||
model = await self.model.create(db, data=data)
|
||||
return self.graphql_type(**model.as_dict())
|
||||
|
||||
async def _do_update(self, db: AsyncSession, obj: models.BaseModel | dict, data: dict) -> GQL_TYPE:
|
||||
async def _do_update(self, db: AsyncSession, obj: models.BaseModel | dict | int, data: dict) -> GQL_TYPE:
|
||||
update_where = {}
|
||||
if isinstance(obj, models.BaseModel):
|
||||
update_where['obj'] = obj
|
||||
else:
|
||||
elif isinstance(obj, dict):
|
||||
update_where['id'] = obj['id']
|
||||
else:
|
||||
update_where['id'] = obj
|
||||
|
||||
model = await self.model.update(db, data=data, **update_where)
|
||||
return self.graphql_type(**model.as_dict())
|
||||
|
||||
async def create(self, context, data: BaseGraphqlInputType) -> GQL_TYPE:
|
||||
input_data = data.to_dict()
|
||||
|
||||
if hasattr(self.model, "created_by_id"):
|
||||
input_data['created_by_id'] = context.user_id
|
||||
|
||||
async with get_session() as db:
|
||||
return await self._do_create(db, input_data)
|
||||
|
||||
async def update(self, id: int, data: BaseGraphqlInputType, user_id: int) -> GQL_TYPE:
|
||||
async with get_session() as db:
|
||||
item = await self._get_one(db, id, user_id)
|
||||
@@ -93,9 +102,3 @@ class BaseMutationResolver(BaseResolver):
|
||||
|
||||
return self.graphql_type(**model.as_dict())
|
||||
|
||||
async def create(self, data: dict, user_id: Optional[int] = None) -> GQL_TYPE:
|
||||
input_data = {**data}
|
||||
if hasattr(self.model, "created_by_id"):
|
||||
input_data['created_by_id'] = user_id
|
||||
|
||||
return await self._do_create(input_data)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
from typing import List, Optional
|
||||
from sqlalchemy import delete, insert
|
||||
from sqlalchemy import delete, insert, select, func, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from strawberry.file_uploads import Upload
|
||||
from background_jobs.elevation import add_terrain_elevation_to_flight
|
||||
@@ -8,9 +8,10 @@ from background_jobs.weather import download_weather
|
||||
from database import models
|
||||
from database.models import flight_has_copilot
|
||||
from database.transaction import get_session
|
||||
from external.gpx_parser import GPXParser
|
||||
from graphql_schema.entities.helpers.combobox import handle_combobox_save
|
||||
from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQueryResolver
|
||||
from graphql_schema.entities.types.mutation_input import EditFlightInput, TrackItemInput, ComboboxInput
|
||||
from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQueryResolver, GQL_TYPE
|
||||
from graphql_schema.entities.types.mutation_input import EditFlightInput, TrackItemInput, ComboboxInput, CreateFlightInput
|
||||
from graphql_schema.entities.types.types import Flight
|
||||
from paths import FLIGHT_GPX_TRACK_PATH
|
||||
from utils.file import delete_file
|
||||
@@ -25,12 +26,20 @@ class FlightQueryResolver(BaseQueryResolver):
|
||||
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
|
||||
|
||||
query = super().get_query(
|
||||
user_id, object_id,
|
||||
user_id,
|
||||
**filters,
|
||||
order_by=[models.Flight.takeoff_datetime.desc()],
|
||||
only_public=only_public
|
||||
)
|
||||
@@ -66,6 +75,90 @@ class FlightMutationResolver(BaseMutationResolver):
|
||||
def __init__(self):
|
||||
super().__init__(Flight, models.Flight)
|
||||
|
||||
async def get_airport_id_by_gps(self, gps_lat: float, gps_lng: float) -> Optional[int]:
|
||||
async with get_session() as db:
|
||||
query = select(
|
||||
models.Airport,
|
||||
func.coalesce((6371 * func.acos(
|
||||
func.cos(func.radians(gps_lat)) *
|
||||
func.cos(func.radians(models.Airport.gps_latitude)) *
|
||||
func.cos(func.radians(models.Airport.gps_longitude) - func.radians(gps_lng)) +
|
||||
func.sin(func.radians(gps_lat)) *
|
||||
func.sin(func.radians(models.Airport.gps_latitude))
|
||||
)), 9999).label("distance")
|
||||
).filter(models.Airport.use_in_gpx_guess.is_(True)).order_by("distance").having(text("distance < 1")).limit(1)
|
||||
|
||||
data = (await db.execute(query)).one_or_none()
|
||||
|
||||
if data:
|
||||
airport, distance = data
|
||||
return airport.id
|
||||
|
||||
return None
|
||||
|
||||
async def extract_data_from_gpx(self, gpx_filename: str) -> dict:
|
||||
data = GPXParser(f"{FLIGHT_GPX_TRACK_PATH}/{gpx_filename}")
|
||||
|
||||
times, coordinates = await asyncio.gather(
|
||||
data.get_times(),
|
||||
data.get_coordinates()
|
||||
)
|
||||
takeoff_airport_id, landing_airport_id = await asyncio.gather(
|
||||
self.get_airport_id_by_gps(coordinates[0]['lat'], coordinates[0]['lng']),
|
||||
self.get_airport_id_by_gps(coordinates[-1]['lat'], coordinates[-1]['lng']),
|
||||
)
|
||||
|
||||
return {
|
||||
"takeoff_airport_id": takeoff_airport_id,
|
||||
"landing_airport_id": landing_airport_id,
|
||||
"takeoff_datetime": times[0],
|
||||
"landing_datetime": times[-1],
|
||||
}
|
||||
|
||||
async def create(self, context, input: CreateFlightInput) -> Flight:
|
||||
data = input.to_dict()
|
||||
user_id = context.user_id
|
||||
|
||||
if input.gpx_track_file:
|
||||
data['gpx_track_filename'] = await handle_upload_gpx(gpx_track=input.gpx_track_file, context=context)
|
||||
data_from_gpx = await self.extract_data_from_gpx(data['gpx_track_filename'])
|
||||
data.update(data_from_gpx)
|
||||
else:
|
||||
async with get_session() as db:
|
||||
data.update({
|
||||
"takeoff_airport_id": await handle_combobox_save(
|
||||
db, models.Airport, input.takeoff_airport, user_id, name_column="icao_code",
|
||||
extra_data={"name": input.takeoff_airport.name}
|
||||
),
|
||||
"landing_airport_id": await handle_combobox_save(
|
||||
db, models.Airport, input.landing_airport, user_id, name_column="icao_code",
|
||||
extra_data={"name": input.landing_airport.name}
|
||||
),
|
||||
})
|
||||
|
||||
async with get_session() as db:
|
||||
aircraft_id = await handle_aircraft_save(db, context.user_id, input.aircraft)
|
||||
|
||||
data.update({
|
||||
"aircraft_id": aircraft_id,
|
||||
"has_terrain_elevation": False,
|
||||
"name": "",
|
||||
"description": "",
|
||||
"created_by_id": context.user_id
|
||||
})
|
||||
flight = await self._do_create(db, data)
|
||||
|
||||
context.background_tasks.add_task(
|
||||
download_weather,
|
||||
flight_id=flight.id, airport_id=flight.takeoff_airport_id, date_time=flight.takeoff_datetime, type_="takeoff"
|
||||
)
|
||||
context.background_tasks.add_task(
|
||||
download_weather,
|
||||
flight_id=flight.id, airport_id=flight.landing_airport_id, date_time=flight.landing_datetime, type_="landing"
|
||||
)
|
||||
|
||||
return flight
|
||||
|
||||
async def update(self, context, id: int, input: EditFlightInput) -> Flight:
|
||||
user_id = context.user_id
|
||||
async with get_session() as db:
|
||||
@@ -75,10 +168,11 @@ class FlightMutationResolver(BaseMutationResolver):
|
||||
|
||||
data = input.to_dict()
|
||||
|
||||
if input.gpx_track is not None:
|
||||
data['gpx_track_filename'] = await handle_upload_gpx(flight_data['gpx_track_filename'], input.gpx_track)
|
||||
context.background_tasks.add_task(
|
||||
add_terrain_elevation_to_flight, flight_id=id, gpx_filename=data['gpx_track_filename']
|
||||
if input.gpx_track_file is not None:
|
||||
data['gpx_track_filename'] = await handle_upload_gpx(
|
||||
gpx_track=input.gpx_track_file,
|
||||
context=context,
|
||||
original_gpx_filename=flight_data['gpx_track_filename']
|
||||
)
|
||||
|
||||
async with get_session() as db:
|
||||
@@ -136,11 +230,14 @@ class FlightMutationResolver(BaseMutationResolver):
|
||||
return await self._do_update(db, flight_data, data)
|
||||
|
||||
|
||||
async def handle_upload_gpx(original_gpx_filename: str, gpx_track: Upload):
|
||||
async def handle_upload_gpx(gpx_track: Upload, context, original_gpx_filename: Optional[str] = None):
|
||||
if original_gpx_filename:
|
||||
delete_file(FLIGHT_GPX_TRACK_PATH + "/" + original_gpx_filename, silent=True)
|
||||
|
||||
return await handle_file_upload(gpx_track, FLIGHT_GPX_TRACK_PATH)
|
||||
filename = await handle_file_upload(gpx_track, FLIGHT_GPX_TRACK_PATH)
|
||||
context.background_tasks.add_task(add_terrain_elevation_to_flight, flight_id=id, gpx_filename=filename)
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
async def handle_track_edit(db: AsyncSession, flight_id: int, track: List[TrackItemInput], user_id: int):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import shutil
|
||||
from time import time
|
||||
from typing import Optional
|
||||
from PIL import Image
|
||||
from pydantic import BaseModel
|
||||
@@ -91,29 +92,35 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
|
||||
async def upload(self, info, input: UploadPhotoInput) -> Photo:
|
||||
path = get_photo_basepath(input.flight_id)
|
||||
filename = await handle_file_upload(input.photo, path)
|
||||
exif_info = await parse_exif_info(path, filename)
|
||||
img_name = await handle_file_upload(input.photo, path, uid_prefix=False, overwrite=False)
|
||||
|
||||
img = Image.open(f"{path}/{filename}")
|
||||
photo = await PhotoMutationResolver().create(
|
||||
user_id=info.context.user_id,
|
||||
data={
|
||||
"flight_id": input.flight_id,
|
||||
"name": input.name,
|
||||
"filename": filename,
|
||||
"width": img.width,
|
||||
"height": img.height,
|
||||
"description": input.description,
|
||||
"exposed_at": exif_info.get("datetime_original"),
|
||||
"gps_latitude": exif_info.get("gps_latitude"),
|
||||
"gps_longitude": exif_info.get("gps_longitude"),
|
||||
"gps_altitude": exif_info.get("gps_altitude"),
|
||||
"is_flight_cover": False,
|
||||
},
|
||||
)
|
||||
exif_info = await parse_exif_info(path, img_name)
|
||||
|
||||
info.context.background_tasks.add_task(resize_photo, path=path, filename=filename, photo_id=photo.id)
|
||||
info.context.background_tasks.add_task(generate_thumbnail, path=path, filename=filename)
|
||||
img = Image.open(f"{path}/{img_name}")
|
||||
filename, filename_ext = os.path.splitext(img_name)
|
||||
|
||||
async with get_session() as db:
|
||||
photo = await PhotoMutationResolver()._do_create(
|
||||
db,
|
||||
data={
|
||||
"flight_id": input.flight_id,
|
||||
"name": input.name,
|
||||
"filename": filename,
|
||||
"filename_extension": filename_ext[1:], # nechci ukladat tecku na zacatku
|
||||
"cache_key": int(time()),
|
||||
"width": img.width,
|
||||
"height": img.height,
|
||||
"description": input.description,
|
||||
"exposed_at": exif_info.get("datetime_original"),
|
||||
"gps_latitude": exif_info.get("gps_latitude"),
|
||||
"gps_longitude": exif_info.get("gps_longitude"),
|
||||
"gps_altitude": exif_info.get("gps_altitude"),
|
||||
"created_by_id": info.context.user_id,
|
||||
},
|
||||
)
|
||||
|
||||
info.context.background_tasks.add_task(resize_photo, path=path, filename=img_name, photo_id=photo.id)
|
||||
info.context.background_tasks.add_task(generate_thumbnail, path=path, filename=img_name)
|
||||
|
||||
if exif_info.get("gps_latitude") and exif_info.get("gps_longitude"):
|
||||
info.context.background_tasks.add_task(add_terrain_elevation_to_photo, photo=photo)
|
||||
@@ -164,7 +171,8 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
async with get_session() as db:
|
||||
return await self._do_update(db, obj={"id": id}, data={
|
||||
"width": editor.img.width,
|
||||
"height": editor.img.height
|
||||
"height": editor.img.height,
|
||||
"cache_key": int(time())
|
||||
})
|
||||
|
||||
async def adjust(self, id: int, user_id: int, adjustment: AdjustmentInput, info):
|
||||
@@ -209,15 +217,25 @@ class PhotoMutationResolver(BaseMutationResolver):
|
||||
|
||||
return await self._do_update(db, obj={"id": id}, data={
|
||||
"width": editor.img.width,
|
||||
"height": editor.img.height
|
||||
"height": editor.img.height,
|
||||
"cache_key": int(time())
|
||||
})
|
||||
|
||||
async def delete(self, user_id: int, id: int) -> Photo:
|
||||
photo = await super().delete(user_id, id)
|
||||
|
||||
base_path = get_photo_basepath(photo.flight_id)
|
||||
delete_file(f"{base_path}/{photo.filename}", silent=True)
|
||||
delete_file(f"{base_path}/_original_{photo.filename}", silent=True)
|
||||
delete_file(f"{base_path}/thumbs/{photo.filename}", silent=True)
|
||||
|
||||
files_to_delete = [
|
||||
photo.filename,
|
||||
f"_original_{photo.filename}",
|
||||
f"{photo.filename}.{photo.filename_extension}",
|
||||
f"_original_{photo.filename}.{photo.filename_extension}",
|
||||
f"thumbs/{photo.filename}",
|
||||
f"thumbs/{photo.filename}.{photo.filename_extension}",
|
||||
f"thumbs/{photo.filename}.webp",
|
||||
]
|
||||
for filename in files_to_delete:
|
||||
delete_file(f"{base_path}/{filename}", silent=True)
|
||||
|
||||
return photo
|
||||
|
||||
@@ -102,7 +102,7 @@ class AdjustmentInput:
|
||||
"takeoff_weather_info_id", "landing_weather_info_id", "gpx_track_filename", "event_id"
|
||||
], all_optional=True)
|
||||
class EditFlightInput(BaseGraphqlInputType):
|
||||
gpx_track: Optional[Upload] = None # TODO: poresit validaci uploadovaneho souboru!
|
||||
gpx_track_file: Optional[Upload] = None # TODO: poresit validaci uploadovaneho souboru!
|
||||
track: Optional[List[TrackItemInput]] = None
|
||||
copilots: Optional[List[ComboboxInput]] = None
|
||||
aircraft: Optional[ComboboxInput] = None
|
||||
@@ -114,10 +114,11 @@ class EditFlightInput(BaseGraphqlInputType):
|
||||
@strawberry.input()
|
||||
class CreateFlightInput(BaseGraphqlInputType):
|
||||
aircraft: ComboboxInput
|
||||
landing_airport: ComboboxInput
|
||||
takeoff_airport: ComboboxInput
|
||||
takeoff_datetime: datetime
|
||||
landing_datetime: datetime
|
||||
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
|
||||
|
||||
|
||||
@strawberry.input()
|
||||
|
||||
@@ -128,8 +128,12 @@ class Flight:
|
||||
copilots: Optional[List[Copilot]] = strawberry.field(resolver=load_copilots)
|
||||
event: Optional[Event] = strawberry.field(resolver=load_event)
|
||||
aircraft: Aircraft = strawberry.field(resolver=lambda root: aircraft_dataloader.load(root.aircraft_id))
|
||||
takeoff_airport: Airport = strawberry.field(resolver=lambda root: airport_dataloader.load(root.takeoff_airport_id))
|
||||
landing_airport: Airport = strawberry.field(resolver=lambda root: airport_dataloader.load(root.landing_airport_id))
|
||||
takeoff_airport: Optional[Airport] = strawberry.field(
|
||||
resolver=lambda root: airport_dataloader.load(root.takeoff_airport_id)
|
||||
)
|
||||
landing_airport: Optional[Airport] = strawberry.field(
|
||||
resolver=lambda root: airport_dataloader.load(root.landing_airport_id)
|
||||
)
|
||||
title_photo: Optional[Photo] = strawberry.field(resolver=lambda root: photo_dataloader.load(root.title_photo_id))
|
||||
track: List[FlightTrack] = strawberry.field(resolver=lambda root: flight_track_dataloader.load(root.id))
|
||||
takeoff_weather_info: Optional[WeatherInfo] = strawberry.field(
|
||||
|
||||
Reference in New Issue
Block a user