Refaktoring a bugfixing

This commit is contained in:
Michal Kváček
2023-10-13 23:24:53 +02:00
parent ce2c023eaf
commit 7f7f79dad6
36 changed files with 698 additions and 722 deletions
+32 -62
View File
@@ -1,56 +1,46 @@
from typing import List, Optional, TYPE_CHECKING
from typing import List
import strawberry
from strawberry.file_uploads import Upload
from database import models
from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_input
from upload_utils import handle_file_upload, delete_file
from database.transaction import get_session
from graphql_schema.entities.helpers.combobox import handle_combobox_save
from .resolvers.aircraft import get_aircraft_resolver
from .resolvers.base import get_list, get_one
from graphql_schema.entities.types.mutation_input import ComboboxInput
from paths import AIRCRAFT_UPLOAD_DEST_PATH
from utils.file import delete_file
from utils.upload import handle_file_upload
from .resolvers.aircraft import AircraftMutationResolver, AircraftQueryResolver
from graphql_schema.entities.types.mutation_input import CreateAircraftInput, EditAircraftInput
from graphql_schema.entities.types.types import Aircraft
if TYPE_CHECKING:
pass
AIRCRAFT_UPLOAD_DEST_PATH = "/app/uploads/aircrafts/"
@strawberry.type
class AircraftQueries:
@strawberry.field()
@authenticated_user_only()
async def aircrafts(root, info) -> List[Aircraft]:
query = get_aircraft_resolver(info.context.user_id, info.context.organization_ids)
return await get_list(models.Aircraft, query)
return await AircraftQueryResolver().get_list(
info.context.user_id,
organization_ids=info.context.organization_ids
)
@strawberry.field()
@authenticated_user_only()
async def aircraft(root, info, id: int) -> Aircraft:
query = get_aircraft_resolver(info.context.user_id, info.context.organization_ids, id)
return await get_one(models.Aircraft, query)
return await AircraftQueryResolver().get_one(
id, user_id=info.context.user_id, organization_ids=info.context.organization_ids
)
@strawberry.type
class CreateAircraftMutation:
@strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['id', 'photo_filename'])
class CreateAircraftInput:
photo: Optional[Upload]
organization: Optional[ComboboxInput] = None
class AircraftMutation:
@strawberry.mutation
@authenticated_user_only()
async def create_aircraft(root, info, input: CreateAircraftInput) -> Aircraft:
# TODO: kontrola organizace
input_data = input.to_dict()
if input.photo:
input_data['photo_filename'] = await handle_file_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH)
async with get_session() as db:
if input.organization:
if input.organization:
async with get_session() as db:
input_data['organization_id'] = await handle_combobox_save(
db,
models.Organization,
@@ -58,29 +48,11 @@ class CreateAircraftMutation:
user_id=info.context.user_id,
)
aircraft = await models.Aircraft.create(
db,
data=dict(
**input_data,
created_by_id=info.context.user_id,
)
)
return Aircraft(**aircraft.as_dict())
@strawberry.type
class EditAircraftMutation:
@strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['photo_filename'])
class EditAircraftInput:
photo: Optional[Upload]
organization: Optional[ComboboxInput] = None
return await AircraftMutationResolver().create(data=input_data, user_id=info.context.user_id)
@strawberry.mutation
@authenticated_user_only()
async def edit_aircraft(root, info, id: int, input: EditAircraftInput) -> Aircraft:
# TODO: kontrola organizace
update_data = input.to_dict()
async with get_session() as db:
if input.organization:
@@ -91,26 +63,24 @@ class EditAircraftMutation:
user_id=info.context.user_id,
)
aircraft = (await db.scalars(get_aircraft_resolver(user_id=info.context.user_id, aircraft_id=id))).one()
query = AircraftQueryResolver().get_query(
user_id=info.context.user_id,
object_id=id,
organization_ids=info.context.organization_ids
)
aircraft = (await db.scalars(query)).one()
existing_photo_filename = aircraft.photo_filename
if input.photo:
if aircraft.photo_filename:
delete_file(AIRCRAFT_UPLOAD_DEST_PATH + "/" + aircraft.photo_filename, silent=True)
update_data['photo_filename'] = await handle_file_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH)
aircraft = await models.Aircraft.update(db, obj=aircraft, data=update_data)
if input.photo:
if existing_photo_filename:
delete_file(AIRCRAFT_UPLOAD_DEST_PATH + "/" + existing_photo_filename, silent=True)
update_data['photo_filename'] = await handle_file_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH)
async with get_session() as db:
aircraft = await models.Aircraft.update(db, id=id, data=update_data)
return Aircraft(**aircraft.as_dict())
@strawberry.type
class DeleteAircraftMutation:
@strawberry.mutation
@authenticated_user_only()
async def delete_aircraft(self, info, id: int) -> Aircraft:
async with get_session() as db:
aircraft = (await db.scalars(get_aircraft_resolver(info.context.user_id, aircraft_id=id))).one()
aircraft = await models.Aircraft.update(db, obj=aircraft, data=dict(deleted=True))
return Aircraft(**aircraft.as_dict())
return await AircraftMutationResolver().delete(info.context.user_id, id)
+4 -11
View File
@@ -2,13 +2,8 @@ from typing import List
import strawberry
from database import models
from decorators.endpoints import authenticated_user_only
from graphql_schema.entities.resolvers.base import get_list, get_one, get_base_resolver
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
@strawberry_sqlalchemy_type(models.Airport)
class Airport:
pass
from graphql_schema.entities.resolvers.base import BaseQueryResolver
from graphql_schema.entities.types.types import Airport
@strawberry.type
@@ -16,11 +11,9 @@ class AirportQueries:
@strawberry.field()
@authenticated_user_only()
async def airports(root, info) -> List[Airport]:
query = get_base_resolver(models.Airport, user_id=info.context.user_id)
return await get_list(models.Airport, query)
return await BaseQueryResolver(Airport, models.Airport).get_list(info.context.user_id)
@strawberry.field()
@authenticated_user_only()
async def airport(root, info, id: int) -> Airport:
query = get_base_resolver(models.Airport, user_id=info.context.user_id, object_id=id)
return await get_one(models.Airport, query)
return await BaseQueryResolver(Airport, models.Airport).get_one(id, user_id=info.context.user_id)
+11 -31
View File
@@ -1,65 +1,45 @@
from typing import List, TYPE_CHECKING
from typing import List
import strawberry
from database import models
from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_input
from .resolvers.base import get_base_resolver, get_list, get_one
from database.transaction import get_session
from graphql_schema.entities.resolvers.base import BaseQueryResolver, BaseMutationResolver
from graphql_schema.entities.types.mutation_input import CreateCopilotInput, EditCopilotInput
from graphql_schema.entities.types.types import Copilot
if TYPE_CHECKING:
pass
@strawberry.type
class CopilotQueries:
@strawberry.field()
@authenticated_user_only()
async def copilots(root, info) -> List[Copilot]:
query = get_base_resolver(models.Copilot, user_id=info.context.user_id, order_by=[models.Copilot.name])
return await get_list(models.Copilot, query)
return await BaseQueryResolver(Copilot, models.Copilot).get_list(info.context.user_id)
@strawberry.field()
@authenticated_user_only()
async def copilot(root, info, id: int) -> Copilot:
query = get_base_resolver(models.Copilot, object_id=id, user_id=info.context.user_id)
return await get_one(models.Copilot, query)
return await BaseQueryResolver(Copilot, models.Copilot).get_one(id, user_id=info.context.user_id)
@strawberry.type
class CreateCopilotMutation:
@strawberry_sqlalchemy_input(model=models.Copilot, exclude_fields=["id"])
class CreateCopilotInput:
pass
@strawberry.mutation
@authenticated_user_only()
async def create_copilot(root, info, input: CreateCopilotInput) -> Copilot:
input_data = input.to_dict()
async with get_session() as db:
copilot = await models.Copilot.create(
db,
data=dict(
**input_data,
created_by_id=info.context.user_id,
)
)
return Copilot(**copilot.as_dict())
return await BaseMutationResolver(Copilot, models.Copilot).create(
data=input.to_dict(),
user_id=info.context.user_id
)
@strawberry.type
class EditCopilotMutation:
@strawberry_sqlalchemy_input(model=models.Copilot, exclude_fields=["id"])
class EditCopilotInput:
pass
@strawberry.mutation
@authenticated_user_only()
async def edit_copilot(root, info, id: int, input: EditCopilotInput) -> Copilot:
async with get_session() as db:
copilot = (await db.scalars(
query=get_base_resolver(models.Copilot, object_id=id, user_id=info.context.user_id)
BaseQueryResolver(Copilot, models.Copilot).get_query(object_id=id, user_id=info.context.user_id)
)).one()
updated_copilot = await models.Copilot.update(db, obj=copilot, data=input.to_dict())
+10 -36
View File
@@ -1,68 +1,42 @@
from typing import List, TYPE_CHECKING
from typing import List
import strawberry
from database import models
from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_input
from .resolvers.base import get_base_resolver, get_list, get_one
from database.transaction import get_session
from graphql_schema.entities.resolvers.base import BaseQueryResolver, BaseMutationResolver
from graphql_schema.entities.types.mutation_input import CreateEventInput, EditEventInput
from graphql_schema.entities.types.types import Event
if TYPE_CHECKING:
pass
@strawberry.type
class EventQueries:
@strawberry.field()
@authenticated_user_only()
async def events(root, info) -> List[Event]:
query = get_base_resolver(
models.Event, user_id=info.context.user_id,
return await BaseQueryResolver(Event, models.Event).get_list(
info.context.user_id,
order_by=[models.Event.date_from.desc(), models.Event.id.desc()]
)
return await get_list(models.Event, query)
@strawberry.field()
@authenticated_user_only()
async def event(root, info, id: int) -> Event:
query = get_base_resolver(models.Event, user_id=info.context.user_id, object_id=id)
return await get_one(models.Event, query)
return await BaseQueryResolver(Event, models.Event).get_one(id, user_id=info.context.user_id)
@strawberry.type
class CreateEventMutation:
@strawberry_sqlalchemy_input(model=models.Event, exclude_fields=["id"])
class CreateEventInput:
pass
class EventMutation:
@strawberry.mutation
@authenticated_user_only()
async def create_event(root, info, input: CreateEventInput) -> Event:
input_data = input.to_dict()
async with get_session() as db:
event = await models.Event.create(
db,
data=dict(
**input_data,
created_by_id=info.context.user_id,
)
)
return Event(**event.as_dict())
@strawberry.type
class EditEventMutation:
@strawberry_sqlalchemy_input(model=models.Event, exclude_fields=["id"])
class EditEventInput:
pass
return await BaseMutationResolver(Event, models.Event).create(input.to_dict(), info.context.user_id)
@strawberry.mutation
@authenticated_user_only()
async def edit_event(root, info, id: int, input: EditEventInput) -> Event:
async with get_session() as db:
event = (await db.scalars(
get_base_resolver(models.Event, user_id=info.context.user_id, object_id=id)
BaseQueryResolver(Event, models.Event).get_query(user_id=info.context.user_id, object_id=id)
)).one()
updated_event = await models.Event.update(db, obj=event, data=input.to_dict())
+40 -60
View File
@@ -2,43 +2,18 @@ import asyncio
from typing import List, Optional
import strawberry
from fastapi import HTTPException
from sqlalchemy import select
from starlette.status import HTTP_401_UNAUTHORIZED
from background_jobs.weather import download_weather
from database import models
from decorators.endpoints import authenticated_user_only
from decorators.error_logging import error_logging
from dependencies.db import get_session
from graphql_schema.entities.resolvers.flight import (
handle_aircraft_save, handle_weather_info, FlightMutationResolver, get_airport,
)
from database.transaction import get_session
from graphql_schema.entities.resolvers.flight import handle_aircraft_save, FlightMutationResolver, FlightQueryResolver
from graphql_schema.entities.types.mutation_input import EditFlightInput, CreateFlightInput
from .resolvers.base import get_list, get_one
from .helpers.combobox import handle_combobox_save
from graphql_schema.entities.types.types import Flight
def get_base_query(user_id: Optional[int], username: Optional[str] = None, is_auth: bool = False):
query = (
select(models.Flight)
.filter(models.Flight.deleted.is_(False))
.order_by(models.Flight.takeoff_datetime.desc())
)
if user_id:
query = query.filter(models.Flight.created_by_id == user_id)
if username:
query = (
query
.join(models.Flight.created_by)
.filter(models.User.public_username == username)
)
if not is_auth:
query = query.filter(models.Flight.is_public.is_(True))
return query
@strawberry.type
class FlightQueries:
@@ -47,11 +22,7 @@ class FlightQueries:
if not info.context.user_id and not username:
raise HTTPException(HTTP_401_UNAUTHORIZED)
query = (
get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id))
.order_by(models.Flight.id.desc())
)
return await get_list(models.Flight, query)
return await FlightQueryResolver().get_list(user_id=info.context.user_id, username=username)
@strawberry.field()
@error_logging
@@ -59,11 +30,7 @@ class FlightQueries:
if not info.context.user_id and not username:
raise HTTPException(HTTP_401_UNAUTHORIZED)
query = (
get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id))
.filter(models.Flight.id == id)
)
return await get_one(models.Flight, query)
return await FlightQueryResolver().get_one(id, user_id=info.context.user_id, username=username)
@strawberry.type
@@ -72,36 +39,49 @@ class FlightMutation:
@authenticated_user_only()
async def create_flight(self, info, input: CreateFlightInput) -> Flight:
data = input.to_dict()
async with (get_session() as db):
takeoff_airport = await get_airport(db, input.takeoff_airport, info.context.user_id)
landing_airport = await get_airport(db, input.landing_airport, info.context.user_id)
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)
weather_takeoff, weather_landing = await asyncio.gather(
handle_weather_info(db, data['takeoff_datetime'], takeoff_airport),
handle_weather_info(db, data['landing_datetime'], landing_airport)
)
await db.flush()
flight = await models.Flight.create(db, data={
**data,
"takeoff_weather_info_id": weather_takeoff.id if weather_takeoff else None,
"landing_weather_info_id": weather_landing.id if weather_landing else None,
"takeoff_airport_id": takeoff_airport.id,
"landing_airport_id": landing_airport.id,
"has_terrain_elevation": False,
# 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,
"created_by_id": info.context.user_id
"has_terrain_elevation": False,
"name": "",
"description": ""
})
return Flight(**flight.as_dict())
flight = 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
@strawberry.mutation
@authenticated_user_only()
async def edit_flight(self, info, id: int, input: EditFlightInput) -> Flight:
return await FlightMutationResolver.update(info.context, id, input)
return await FlightMutationResolver().update(info.context, id, input)
@strawberry.mutation
@authenticated_user_only()
async def delete_flight(self, info, id: int) -> Flight:
return await FlightMutationResolver.delete(info.context.user_id, id)
return await FlightMutationResolver().delete(info.context.user_id, id)
+31 -46
View File
@@ -1,54 +1,54 @@
from typing import List, TYPE_CHECKING
from typing import List
import strawberry
from sqlalchemy import delete
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.exc import IntegrityError
from database import models
from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_input
from .resolvers.base import get_base_resolver, get_list, get_one
from database.transaction import get_session
from graphql_schema.entities.resolvers.base import BaseQueryResolver, BaseMutationResolver
from graphql_schema.entities.types.mutation_input import CreateOrganizationInput, EditOrganizationInput
from graphql_schema.entities.types.types import Organization
if TYPE_CHECKING:
pass
@strawberry.type
class OrganizationQueries:
@strawberry.field()
@authenticated_user_only()
async def organizations(root, info) -> List[Organization]:
query = get_base_resolver(models.Organization, order_by=[models.Organization.name])
return await get_list(models.Organization, query)
return await BaseQueryResolver(Organization, models.Organization).get_list(
info.context.user_id, order_by=[models.Organization.name]
)
@strawberry.field()
@authenticated_user_only()
async def organization(root, info, id: int) -> Organization:
query = get_base_resolver(models.Organization, object_id=id)
return await get_one(models.Organization, query)
return await BaseQueryResolver(Organization, models.Organization).get_one(id, info.context.user_id)
@strawberry.type
class CreateOrganizationMutation:
@strawberry_sqlalchemy_input(model=models.Organization, exclude_fields=["id"])
class CreateOrganizationInput:
pass
class OrganizationMutation:
@strawberry.mutation
@authenticated_user_only()
async def create_organization(root, info, input: CreateOrganizationInput) -> Organization:
input_data = input.to_dict()
async with get_session() as db:
organization = await models.Organization.create(
db,
data=dict(
**input_data,
created_by_id=info.context.user_id,
)
)
return await BaseMutationResolver(Organization, models.Organization).create(
data=input.to_dict(),
user_id=info.context.user_id
)
return Organization(**organization.as_dict())
@strawberry.mutation
@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())
@strawberry.type
@@ -58,7 +58,9 @@ class OrganizationUserMutation:
@authenticated_user_only()
async def add_to_organization(root, info, organization_id: int) -> Organization:
async with get_session() as db:
organization = (await db.scalars(get_base_resolver(models.Organization, object_id=organization_id))).one()
organization = (await db.scalars(
BaseQueryResolver(Organization, models.Organization).get_query(object_id=organization_id)
)).one()
try:
await db.execute(
@@ -68,7 +70,6 @@ class OrganizationUserMutation:
)
)
except IntegrityError:
print("jiz existuje")
pass
return Organization(**organization.as_dict())
@@ -77,7 +78,9 @@ class OrganizationUserMutation:
@authenticated_user_only()
async def remove_from_organization(root, info, organization_id: int) -> Organization:
async with get_session() as db:
organization = (await db.scalars(get_base_resolver(models.Organization, object_id=organization_id))).one()
organization = (await db.scalars(
BaseQueryResolver(Organization, models.Organization).get_query(object_id=organization_id)
)).one()
await db.execute(
delete(models.user_is_in_organization).filter_by(
@@ -87,21 +90,3 @@ class OrganizationUserMutation:
)
return Organization(**organization.as_dict())
@strawberry.type
class EditOrganizationMutation:
@strawberry_sqlalchemy_input(model=models.Organization, exclude_fields=["id"])
class EditOrganizationInput:
pass
@strawberry.mutation
@authenticated_user_only()
async def edit_organization(root, info, id: int, input: EditOrganizationInput) -> Organization:
async with get_session() as db:
organization = (await db.scalars(
get_base_resolver(models.Organization, 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())
+38 -41
View File
@@ -4,17 +4,17 @@ import strawberry
from sqlalchemy import update
from strawberry.file_uploads import Upload
from background_jobs.elevation import add_terrain_elevation_to_photo
from background_jobs.photo import generate_thumbnail, resize_photo
from database import models
from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from database.transaction import get_session
from graphql_schema.entities.resolvers.base import BaseQueryResolver, BaseMutationResolver
from graphql_schema.entities.types.types import Photo
from paths import get_photo_basepath
from upload_utils import (
handle_file_upload, delete_file, parse_exif_info, generate_thumbnail, resize_image,
rotate_image
)
from graphql_schema.entities.helpers.combobox import handle_combobox_save
from .resolvers.base import get_base_resolver, get_list
from utils.file import delete_file
from utils.image import parse_exif_info, rotate_image
from utils.upload import handle_file_upload
from graphql_schema.entities.types.mutation_input import ComboboxInput
@@ -22,8 +22,7 @@ from graphql_schema.entities.types.mutation_input import ComboboxInput
class PhotoQueries:
@strawberry.field()
async def photos(root, info) -> List[Photo]:
query = get_base_resolver(models.Photo, user_id=info.context.user_id)
return await get_list(models.Photo, query)
return await BaseQueryResolver(Photo, models.Photo).get_list(user_id=info.context.user_id)
@strawberry.type
@@ -58,8 +57,7 @@ class UploadPhotoMutation:
}, db_session=db)
photo = Photo(**photo_model.as_dict())
# TODO: udelat primo konkretni bg joby na resize a thumbnaily
info.context.background_tasks.add_task(resize_image, path=path, filename=filename, new_width=2500, quality=85)
info.context.background_tasks.add_task(resize_photo, path=path, filename=filename)
info.context.background_tasks.add_task(generate_thumbnail, path=path, filename=filename)
if exif_info.get("gps_latitude") and exif_info.get("gps_longitude"):
@@ -77,19 +75,20 @@ class EditPhotoMutation:
point_of_interest: Optional[ComboboxInput] = None
is_flight_cover: Optional[bool] = None
def to_dict(self):
return {
key: getattr(self, key) for key in ('name', 'description', 'is_flight_cover')
if getattr(self, key) is not None
}
@strawberry.mutation()
@authenticated_user_only()
async def edit_photo(self, info, id: int, input: EditPhotoInput) -> Photo:
# TODO: base trida pro inputy s definici to_dict/as_dict?
data = {
key: getattr(input, key) for key in ('name', 'description', 'is_flight_cover')
if getattr(input, key) is not None
}
query = get_base_resolver(models.Photo, user_id=info.context.user_id, object_id=id)
data = input.to_dict()
async with get_session() as db:
photo = (await db.scalars(query)).one()
photo = (await db.scalars(
BaseQueryResolver(Photo, models.Photo).get_query(user_id=info.context.user_id, object_id=id)
)).one()
if input.point_of_interest:
data['point_of_interest_id'] = await handle_combobox_save(
@@ -116,25 +115,27 @@ class EditPhotoMutation:
@strawberry.mutation()
@authenticated_user_only()
async def rotate_photo(self, info, id: int, angle: int) -> Photo:
async with get_session() as db:
query = get_base_resolver(models.Photo, user_id=info.context.user_id, object_id=id)
photo = (await db.scalars(query)).one()
photo = (await db.scalars(
BaseQueryResolver(Photo, models.Photo).get_query(user_id=info.context.user_id, object_id=id)
)).one()
photo_filename = photo.filename
photo_as_dict = photo.as_dict()
await asyncio.gather(
rotate_image(
path=get_photo_basepath(photo.flight_id),
filename=photo.filename,
angle=angle,
),
rotate_image(
path=get_photo_basepath(photo.flight_id) + "/thumbs",
filename=photo.filename,
angle=angle,
),
)
await asyncio.gather(
rotate_image(
path=get_photo_basepath(photo.flight_id),
filename=photo_filename,
angle=angle,
),
rotate_image(
path=get_photo_basepath(photo.flight_id) + "/thumbs",
filename=photo_filename,
angle=angle,
),
)
return Photo(**photo.as_dict())
return Photo(**photo_as_dict)
@strawberry.type
@@ -142,14 +143,10 @@ class DeletePhotoMutation:
@strawberry.mutation()
@authenticated_user_only()
async def delete_photo(self, info, id: int) -> Photo:
query = get_base_resolver(models.Photo, user_id=info.context.user_id, object_id=id)
async with get_session() as db:
photo_model = (await db.scalars(query)).one()
await db.delete(photo_model)
photo = Photo(**photo_model.as_dict())
photo = await BaseMutationResolver(Photo, models.Photo).delete(user_id=info.context.user_id, id=id)
base_path = get_photo_basepath(photo.flight_id)
delete_file(f"{base_path}/{photo.filename}", silent=True)
delete_file(f"{base_path}/thumbs/{photo.filename}", silent=True)
return photo_model
return photo
+13 -38
View File
@@ -1,13 +1,12 @@
from typing import List, Optional
from typing import List
import strawberry
from database import models
from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from database.transaction import get_session
from graphql_schema.entities.helpers.combobox import handle_combobox_save
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_input
from graphql_schema.entities.resolvers.base import BaseQueryResolver, BaseMutationResolver
from graphql_schema.entities.types.types import PointOfInterest
from .resolvers.base import get_base_resolver, get_list, get_one
from graphql_schema.entities.types.mutation_input import ComboboxInput
from graphql_schema.entities.types.mutation_input import CreatePointOfInterestInput, EditPointOfInterestInput
@strawberry.type
@@ -15,22 +14,16 @@ class PointOfInterestQueries:
@strawberry.field()
@authenticated_user_only()
async def points_of_interest(root, info) -> List[PointOfInterest]:
query = get_base_resolver(models.PointOfInterest, user_id=info.context.user_id)
return await get_list(models.PointOfInterest, query)
return await BaseQueryResolver(PointOfInterest, models.PointOfInterest).get_list(info.context.user_id)
@strawberry.field()
@authenticated_user_only()
async def point_of_interest(root, info, id: int) -> PointOfInterest:
query = get_base_resolver(models.PointOfInterest, user_id=info.context.user_id, object_id=id)
return await get_one(models.PointOfInterest, query)
return await BaseQueryResolver(PointOfInterest, models.PointOfInterest).get_one(id, info.context.user_id)
@strawberry.type
class CreatePointOfInterestMutation:
@strawberry_sqlalchemy_input(models.PointOfInterest, exclude_fields=['id', 'type_id'])
class CreatePointOfInterestInput:
type: Optional[ComboboxInput] = None
class PointOfInterestMutation:
@strawberry.mutation
@authenticated_user_only()
async def create_point_of_interest(root, info, input: CreatePointOfInterestInput) -> PointOfInterest:
@@ -42,23 +35,17 @@ class CreatePointOfInterestMutation:
db, models.PointOfInterestType, input.type, info.context.user_id
)
poi = await models.PointOfInterest.create(db, data=dict(**input_data, created_by_id=info.context.user_id))
return PointOfInterest(**poi.as_dict())
@strawberry.type
class EditPointOfInterestMutation:
@strawberry_sqlalchemy_input(models.PointOfInterest, exclude_fields=['id', 'type_id'])
class EditPointOfInterestInput:
type: Optional[ComboboxInput] = None
return await BaseMutationResolver(PointOfInterest, models.PointOfInterest).create(
input_data, info.context.user_id
)
@strawberry.mutation
@authenticated_user_only()
async def edit_point_of_interest(root, info, id: int, input: EditPointOfInterestInput) -> PointOfInterest:
input_data = input.to_dict()
query = get_base_resolver(
models.PointOfInterest, user_id=info.context.user_id, object_id=id, include_public=False
query = BaseQueryResolver(PointOfInterest, models.PointOfInterest).get_query(
user_id=info.context.user_id, object_id=id, include_public=False
)
async with get_session() as db:
@@ -71,19 +58,7 @@ class EditPointOfInterestMutation:
updated_poi = await models.PointOfInterest.update(db, obj=poi, data=input_data)
return PointOfInterest(**updated_poi.as_dict())
@strawberry.type
class DeletePointOfInterestMutation:
@strawberry.mutation
@authenticated_user_only()
async def delete_point_of_interest(self, info, id: int) -> PointOfInterest:
query = get_base_resolver(
models.PointOfInterest, user_id=info.context.user_id, object_id=id, include_public=False
)
async with get_session() as db:
poi = (await db.scalars(query)).one()
updated_poi = await models.PointOfInterest.update(db, obj=poi, data=dict(deleted=True))
return PointOfInterest(**updated_poi.as_dict())
return await BaseMutationResolver(PointOfInterest, models.PointOfInterest).delete(info.context.user_id, id=id)
+5 -5
View File
@@ -2,7 +2,7 @@ from typing import List
import strawberry
from database import models
from decorators.endpoints import authenticated_user_only
from graphql_schema.entities.resolvers.base import get_base_resolver, get_list, get_one
from graphql_schema.entities.resolvers.base import BaseQueryResolver
from graphql_schema.entities.types.types import PointOfInterestType
@@ -12,14 +12,14 @@ class PointOfInterestTypeQueries:
@strawberry.field()
@authenticated_user_only()
async def point_of_interest_types(root, info) -> List[PointOfInterestType]:
query = get_base_resolver(models.PointOfInterestType, user_id=info.context.user_id)
return await get_list(models.PointOfInterestType, query)
return await BaseQueryResolver(PointOfInterestType, models.PointOfInterestType).get_list(info.context.user_id)
@strawberry.field()
@authenticated_user_only()
async def point_of_interest_type(root, info, id: int) -> PointOfInterestType:
query = get_base_resolver(models.PointOfInterestType, user_id=info.context.user_id, object_id=id)
return await get_one(models.PointOfInterestType, query)
return await BaseQueryResolver(PointOfInterestType, models.PointOfInterestType).get_one(
id, info.context.user_id
)
#
# @strawberry.type
@@ -1,24 +1,41 @@
from operator import or_
from typing import Set, Optional
from database import models
from graphql_schema.entities.resolvers.base import get_base_resolver
from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQueryResolver
from graphql_schema.entities.types.types import Aircraft
def get_aircraft_resolver(user_id: int, organization_ids: Optional[Set[int]] = None, aircraft_id: Optional[int] = None):
query = get_base_resolver(
model=models.Aircraft,
order_by=[models.Aircraft.id.desc()],
object_id=aircraft_id,
user_id=user_id if not organization_ids else None
)
if organization_ids:
query = (
query.filter(
or_(
models.Aircraft.created_by_id == user_id,
models.Aircraft.organization_id.in_(organization_ids)
class AircraftQueryResolver(BaseQueryResolver):
def __init__(self):
super().__init__(graphql_type=Aircraft, model=models.Aircraft)
def get_query(
self,
user_id: Optional[int] = None,
object_id: Optional[int] = None,
order_by: Optional[list] = None,
organization_ids: Optional[Set[int]] = None,
*args,
**kwargs,
):
query = super().get_query(
order_by=[models.Aircraft.id.desc()],
object_id=object_id,
user_id=user_id if not organization_ids else None
)
if organization_ids:
query = (
query.filter(
or_(
models.Aircraft.created_by_id == user_id,
models.Aircraft.organization_id.in_(organization_ids)
)
)
)
)
return query
return query
class AircraftMutationResolver(BaseMutationResolver):
def __init__(self):
super().__init__(graphql_type=Aircraft, model=models.Aircraft)
+101 -46
View File
@@ -1,53 +1,108 @@
from typing import Optional, Type
from typing import Optional, Type, T
from sqlalchemy import select, or_
from sqlalchemy.ext.asyncio import AsyncSession
from database import models
from dependencies.db import get_session
from database.transaction import get_session
def get_base_resolver(
model: Type[models.BaseModel],
object_id: Optional[int] = None,
user_id: Optional[int] = None,
order_by: Optional[list] = None,
include_public: Optional[bool] = True,
):
query = select(model)
class BaseQueryResolver:
if object_id:
if hasattr(model, "id"):
query = query.filter(model.id == object_id)
def __init__(self, graphql_type, model):
self.graphql_type = graphql_type
self.model = model
def get_query(
self,
user_id: Optional[int] = None,
object_id: Optional[int] = None,
order_by: Optional[list] = None,
include_public: Optional[bool] = True,
*args,
**kwargs,
):
query = select(self.model)
if object_id:
if hasattr(self.model, "id"):
query = query.filter(self.model.id == object_id)
else:
raise AssertionError(f"Model {self.model} has no ID column! Cannot query by ID!")
if hasattr(self.model, "deleted"):
query = query.filter(self.model.deleted.is_(False))
ownership_clause = []
if hasattr(self.model, "is_public") and include_public:
ownership_clause.append(self.model.is_public.is_(True))
if hasattr(self.model, "created_by_id") and user_id:
ownership_clause.append(self.model.created_by_id == user_id)
if len(ownership_clause) > 1:
query = query.filter(or_(*ownership_clause))
elif len(ownership_clause) == 1:
query = query.filter(*ownership_clause)
if order_by:
query = query.order_by(*order_by)
return query
async def _get_list(self, query):
async with get_session() as db:
items = (await db.scalars(query)).all()
return [self.model(**m.as_dict()) for m in items]
async def _get_one(self, query):
async with get_session() as db:
data = (await db.scalars(query)).one()
return self.model(**data.as_dict())
async def get_list(self, user_id: Optional[int] = None, **kwargs) -> list:
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):
query = self.get_query(user_id, object_id=id, **kwargs)
return await self._get_one(query)
class BaseMutationResolver:
model: Type[models.BaseModel]
graphql_type: Type[T] = None
def __init__(self, graphql_type: Type[T], model: Type[models.BaseModel]):
self.graphql_type = graphql_type
self.model = model
async def delete(self, user_id: int, id: int) -> T:
async with get_session() as db:
query = BaseQueryResolver(self.graphql_type, self.model).get_query(user_id, object_id=id)
model = (await db.scalars(query)).one()
if hasattr(self.model, "deleted"):
model = await self.model.update(db, obj=model, data=dict(deleted=True))
else:
await db.delete(model)
return self.graphql_type(**model.as_dict())
async def create(self, data: dict, user_id: Optional[int] = None) -> T:
input_data = {**data}
if hasattr(self.model, "created_by_id"):
input_data['created_by_id'] = user_id
async with get_session() as db:
model = await self.model.create(db, data=input_data)
return self.graphql_type(**model.as_dict())
async def _do_update(self, db: AsyncSession, obj: models.BaseModel | dict, data: dict) -> T:
update_where = {}
if isinstance(obj, models.BaseModel):
update_where['obj'] = obj
else:
raise AssertionError(f"Model {model} has no ID column! Cannot query by ID!")
update_where['id'] = obj['id']
if hasattr(model, "deleted"):
query = query.filter(model.deleted.is_(False))
ownership_clause = []
if hasattr(model, "is_public") and include_public:
ownership_clause.append(model.is_public.is_(True))
if hasattr(model, "created_by_id") and user_id:
ownership_clause.append(model.created_by_id == user_id)
if len(ownership_clause) > 1:
query = query.filter(or_(*ownership_clause))
elif len(ownership_clause) == 1:
query = query.filter(*ownership_clause)
if order_by:
query = query.order_by(*order_by)
return query
async def get_list(model, query):
async with get_session() as db:
items = (await db.scalars(query)).all()
return [model(**m.as_dict()) for m in items]
async def get_one(model, query):
async with get_session() as db:
data = (await db.scalars(query)).one()
return model(**data.as_dict())
model = await self.model.update(db, data=data, **update_where)
return self.graphql_type(**model.as_dict())
+60 -123
View File
@@ -1,77 +1,51 @@
import asyncio
from datetime import datetime
from typing import List, Optional, Type
from aiocache import cached
from typing import List, Optional
from sqlalchemy import delete, insert
from sqlalchemy.ext.asyncio import AsyncSession
from strawberry.file_uploads import Upload
from background_jobs.elevation import add_terrain_elevation_to_flight
from background_jobs.weather import download_weather
from database import models
from database.models import flight_has_copilot
from dependencies.db import get_session
from external.weather import weather_api
from database.transaction import get_session
from graphql_schema.entities.helpers.combobox import handle_combobox_save
from graphql_schema.entities.resolvers.base import get_base_resolver
from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQueryResolver
from graphql_schema.entities.types.mutation_input import EditFlightInput, TrackItemInput, ComboboxInput
from graphql_schema.entities.types.types import Flight
from upload_utils import delete_file, handle_file_upload
from paths import FLIGHT_GPX_TRACK_PATH
from utils.file import delete_file
from utils.upload import handle_file_upload
class BaseMutationResolver:
model: Type[models.BaseModel]
graphql_type = Flight
class FlightQueryResolver(BaseQueryResolver):
def __init__(self):
super().__init__(graphql_type=Flight, model=models.Flight)
@classmethod
async def delete(cls, user_id: int, id: int):
async with get_session() as db:
model = (
(await db.scalars(
get_base_resolver(cls.model, user_id=user_id, object_id=id)
.filter(cls.model.id == id))
)
.one()
def get_query(self, user_id: int, object_id: Optional[int] = None, *args, **kwargs):
query = super().get_query(
user_id, object_id,
order_by=[models.Flight.takeoff_datetime.desc()],
include_public=bool(user_id)
)
if kwargs.get('username'):
query = (
query
.join(models.Flight.created_by)
.filter(models.User.public_username == kwargs['username'])
)
if hasattr(cls.model, "deleted"):
model = await cls.model.update(db, obj=model, data=dict(deleted=True))
else:
db.delete(model)
return cls.graphql_type(**model.as_dict())
@classmethod
async def _do_update(cls, db: AsyncSession, obj: models.BaseModel | dict, data: dict):
update_where = {}
if isinstance(obj, models.BaseModel):
update_where['obj'] = obj
else:
update_where['id'] = obj['id']
model = await cls.model.update(db, data=data, **update_where)
return cls.graphql_type(**model.as_dict())
async def handle_upload_gpx(original_gpx_filename: str, gpx_track: Upload):
path = "/app/uploads/tracks"
if original_gpx_filename:
delete_file(path + "/" + original_gpx_filename, silent=True)
return await handle_file_upload(gpx_track, path)
return query
class FlightMutationResolver(BaseMutationResolver):
model = models.Flight
graphql_type = Flight
def __init__(self):
super().__init__(Flight, models.Flight)
async def create(self):
pass
@classmethod
async def update(cls, context, id: int, input: EditFlightInput):
async def update(self, context, id: int, input: EditFlightInput):
user_id = context.user_id
async with get_session() as db:
flight = (await db.scalars(get_base_resolver(models.Flight, user_id=user_id, object_id=id))).one()
flight = await FlightQueryResolver().get_one(id, user_id)
flight_data = flight.as_dict()
flight_id = flight.id
@@ -84,31 +58,32 @@ class FlightMutationResolver(BaseMutationResolver):
)
async with get_session() as db:
if input.landing_airport:
landing_airport = await get_airport(db, input.landing_airport, user_id)
landing_datetime = input.landing_datetime or flight_data['landing_datetime']
data['landing_airport_id'] = landing_airport.id
data['landing_datetime'] = landing_datetime
weather_info = await handle_weather_info(
db, landing_datetime, landing_airport, flight_data['landing_weather_info_id']
)
if weather_info:
data['landing_weather_info_id'] = weather_info.id
if input.takeoff_airport:
takeoff_airport = await get_airport(db, input.takeoff_airport, user_id)
takeoff_datetime = input.takeoff_datetime or flight_data['takeoff_datetime']
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}
)
data['takeoff_airport_id'] = takeoff_airport_id
data['takeoff_datetime'] = input.takeoff_datetime or flight_data['takeoff_datetime']
data['takeoff_airport_id'] = takeoff_airport.id
data['takeoff_datetime'] = takeoff_datetime
context.background_tasks.add_task(
download_weather, flight_id=id, airport_id=takeoff_airport_id, date_time=data['takeoff_datetime'],
type_="takeoff"
)
weather_info = await handle_weather_info(
db, takeoff_datetime, takeoff_airport, flight_data['takeoff_weather_info_id']
if input.landing_airport:
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}
)
data['landing_airport_id'] = landing_airport_id
data['landing_datetime'] = input.landing_datetime or flight_data['landing_datetime']
context.background_tasks.add_task(
download_weather, flight_id=id, airport_id=landing_airport_id, date_time=data['landing_datetime'],
type_="landing"
)
if weather_info:
data['takeoff_weather_info_id'] = weather_info.id
if input.aircraft is not None:
data['aircraft_id'] = await handle_aircraft_save(db, user_id, input.aircraft)
@@ -127,46 +102,21 @@ class FlightMutationResolver(BaseMutationResolver):
if input.copilots is not None:
await db.execute(delete(flight_has_copilot).filter_by(flight_id=flight_id))
copilots = await handle_copilots_edit(db, input.copilots or [], user_id)
copilots = await asyncio.gather(*[
handle_combobox_save(db, models.Copilot, copilot, user_id) for copilot in input.copilots
])
for copilot_id in copilots:
await db.execute(insert(flight_has_copilot).values(flight_id=flight_id, copilot_id=copilot_id))
return await cls._do_update(db, flight_data, data)
return await self._do_update(db, flight_data, data)
async def handle_weather_info(
db: AsyncSession, date_time: datetime, airport: models.Airport, existing_weather_id: Optional[int] = None
) -> Optional[models.WeatherInfo]:
if not airport.gps_latitude or not airport.gps_longitude:
return None
async def handle_upload_gpx(original_gpx_filename: str, gpx_track: Upload):
if original_gpx_filename:
delete_file(FLIGHT_GPX_TRACK_PATH + "/" + original_gpx_filename, silent=True)
try:
weather = await weather_api.get_weather_for_hour(
date_time.astimezone(),
gps=(airport.gps_latitude, airport.gps_longitude)
)
except Exception as e:
print(e)
return None
data = {
"datetime": weather['datetime'],
"qnh": weather['pressure_msl'],
"temperature_surface": weather['temperature_2m'],
"dewpoint_surface": weather['dewpoint_2m'],
"rain": weather['rain'],
"cloudcover_total": weather['cloudcover'],
"cloudcover_low": weather['cloudcover_low'],
"wind_speed_surface": weather['windspeed_10m'],
"wind_direction_surface": weather['winddirection_10m'],
}
if existing_weather_id:
model = await models.WeatherInfo.update(db, id=existing_weather_id, data=data)
else:
model = await models.WeatherInfo.create(db, data=data)
return model
return await handle_file_upload(gpx_track, FLIGHT_GPX_TRACK_PATH)
async def handle_track_edit(db: AsyncSession, flight_id: int, track: List[TrackItemInput], user_id: int):
@@ -176,6 +126,7 @@ async def handle_track_edit(db: AsyncSession, flight_id: int, track: List[TrackI
for item in track:
poi_id = None
airport_id = None
if item.point_of_interest:
poi_id = await handle_combobox_save(
db, models.PointOfInterest, item.point_of_interest, user_id, extra_data={"description": ""}
@@ -195,7 +146,7 @@ async def handle_track_edit(db: AsyncSession, flight_id: int, track: List[TrackI
"point_of_interest_id": poi_id,
"airport_id": airport_id,
"order": order,
"landing_duration": item.landing_duration
"landing_duration": item.landing_duration if airport_id else None
}
)
order += 1
@@ -211,17 +162,3 @@ async def handle_aircraft_save(db: AsyncSession, user_id: int, aircraft: Combobo
"seats": 2,
"manufacturer": "",
})
@cached()
async def get_airport(db: AsyncSession, input: ComboboxInput, user_id: int):
airport_id = await handle_combobox_save(
db, models.Airport, input, user_id, name_column="icao_code", extra_data={"name": input.name}
)
return (await db.scalars(get_base_resolver(models.Airport, object_id=airport_id))).one()
async def handle_copilots_edit(db: AsyncSession, copilots: List[ComboboxInput], user_id: int) -> tuple:
cors = [handle_combobox_save(db, models.Copilot, copilot, user_id) for copilot in copilots]
return await asyncio.gather(*cors)
+21
View File
@@ -0,0 +1,21 @@
from datetime import datetime
from typing import List
class BaseGraphqlInputType:
base_ignored_fields: List[str] = ["created_at", "created_by_id", "updated_by_id", "updated_at", "deleted"]
ignored_fields: List[str] = []
def to_dict(self) -> dict:
dict_data = {}
for key in self.__annotations__.keys():
value = getattr(self, key)
if value is None or key in self.ignored_fields + self.base_ignored_fields:
continue
if isinstance(value, datetime):
value = value.astimezone()
dict_data[key] = value
return dict_data
@@ -1,8 +1,10 @@
from __future__ import annotations
from datetime import datetime
from typing import Optional, List
import strawberry
from strawberry.file_uploads import Upload
from database import models
from graphql_schema.entities.types.base import BaseGraphqlInputType
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_input
@@ -12,11 +14,51 @@ class ComboboxInput:
name: str
@strawberry_sqlalchemy_input(model=models.Copilot, exclude_fields=["id"])
class CreateCopilotInput(BaseGraphqlInputType):
pass
@strawberry_sqlalchemy_input(model=models.Copilot, exclude_fields=["id"])
class EditCopilotInput(BaseGraphqlInputType):
pass
@strawberry_sqlalchemy_input(models.PointOfInterest, exclude_fields=['id', 'type_id'])
class CreatePointOfInterestInput(BaseGraphqlInputType):
type: Optional[ComboboxInput] = None
@strawberry_sqlalchemy_input(models.PointOfInterest, exclude_fields=['id', 'type_id'])
class EditPointOfInterestInput(BaseGraphqlInputType):
type: Optional[ComboboxInput] = None
@strawberry_sqlalchemy_input(model=models.Organization, exclude_fields=["id"])
class CreateOrganizationInput(BaseGraphqlInputType):
pass
@strawberry_sqlalchemy_input(model=models.Organization, exclude_fields=["id"])
class EditOrganizationInput(BaseGraphqlInputType):
pass
@strawberry_sqlalchemy_input(model=models.Event, exclude_fields=["id"])
class CreateEventInput(BaseGraphqlInputType):
pass
@strawberry_sqlalchemy_input(model=models.Event, exclude_fields=["id"])
class EditEventInput(BaseGraphqlInputType):
pass
@strawberry_sqlalchemy_input(models.Flight, exclude_fields=[
"id", "aircraft_id", "deleted", "landing_airport_id", "takeoff_airport_id",
"takeoff_weather_info_id", "landing_weather_info_id", "gpx_track_filename"
"takeoff_weather_info_id", "landing_weather_info_id", "gpx_track_filename", "event_id"
], all_optional=True)
class EditFlightInput:
class EditFlightInput(BaseGraphqlInputType):
gpx_track: Optional[Upload] = None # TODO: poresit validaci uploadovaneho souboru!
track: Optional[List[TrackItemInput]] = None
copilots: Optional[List[ComboboxInput]] = None
@@ -26,19 +68,29 @@ class EditFlightInput:
event: Optional[ComboboxInput] = None
@strawberry_sqlalchemy_input(models.Flight, exclude_fields=[
"id", "aircraft_id", "landing_airport_id", "takeoff_airport_id", "weather_info_takeoff_id",
"weather_info_landing_id", "with_instructor", "has_terrain_elevation"
])
class CreateFlightInput:
@strawberry.input()
class CreateFlightInput(BaseGraphqlInputType):
aircraft: ComboboxInput
landing_airport: ComboboxInput
takeoff_airport: ComboboxInput
takeoff_datetime: datetime
landing_datetime: datetime
@strawberry.input()
class TrackItemInput:
# order: int
point_of_interest: Optional[ComboboxInput] = None
airport: Optional[ComboboxInput] = None
landing_duration: Optional[int] = None
@strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['id', 'photo_filename'])
class CreateAircraftInput(BaseGraphqlInputType):
photo: Optional[Upload]
organization: Optional[ComboboxInput] = None
@strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['photo_filename'])
class EditAircraftInput(BaseGraphqlInputType):
photo: Optional[Upload]
organization: Optional[ComboboxInput] = None
+50 -79
View File
@@ -2,7 +2,6 @@ from __future__ import annotations
from datetime import datetime
from typing import Optional, Annotated, List
import strawberry
from config import API_URL
from database import models
from decorators.endpoints import authenticated_user_only
from external.gpx_parser import GPXParser
@@ -17,30 +16,10 @@ from graphql_schema.dataloaders.single_model import (
poi_dataloader, poi_type_dataloader, event_dataloader, aircraft_dataloader, airport_dataloader, cover_photo_loader,
airport_weather_info_loader, organizations_dataloader
)
from graphql_schema.entities.airport import Airport
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
from paths import get_photo_basepath
from upload_utils import file_exists, get_public_url
@strawberry_sqlalchemy_type(models.FlightTrack)
class FlightTrack:
point_of_interest: Optional[PointOfInterest] = strawberry.field(
resolver=lambda root: poi_dataloader.load(root.point_of_interest_id)
)
airport: Optional[Airport] = strawberry.field(
resolver=lambda root: airport_dataloader.load(root.airport_id)
)
@strawberry_sqlalchemy_type(models.PointOfInterestType)
class PointOfInterestType:
pass
@strawberry_sqlalchemy_type(models.WeatherInfo)
class WeatherInfo:
pass
from paths import (
get_public_url, get_avatar_url, get_title_image_url, get_photo_thumbnail_url, get_photo_url, FLIGHT_GPX_TRACK_PATH
)
@strawberry.type
@@ -49,35 +28,6 @@ class Point:
lng: float
@strawberry_sqlalchemy_type(models.PointOfInterest)
class PointOfInterest:
type: Optional[PointOfInterestType] = strawberry.field(
resolver=lambda root: poi_type_dataloader.load(root.type_id)
)
photos: List[Annotated["Photo", strawberry.lazy('.photo')]] = strawberry.field(
resolver=lambda root: poi_photos_dataloader.load(root.id)
)
flights: List[Annotated["Flight", strawberry.lazy('.flight')]] = strawberry.field(
resolver=lambda root: flight_by_poi_dataloader.load(root.id)
)
@strawberry_sqlalchemy_type(models.Photo)
class Photo:
def resolve_thumb_url(root):
thumbnail = get_photo_basepath(root.flight_id) + "/thumbs/" + root.filename
if not file_exists(thumbnail):
return get_public_url(f"photos/{root.flight_id}/{root.filename}")
return get_public_url(f"photos/{root.flight_id}/thumbs/{root.filename}")
url: str = strawberry.field(resolver=lambda root: get_public_url(f"photos/{root.flight_id}/{root.filename}"))
thumbnail_url: str = strawberry.field(resolver=resolve_thumb_url)
point_of_interest: Optional[Annotated["PointOfInterest", strawberry.lazy('.poi')]] = strawberry.field(
resolver=lambda root: poi_dataloader.load(root.point_of_interest_id)
)
@strawberry.type
class GPXTrack:
coordinates: List[Point]
@@ -92,6 +42,45 @@ class GPXTrack:
avg_altitude: float
@strawberry_sqlalchemy_type(models.Airport)
class Airport:
pass
@strawberry_sqlalchemy_type(models.FlightTrack)
class FlightTrack:
point_of_interest: Optional[PointOfInterest] = strawberry.field(
resolver=lambda root: poi_dataloader.load(root.point_of_interest_id)
)
airport: Optional[Airport] = strawberry.field(resolver=lambda root: airport_dataloader.load(root.airport_id))
@strawberry_sqlalchemy_type(models.PointOfInterestType)
class PointOfInterestType:
pass
@strawberry_sqlalchemy_type(models.WeatherInfo)
class WeatherInfo:
pass
@strawberry_sqlalchemy_type(models.PointOfInterest)
class PointOfInterest:
type: Optional[PointOfInterestType] = strawberry.field(resolver=lambda root: poi_type_dataloader.load(root.type_id))
photos: List[Photo] = strawberry.field(resolver=lambda root: poi_photos_dataloader.load(root.id))
flights: List[Flight] = strawberry.field(resolver=lambda root: flight_by_poi_dataloader.load(root.id))
@strawberry_sqlalchemy_type(models.Photo)
class Photo:
url: str = strawberry.field(resolver=get_photo_url)
thumbnail_url: str = strawberry.field(resolver=get_photo_thumbnail_url)
point_of_interest: Optional[PointOfInterest] = strawberry.field(
resolver=lambda root: poi_dataloader.load(root.point_of_interest_id)
)
@strawberry_sqlalchemy_type(models.Flight)
class Flight:
async def load_gpx_track(root):
@@ -99,7 +88,7 @@ class Flight:
return None
try:
gpx_parser = GPXParser(f"/app/uploads/tracks/{root.gpx_track_filename}")
gpx_parser = GPXParser(f"{FLIGHT_GPX_TRACK_PATH}/{root.gpx_track_filename}")
except OSError:
return None
@@ -146,9 +135,7 @@ class Flight:
@strawberry_sqlalchemy_type(models.Copilot)
class Copilot:
flights: List[Annotated["Flight", strawberry.lazy('.flight')]] = strawberry.field(
resolver=lambda root: flights_by_copilot_dataloader.load(root.id)
)
flights: List[Flight] = strawberry.field(resolver=lambda root: flights_by_copilot_dataloader.load(root.id))
@strawberry_sqlalchemy_type(models.Aircraft)
@@ -156,40 +143,24 @@ class Aircraft:
photo_url: Optional[str] = strawberry.field(
resolver=lambda root: get_public_url(f"aircrafts/{root.photo_filename}") if root.photo_filename else None
)
flights: List[Annotated["Flight", strawberry.lazy('.flight')]] = strawberry.field(
resolver=lambda root: flights_by_aircraft_dataloader.load(root.id)
)
organization: Optional[Annotated["Organization", strawberry.lazy(".organization")]] = strawberry.field(
flights: List[Flight] = strawberry.field(resolver=lambda root: flights_by_aircraft_dataloader.load(root.id))
organization: Optional[Organization] = strawberry.field(
resolver=lambda root: organizations_dataloader.load(root.organization_id)
)
@strawberry_sqlalchemy_type(models.Organization)
class Organization:
users: List[Annotated["User", strawberry.lazy(".user")]] = strawberry.field(
resolver=lambda root: users_in_organization_dataloader.load(root.id)
)
aircrafts: List[Annotated["Aircraft", strawberry.lazy(".aircraft")]] = strawberry.field(
users: List[User] = strawberry.field(resolver=lambda root: users_in_organization_dataloader.load(root.id))
aircrafts: List[Aircraft] = strawberry.field(
resolver=lambda root: aircrafts_from_organization_dataloader.load(root.id)
)
@strawberry_sqlalchemy_type(models.User, exclude_fields=['password_hashed'])
class User:
async def load_avatar_image_url(root):
if not root.avatar_image_filename:
return None
return get_public_url(f"profile/{root.id}/{root.avatar_image_filename}")
async def load_title_image_url(root):
if not root.title_image_filename:
return f"{API_URL}/static/default-title-image.jpg"
return get_public_url(f"profile/{root.id}/{root.title_image_filename}")
avatar_image_url: Optional[str] = strawberry.field(resolver=load_avatar_image_url)
title_image_url: str = strawberry.field(resolver=load_title_image_url)
avatar_image_url: Optional[str] = strawberry.field(resolver=lambda root: get_avatar_url(root))
title_image_url: str = strawberry.field(resolver=lambda root: get_title_image_url(root))
organizations: List[Annotated['Organization', strawberry.lazy(".organization")]] = strawberry.field(
resolver=lambda root: user_organizations_dataloader.load(root.id)
)
+5 -6
View File
@@ -1,4 +1,4 @@
from typing import Optional, TYPE_CHECKING
from typing import Optional
import strawberry
from graphql import GraphQLError
from passlib.hash import bcrypt
@@ -7,12 +7,11 @@ from strawberry.file_uploads import Upload
from database import models
from decorators.endpoints import authenticated_user_only
from decorators.error_logging import error_logging
from dependencies.db import get_session
from upload_utils import handle_file_upload, delete_file, resize_image
from database.transaction import get_session
from graphql_schema.entities.types.types import User
if TYPE_CHECKING:
pass
from utils.file import delete_file
from utils.image import resize_image
from utils.upload import handle_file_upload
@strawberry.type