Pridani skriptu pro pridani elevace, uprava prace s transakcemi

This commit is contained in:
Michal Kváček
2023-09-12 11:00:36 +02:00
parent ed5df4f953
commit 64cb544e2d
22 changed files with 519 additions and 226 deletions
@@ -0,0 +1,46 @@
"""add terrain elevation to photos and event table
Revision ID: 6e5cc5123a2b
Revises: 76752a74a304
Create Date: 2023-09-05 09:54:45.509461
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6e5cc5123a2b'
down_revision = '76752a74a304'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('event',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=128), nullable=False),
sa.Column('description', sa.Text(), nullable=False),
sa.Column('is_public', sa.Boolean(), server_default='0', 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.Column('deleted', sa.Boolean(), server_default='0', nullable=False),
sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.add_column('flight', sa.Column('event_id', sa.Integer(), nullable=True))
op.add_column('flight', sa.Column('has_terrain_elevation', sa.Boolean(), nullable=False))
op.create_foreign_key(None, 'flight', 'event', ['event_id'], ['id'])
op.add_column('photo', sa.Column('terrain_elevation', sa.Float(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('photo', 'terrain_elevation')
op.drop_constraint(None, 'flight', type_='foreignkey')
op.drop_column('flight', 'has_terrain_elevation')
op.drop_column('flight', 'event_id')
op.drop_table('event')
# ### end Alembic commands ###
+19
View File
@@ -0,0 +1,19 @@
from database import models
from dependencies.db import get_session
from external.elevation import elevation_api
async def add_terrain_elevation(photo):
async with get_session() as db:
try:
elevation = await elevation_api.get_elevation_for_points([{"lat": photo.gps_latitude, "lng": photo.gps_longitude}])
if not elevation:
print("Cannot get elevation")
return
terrain_elevation = elevation[0]['elevation']
await models.Photo.update(db_session=db, obj=photo, data={"terrain_elevation": terrain_elevation})
except Exception as e:
print(f"Cannot get elevation: {e}")
+19 -1
View File
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
@as_declarative() @as_declarative()
class BaseModel: class BaseModel:
excluded_columns_in_dict = tuple() excluded_columns_in_dict = ("deleted",)
def as_dict(self): def as_dict(self):
return { return {
@@ -122,6 +122,7 @@ class Photo(BaseModel):
gps_latitude: Mapped[float] = mapped_column(Float, nullable=True) gps_latitude: Mapped[float] = mapped_column(Float, nullable=True)
gps_longitude: Mapped[float] = mapped_column(Float, nullable=True) gps_longitude: Mapped[float] = mapped_column(Float, nullable=True)
gps_altitude: Mapped[float] = mapped_column(Float, nullable=True) gps_altitude: Mapped[float] = mapped_column(Float, nullable=True)
terrain_elevation: Mapped[float] = mapped_column(Float, nullable=True)
point_of_interest_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest.id"), nullable=True) point_of_interest_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest.id"), nullable=True)
flight_id: Mapped[int] = mapped_column(Integer, ForeignKey("flight.id"), nullable=False) flight_id: Mapped[int] = mapped_column(Integer, ForeignKey("flight.id"), nullable=False)
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id')) created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
@@ -208,12 +209,27 @@ class WeatherInfo(BaseModel):
datetime: Mapped[datetime] = mapped_column(DateTime, nullable=False) datetime: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class Event(BaseModel):
__tablename__ = "event"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(128), nullable=False)
description: Mapped[str] = mapped_column(Text, nullable=False)
is_public: Mapped[bool] = mapped_column(Boolean, server_default='0')
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
created_by: Mapped['User'] = relationship()
class Flight(BaseModel): class Flight(BaseModel):
__tablename__ = "flight" __tablename__ = "flight"
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(128), nullable=False) name: Mapped[str] = mapped_column(String(128), nullable=False)
description: Mapped[str] = mapped_column(Text, nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False)
event_id: Mapped[int] = mapped_column(Integer, ForeignKey("event.id"), nullable=True)
takeoff_airport_id: Mapped[int] = mapped_column(Integer, ForeignKey("airport.id"), nullable=False) takeoff_airport_id: Mapped[int] = mapped_column(Integer, ForeignKey("airport.id"), nullable=False)
landing_airport_id: Mapped[int] = mapped_column(Integer, ForeignKey("airport.id"), nullable=False) landing_airport_id: Mapped[int] = mapped_column(Integer, ForeignKey("airport.id"), nullable=False)
takeoff_datetime: Mapped[datetime] = mapped_column(DateTime, nullable=False) takeoff_datetime: Mapped[datetime] = mapped_column(DateTime, nullable=False)
@@ -221,6 +237,7 @@ class Flight(BaseModel):
duration_total: Mapped[int] = mapped_column(Integer, nullable=True) duration_total: Mapped[int] = mapped_column(Integer, nullable=True)
duration_pic: Mapped[int] = mapped_column(Integer, nullable=True) duration_pic: Mapped[int] = mapped_column(Integer, nullable=True)
gpx_track_filename: Mapped[str] = mapped_column(String(128), nullable=True) gpx_track_filename: Mapped[str] = mapped_column(String(128), nullable=True)
has_terrain_elevation: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
aircraft_id: Mapped[int] = mapped_column(Integer, ForeignKey('aircraft.id')) aircraft_id: Mapped[int] = mapped_column(Integer, ForeignKey('aircraft.id'))
takeoff_weather_info_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True) takeoff_weather_info_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True)
landing_weather_info_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True) landing_weather_info_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True)
@@ -235,6 +252,7 @@ class Flight(BaseModel):
weather_info_landing: Mapped[WeatherInfo] = relationship(foreign_keys=[landing_weather_info_id]) weather_info_landing: Mapped[WeatherInfo] = relationship(foreign_keys=[landing_weather_info_id])
weather_info_takeoff: Mapped[WeatherInfo] = relationship(foreign_keys=[takeoff_weather_info_id]) weather_info_takeoff: Mapped[WeatherInfo] = relationship(foreign_keys=[takeoff_weather_info_id])
track: Mapped['FlightTrack'] = relationship() track: Mapped['FlightTrack'] = relationship()
event: Mapped['Event'] = relationship()
copilots: Mapped[List['Copilot']] = relationship(secondary=flight_has_copilot) copilots: Mapped[List['Copilot']] = relationship(secondary=flight_has_copilot)
aircraft: Mapped['Aircraft'] = relationship(back_populates="flights") aircraft: Mapped['Aircraft'] = relationship(back_populates="flights")
photos: Mapped[List['Photo']] = relationship(foreign_keys=[Photo.flight_id]) photos: Mapped[List['Photo']] = relationship(foreign_keys=[Photo.flight_id])
+35
View File
@@ -0,0 +1,35 @@
import asyncio
import functools
from typing import Callable
from database import async_session
def transactional(func: Callable) -> Callable:
@functools.wraps(func)
async def _wrapper(*args, **kwargs):
# db_session = db_session_context.get()
# if db_session:
# return func(*args, **kwargs)
db_session = async_session()
print("STARTUJI TRANSAKCI")
db_session.begin()
# db_session_context.set(db_session)
try:
kwargs['db'] = db_session
result = await func(*args, **kwargs)
await db_session.commit()
print("KONEC TRANSAKCE V DEKORATORU")
except Exception as e:
await db_session.rollback()
raise
finally:
await db_session.close()
# db_session_context.set(None)
return result
return _wrapper
+21
View File
@@ -1,9 +1,30 @@
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from sqlalchemy.ext.asyncio import AsyncSession
from database import async_session from database import async_session
@asynccontextmanager
async def get_session():
async with async_session() as session:
async with session.begin():
try:
yield session
await session.commit()
except:
await session.rollback()
raise
finally:
session.expunge_all()
await session.close()
async def db_session(): async def db_session():
async with async_session() as session: async with async_session() as session:
async with session.begin(): async with session.begin():
yield session yield session
await session.flush() await session.flush()
await session.commit() await session.commit()
print("CCCCCCCCCCCCCOOOOOOOOOOOOOOMMMMMMMMMMIIIIIIIIIIIITTTTTTTTTTTT")
+4 -1
View File
@@ -22,4 +22,7 @@ class ElevationAPI:
"lat": loc['latitude'], "lat": loc['latitude'],
"lng": loc['longitude'], "lng": loc['longitude'],
"elevation": loc['elevation'] "elevation": loc['elevation']
} for loc in response['results']] } for loc in response['results']]
elevation_api = ElevationAPI()
+1
View File
@@ -17,6 +17,7 @@ class PhotoDataloader:
models = (await session.scalars( models = (await session.scalars(
select(Photo) select(Photo)
.filter(getattr(Photo, self.relationship_column).in_(ids)) .filter(getattr(Photo, self.relationship_column).in_(ids))
.order_by(Photo.exposed_at)
)).all() )).all()
photos_by_relationship_id = defaultdict(list) photos_by_relationship_id = defaultdict(list)
for photo in models: for photo in models:
+21 -8
View File
@@ -4,6 +4,7 @@ from strawberry.file_uploads import Upload
from sqlalchemy import select from sqlalchemy import select
from database import models from database import models
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input
from upload_utils import handle_file_upload, delete_file, get_public_url from upload_utils import handle_file_upload, delete_file, get_public_url
from ..dataloaders.flight import flights_by_aircraft_dataloader from ..dataloaders.flight import flights_by_aircraft_dataloader
@@ -44,8 +45,10 @@ class AircraftQueries:
get_base_query(info.context.user_id) get_base_query(info.context.user_id)
.order_by(models.Aircraft.id.desc()) .order_by(models.Aircraft.id.desc())
) )
async with get_session() as db:
aircrafts = (await db.scalars(query)).all()
return (await info.context.db.scalars(query)).all() return [Aircraft(**a.as_dict()) for a in aircrafts]
@strawberry.field() @strawberry.field()
@authenticated_user_only() @authenticated_user_only()
@@ -54,7 +57,9 @@ class AircraftQueries:
get_base_query(info.context.user_id) get_base_query(info.context.user_id)
.filter(models.Aircraft.id == id) .filter(models.Aircraft.id == id)
) )
return (await info.context.db.scalars(query)).one() async with get_session() as db:
aircraft = (await db.scalars(query)).one()
return Aircraft(**aircraft.as_dict())
@strawberry.type @strawberry.type
@@ -73,7 +78,7 @@ class CreateAircraftMutation:
input_data['photo_filename'] = await handle_file_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH) input_data['photo_filename'] = await handle_file_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH)
return await models.Aircraft.create( return await models.Aircraft.create(
info.context.db, db,
data=dict( data=dict(
**input_data, **input_data,
created_by_id=info.context.user_id, created_by_id=info.context.user_id,
@@ -91,17 +96,20 @@ class EditAircraftMutation:
@authenticated_user_only() @authenticated_user_only()
async def edit_aircraft(root, info, id: int, input: EditAircraftInput) -> Aircraft: async def edit_aircraft(root, info, id: int, input: EditAircraftInput) -> Aircraft:
# TODO: kontrola organizace # TODO: kontrola organizace
# TODO: kontrola opravneni na akci
update_data = input.to_dict() update_data = input.to_dict()
aircraft = await models.Aircraft.get_one(info.context.db, id) async with get_session() as db:
aircraft = (await db.scalars(
get_base_query(info.context.user_id)
.filter(models.Aircraft.id == id)
)).one()
if input.photo: if input.photo:
if aircraft.photo_filename: if aircraft.photo_filename:
delete_file(AIRCRAFT_UPLOAD_DEST_PATH + "/" + aircraft.photo_filename, silent=True) 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) update_data['photo_filename'] = await handle_file_upload(input.photo, AIRCRAFT_UPLOAD_DEST_PATH)
return await models.Aircraft.update(info.context.db, obj=aircraft, data=update_data) return await models.Aircraft.update(db, obj=aircraft, data=update_data)
@strawberry.type @strawberry.type
@@ -110,6 +118,11 @@ class DeleteAircraftMutation:
@strawberry.mutation @strawberry.mutation
@authenticated_user_only() @authenticated_user_only()
async def delete_aircraft(self, info, id: int) -> Aircraft: async def delete_aircraft(self, info, id: int) -> Aircraft:
# TODO: kontrola opravneni na akci async with get_session() as db:
aircraft = (await db.scalars(
get_base_query(info.context.user_id)
.filter(models.Aircraft.id == id)
)).one()
return await models.Aircraft.update(info.context.db, id=id, data=dict(deleted=True)) aircraft = await models.Aircraft.update(db, obj=aircraft, data=dict(deleted=True))
return Aircraft(**aircraft.as_dict())
+12 -9
View File
@@ -3,6 +3,7 @@ import strawberry
from sqlalchemy import select, or_ from sqlalchemy import select, or_
from database import models from database import models
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
@@ -19,27 +20,29 @@ def get_base_query(user_id: int):
models.Airport.created_by_id == user_id, models.Airport.created_by_id == user_id,
models.Airport.created_by_id.is_(None), models.Airport.created_by_id.is_(None),
)) ))
.order_by(models.Airport.icao_code)
) )
@strawberry.type @strawberry.type
class AirportQueries: class AirportQueries:
@strawberry.field()
@strawberry.field
@authenticated_user_only() @authenticated_user_only()
async def airports(root, info) -> List[Airport]: async def airports(root, info) -> List[Airport]:
query = ( query = get_base_query(info.context.user_id)
get_base_query(info.context.user_id)
.order_by(models.Airport.id.desc())
)
return (await info.context.db.scalars(query)).all() async with get_session() as db:
airports = (await db.scalars(query)).all()
return [Airport(**a.as_dict()) for a in airports]
@strawberry.field @strawberry.field()
@authenticated_user_only() @authenticated_user_only()
async def airport(root, info, id: int) -> Airport: async def airport(root, info, id: int) -> Airport:
query = ( query = (
get_base_query(info.context.user_id) get_base_query(info.context.user_id)
.filter(models.Airport.id == id) .filter(models.Airport.id == id)
) )
return (await info.context.db.scalars(query)).one()
async with get_session() as db:
airport = (await db.scalars(query)).one()
return Airport(**airport.as_dict())
+31 -18
View File
@@ -3,6 +3,7 @@ import strawberry
from sqlalchemy import select from sqlalchemy import select
from database import models from database import models
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.dataloaders.flight import flights_by_copilot_dataloader from graphql_schema.dataloaders.flight import flights_by_copilot_dataloader
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input
@@ -23,7 +24,7 @@ def get_base_query(user_id: int):
select(models.Copilot) select(models.Copilot)
.filter(models.Copilot.created_by_id == user_id) .filter(models.Copilot.created_by_id == user_id)
.filter(models.Copilot.deleted.is_(False)) .filter(models.Copilot.deleted.is_(False))
.order_by(models.Copilot.id.desc()) .order_by(models.Copilot.name)
) )
@@ -32,17 +33,22 @@ class CopilotQueries:
@strawberry.field() @strawberry.field()
@authenticated_user_only() @authenticated_user_only()
async def copilots(root, info) -> List[Copilot]: async def copilots(root, info) -> List[Copilot]:
return (await info.context.db.scalars( async with get_session() as db:
get_base_query(info.context.user_id) copilots = (await db.scalars(
)).all() get_base_query(info.context.user_id)
)).all()
return [Copilot(**c.as_dict()) for c in copilots]
@strawberry.field() @strawberry.field()
@authenticated_user_only() @authenticated_user_only()
async def copilot(root, info, id: int) -> Copilot: async def copilot(root, info, id: int) -> Copilot:
return (await info.context.db.scalars( async with get_session() as db:
get_base_query(info.context.user_id) copilot = (await db.scalars(
.filter(models.Copilot.id == id) get_base_query(info.context.user_id)
)).one() .filter(models.Copilot.id == id)
)).one()
return Copilot(**copilot.as_dict())
@strawberry.type @strawberry.type
@@ -56,13 +62,16 @@ class CreateCopilotMutation:
@authenticated_user_only() @authenticated_user_only()
async def create_copilot(root, info, input: CreateCopilotInput) -> Copilot: async def create_copilot(root, info, input: CreateCopilotInput) -> Copilot:
input_data = input.to_dict() input_data = input.to_dict()
return await models.Copilot.create( async with get_session() as db:
info.context.db, copilot = await models.Copilot.create(
data=dict( db,
**input_data, data=dict(
created_by_id=info.context.user_id, **input_data,
created_by_id=info.context.user_id,
)
) )
)
return Copilot(**copilot.as_dict())
@strawberry.type @strawberry.type
@@ -75,8 +84,12 @@ class EditCopilotMutation:
@strawberry.mutation @strawberry.mutation
@authenticated_user_only() @authenticated_user_only()
async def edit_copilot(root, info, id: int, input: EditCopilotInput) -> Copilot: async def edit_copilot(root, info, id: int, input: EditCopilotInput) -> Copilot:
copilot = (await info.context.db.scalars(
get_base_query(info.context.user_id).filter(models.Copilot.id == id)
)).one()
return await models.Copilot.update(info.context.db, obj=copilot, data=input.to_dict()) async with get_session() as db:
copilot = (await db.scalars(
get_base_query(info.context.user_id).filter(models.Copilot.id == id)
)).one()
updated_copilot = await models.Copilot.update(db, obj=copilot, data=input.to_dict())
return Copilot(**updated_copilot.as_dict())
+95 -71
View File
@@ -11,6 +11,7 @@ from database import models
from database.models import flight_has_copilot from database.models import flight_has_copilot
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from decorators.error_logging import error_logging from decorators.error_logging import error_logging
from dependencies.db import get_session
from external.gpx_parser import GPXParser from external.gpx_parser import GPXParser
from graphql_schema.dataloaders.aircraft import aircraft_dataloader from graphql_schema.dataloaders.aircraft import aircraft_dataloader
from graphql_schema.dataloaders.airport import airport_dataloader from graphql_schema.dataloaders.airport import airport_dataloader
@@ -157,7 +158,11 @@ def get_base_query(user_id: Optional[int], username: Optional[str] = None, is_au
query = query.filter(models.Flight.created_by_id == user_id) query = query.filter(models.Flight.created_by_id == user_id)
if username: if username:
query = query.join(models.Flight.created_by).filter(models.User.public_username == username) query = (
query
.join(models.Flight.created_by)
.filter(models.User.public_username == username)
)
if not is_auth: if not is_auth:
query = query.filter(models.Flight.is_public.is_(True)) query = query.filter(models.Flight.is_public.is_(True))
@@ -171,13 +176,16 @@ class FlightQueries:
@strawberry.field() @strawberry.field()
async def flights(root, info, username: Optional[str] = None) -> List[Flight]: async def flights(root, info, username: Optional[str] = None) -> List[Flight]:
if not info.context.user_id and not username: if not info.context.user_id and not username:
raise HTTPException(HTTP_401_UNAUTHORIZED, f"user_id={info.context.user_id}, {username=}") raise HTTPException(HTTP_401_UNAUTHORIZED)
query = ( query = (
get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id)) get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id))
.order_by(models.Flight.id.desc()) .order_by(models.Flight.id.desc())
) )
return (await info.context.db.scalars(query)).all()
async with get_session() as db:
flights = (await db.scalars(query)).all()
return [Flight(**f.as_dict()) for f in flights]
@strawberry.field() @strawberry.field()
@error_logging @error_logging
@@ -189,14 +197,17 @@ class FlightQueries:
get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id)) get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id))
.filter(models.Flight.id == id) .filter(models.Flight.id == id)
) )
return (await info.context.db.scalars(query)).one()
async with get_session() as db:
flight = (await db.scalars(query)).one()
return Flight(**flight.as_dict())
@strawberry.type @strawberry.type
class CreateFlightMutation: class CreateFlightMutation:
@strawberry_sqlalchemy_input(models.Flight, exclude_fields=[ @strawberry_sqlalchemy_input(models.Flight, exclude_fields=[
"id", "aircraft_id", "landing_airport_id", "takeoff_airport_id", "weather_info_takeoff_id", "id", "aircraft_id", "landing_airport_id", "takeoff_airport_id", "weather_info_takeoff_id",
"weather_info_landing_id", "with_instructor" "weather_info_landing_id", "with_instructor", "has_terrain_elevation"
]) ])
class CreateFlightInput: class CreateFlightInput:
aircraft: ComboboxInput aircraft: ComboboxInput
@@ -206,28 +217,29 @@ class CreateFlightMutation:
@strawberry.mutation @strawberry.mutation
@authenticated_user_only() @authenticated_user_only()
async def create_flight(self, info, input: CreateFlightInput) -> Flight: async def create_flight(self, info, input: CreateFlightInput) -> Flight:
db = info.context.db
data = input.to_dict() data = input.to_dict()
takeoff_airport, landing_airport = await get_airports(db, input.takeoff_airport.id, input.landing_airport.id) async with get_session() as db:
takeoff_airport, landing_airport = await get_airports(db, input.takeoff_airport.id, input.landing_airport.id)
aircraft_id = await handle_aircraft_save(db, info.context.user_id, input.aircraft) aircraft_id = await handle_aircraft_save(db, info.context.user_id, input.aircraft)
weather_takeoff, weather_landing = await asyncio.gather( weather_takeoff, weather_landing = await asyncio.gather(
handle_weather_info(db, data['takeoff_datetime'], takeoff_airport), handle_weather_info(db, data['takeoff_datetime'], takeoff_airport),
handle_weather_info(db, data['landing_datetime'], landing_airport) handle_weather_info(db, data['landing_datetime'], landing_airport)
) )
await db.flush() await db.flush()
flight = await models.Flight.create(db, data={ flight = await models.Flight.create(db, data={
**data, **data,
"takeoff_weather_info_id": weather_takeoff.id, "takeoff_weather_info_id": weather_takeoff.id,
"landing_weather_info_id": weather_landing.id, "landing_weather_info_id": weather_landing.id,
"takeoff_airport_id": takeoff_airport.id, "takeoff_airport_id": takeoff_airport.id,
"landing_airport_id": landing_airport.id, "landing_airport_id": landing_airport.id,
"aircraft_id": aircraft_id, "has_terrain_elevation": False,
"created_by_id": info.context.user_id "aircraft_id": aircraft_id,
}) "created_by_id": info.context.user_id
return flight })
return Flight(**flight.as_dict())
@strawberry.type @strawberry.type
@@ -247,56 +259,66 @@ class EditFlightMutation:
@strawberry.mutation @strawberry.mutation
@authenticated_user_only() @authenticated_user_only()
async def edit_flight(self, info, id: int, input: EditFlightInput) -> Flight: async def edit_flight(self, info, id: int, input: EditFlightInput) -> Flight:
db = info.context.db
user_id = info.context.user_id user_id = info.context.user_id
flight = (await db.scalars( async with get_session() as db:
get_base_query(user_id=user_id, is_auth=bool(user_id)).filter(models.Flight.id == id)
)).one()
takeoff_airport, landing_airport = await get_airports( flight = (await db.scalars(
db, get_base_query(user_id=user_id, is_auth=bool(user_id)).filter(models.Flight.id == id)
takeoff_airport_id=input.takeoff_airport.id if input.takeoff_airport else flight.takeoff_airport_id, )).one()
landing_airport_id=input.landing_airport.id if input.landing_airport else flight.landing_airport_id,
)
data = input.to_dict() takeoff_airport, landing_airport = await get_airports(
if input.gpx_track is not None:
data['gpx_track_filename'] = await handle_upload_gpx(flight, input.gpx_track)
info.context.background_tasks.add_task(add_terrain_elevation, flight=flight, gpx_filename=data['gpx_track_filename'], db=db)
# TODO: nasledujici metody volat i pokud se zmenil cas vzletu!
if input.takeoff_airport and input.takeoff_airport.id != flight.takeoff_airport_id:
await handle_airport_changed(
db, db,
flight, takeoff_airport_id=input.takeoff_airport.id if input.takeoff_airport else flight.takeoff_airport_id,
takeoff_airport, landing_airport_id=input.landing_airport.id if input.landing_airport else flight.landing_airport_id,
type_="takeoff",
input_datetime=data.get('takeoff_datetime')
) )
if input.landing_airport and input.landing_airport.id != flight.landing_airport_id: data = input.to_dict()
await handle_airport_changed(
db,
flight,
landing_airport,
type_="landing",
input_datetime=data.get('landing_datetime')
)
if input.aircraft is not None: if input.gpx_track is not None:
data['aircraft_id'] = await handle_aircraft_save(db, user_id, input.aircraft) data['gpx_track_filename'] = await handle_upload_gpx(flight, input.gpx_track)
info.context.background_tasks.add_task(
add_terrain_elevation, flight=flight, gpx_filename=data['gpx_track_filename'], db=db
)
if input.track is not None: if (
await handle_track_edit(db=db, flight=flight, track=input.track, user_id=user_id) (input.takeoff_airport and input.takeoff_airport.id != flight.takeoff_airport_id) or
(data.get('takeoff_datetime') and data.get('takeoff_datetime') != flight.takeoff_datetime)
):
await handle_airport_changed(
db,
flight,
takeoff_airport,
type_="takeoff",
input_datetime=data.get('takeoff_datetime')
)
copilots = await handle_copilots_edit(db, input.copilots or [], user_id) if (
await db.execute(delete(flight_has_copilot).filter_by(flight_id=flight.id)) (input.landing_airport and input.landing_airport.id != flight.landing_airport_id) or
for copilot_id in copilots: (data.get('landing_datetime') and data.get('landing_datetime') != flight.takeoff_datetime)
await db.execute(insert(flight_has_copilot).values(flight_id=flight.id, copilot_id=copilot_id)) ):
await handle_airport_changed(
db,
flight,
landing_airport,
type_="landing",
input_datetime=data.get('landing_datetime')
)
return await models.Flight.update(db, obj=flight, data=data) if input.aircraft is not None:
data['aircraft_id'] = await handle_aircraft_save(db, user_id, input.aircraft)
if input.track is not None:
await handle_track_edit(db=db, flight=flight, track=input.track, user_id=user_id)
copilots = await handle_copilots_edit(db, input.copilots or [], user_id)
await db.execute(delete(flight_has_copilot).filter_by(flight_id=flight.id))
for copilot_id in copilots:
await db.execute(insert(flight_has_copilot).values(flight_id=flight.id, copilot_id=copilot_id))
updated_flight = await models.Flight.update(db, obj=flight, data=data)
return Flight(**updated_flight.as_dict())
@strawberry.type @strawberry.type
@@ -305,13 +327,15 @@ class DeleteFlightMutation:
@strawberry.mutation @strawberry.mutation
@authenticated_user_only() @authenticated_user_only()
async def delete_flight(self, info, id: int) -> Flight: async def delete_flight(self, info, id: int) -> Flight:
flight = ( async with get_session() as db:
(await info.context.db.scalars( flight = (
get_base_query(user_id=info.context.user_id, is_auth=True) (await db.scalars(
.filter(models.Flight.id == id)) get_base_query(user_id=info.context.user_id, is_auth=True)
) .filter(models.Flight.id == id))
.one() )
) .one()
flight.deleted = True )
return flight updated_flight = await models.Flight.update(db, obj=flight, data=dict(deleted=True))
return Flight(**updated_flight.as_dict())
@@ -9,7 +9,7 @@ from starlette.background import BackgroundTasks
from strawberry.file_uploads import Upload from strawberry.file_uploads import Upload
from database import models from database import models
from external.elevation import ElevationAPI from external.elevation import ElevationAPI, elevation_api
from external.gpx_parser import GPXParser from external.gpx_parser import GPXParser
from external.weather import Weather from external.weather import Weather
from graphql_schema.types import ComboboxInput from graphql_schema.types import ComboboxInput
@@ -104,6 +104,7 @@ async def handle_airport_changed(
flight_datetime = getattr(flight, f"{type_}_datetime") flight_datetime = getattr(flight, f"{type_}_datetime")
if input_datetime and input_datetime != flight_datetime: if input_datetime and input_datetime != flight_datetime:
weather = await handle_weather_info(db, input_datetime, airport) weather = await handle_weather_info(db, input_datetime, airport)
await db.flush()
existing_weather_id = getattr(flight, f"{type_}_weather_info_id") existing_weather_id = getattr(flight, f"{type_}_weather_info_id")
if existing_weather_id: if existing_weather_id:
@@ -119,7 +120,6 @@ async def handle_airport_changed(
async def add_terrain_elevation(db: AsyncSession, flight: models.Flight, gpx_filename: str): async def add_terrain_elevation(db: AsyncSession, flight: models.Flight, gpx_filename: str):
path = "/app/uploads/tracks" # TODO vytahnout do configu path = "/app/uploads/tracks" # TODO vytahnout do configu
elevation_api = ElevationAPI()
gpx_parser = GPXParser(f"{path}/{gpx_filename}") gpx_parser = GPXParser(f"{path}/{gpx_filename}")
coordinates = await gpx_parser.get_coordinates() coordinates = await gpx_parser.get_coordinates()
@@ -131,7 +131,7 @@ async def add_terrain_elevation(db: AsyncSession, flight: models.Flight, gpx_fil
tree_with_elevation = gpx_parser.add_terrain_elevation(elevation) tree_with_elevation = gpx_parser.add_terrain_elevation(elevation)
output_name = f"terrain_{gpx_filename}" output_name = f"terrain_{gpx_filename}"
gpx_parser.write(tree_with_elevation, f"{path}/{output_name}") gpx_parser.write(tree_with_elevation, f"{path}/{output_name}")
await models.Flight.update(db, {"gpx_track_filename": output_name}, obj=flight) await models.Flight.update(db, {"gpx_track_filename": output_name, "has_terrain_elevation": True}, obj=flight)
except ClientResponseError: except ClientResponseError:
print("NEumim elevation!") print("NEumim elevation!")
+55 -41
View File
@@ -2,8 +2,10 @@ from typing import List, Optional, Annotated, TYPE_CHECKING
import strawberry import strawberry
from sqlalchemy import select, update from sqlalchemy import select, update
from strawberry.file_uploads import Upload from strawberry.file_uploads import Upload
from background_jobs.photo import add_terrain_elevation
from database import models from database import models
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.dataloaders.poi import poi_dataloader from graphql_schema.dataloaders.poi import poi_dataloader
from graphql_schema.entities.poi import PointOfInterest from graphql_schema.entities.poi import PointOfInterest
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
@@ -55,7 +57,10 @@ class PhotoQueries:
@strawberry.field() @strawberry.field()
async def photos(root, info) -> List[Photo]: async def photos(root, info) -> List[Photo]:
query = get_base_query(info.context.user_id) query = get_base_query(info.context.user_id)
return (await info.context.db.scalars(query)).all()
async with get_session() as db:
photos = (await db.scalars(query)).all()
return [Photo(**photo.as_dict()) for photo in photos]
@strawberry.type @strawberry.type
@@ -73,24 +78,30 @@ class UploadPhotoMutation:
async def upload_photo(self, info, input: UploadPhotoInput) -> Photo: async def upload_photo(self, info, input: UploadPhotoInput) -> Photo:
path = get_photo_basepath(input.flight_id) path = get_photo_basepath(input.flight_id)
filename = await handle_file_upload(input.photo, path) filename = await handle_file_upload(input.photo, path)
info.context.background_tasks.add_task(resize_image, path=path, filename=filename, new_width=2500)
info.context.background_tasks.add_task(generate_thumbnail, path=path, filename=filename)
exif_info = await parse_exif_info(path, filename) exif_info = await parse_exif_info(path, filename)
return await models.Photo.create(data={ async with get_session() as db:
"flight_id": input.flight_id, photo_model = await models.Photo.create(data={
"name": input.name, "flight_id": input.flight_id,
"filename": filename, "name": input.name,
"description": input.description, "filename": filename,
"exposed_at": exif_info.get("datetime"), "description": input.description,
"gps_latitude": exif_info.get("gps_latitude"), "exposed_at": exif_info.get("datetime"),
"gps_longitude": exif_info.get("gps_longitude"), "gps_latitude": exif_info.get("gps_latitude"),
"gps_altitude": exif_info.get("gps_altitude"), "gps_longitude": exif_info.get("gps_longitude"),
"is_flight_cover": False, "gps_altitude": exif_info.get("gps_altitude"),
"created_by_id": info.context.user_id, "is_flight_cover": False,
}, db_session=info.context.db) "created_by_id": info.context.user_id,
}, db_session=db)
photo = Photo(**photo_model.as_dict())
info.context.background_tasks.add_task(resize_image, path=path, filename=filename, new_width=2500, quality=85)
info.context.background_tasks.add_task(generate_thumbnail, path=path, filename=filename)
if exif_info.get("gps_latitude") and exif_info.get("gps_longitude"):
info.context.background_tasks.add_task(add_terrain_elevation, photo=photo)
return photo
@strawberry.type @strawberry.type
@@ -106,34 +117,36 @@ class EditPhotoMutation:
@authenticated_user_only() @authenticated_user_only()
async def edit_photo(self, info, id: int, input: EditPhotoInput) -> Photo: async def edit_photo(self, info, id: int, input: EditPhotoInput) -> Photo:
query = get_base_query(info.context.user_id) query = get_base_query(info.context.user_id)
photo = (await info.context.db.scalars(query.filter(models.Photo.id == id))).one()
data = { data = {
key: getattr(input, key) for key in ('name', 'description', 'is_flight_cover') key: getattr(input, key) for key in ('name', 'description', 'is_flight_cover')
if getattr(input, key) is not None if getattr(input, key) is not None
} }
if input.point_of_interest: async with get_session() as db:
data['point_of_interest_id'] = await handle_combobox_save( photo = (await db.scalars(query.filter(models.Photo.id == id))).one()
info.context.db,
models.PointOfInterest,
input.point_of_interest,
info.context.user_id,
extra_data={
"description": ""
}
)
updated_model = await models.Photo.update(info.context.db, obj=photo, data=data)
if input.is_flight_cover: if input.point_of_interest:
# reset other covers data['point_of_interest_id'] = await handle_combobox_save(
(await info.context.db.execute( db,
update(models.Photo) models.PointOfInterest,
.filter(models.Photo.flight_id == photo.flight_id) input.point_of_interest,
.filter(models.Photo.id != id).values(is_flight_cover=False)) info.context.user_id,
) extra_data={
"description": ""
}
)
return updated_model if input.is_flight_cover:
# reset other covers
(await db.execute(
update(models.Photo)
.filter(models.Photo.flight_id == photo.flight_id)
.filter(models.Photo.id != id).values(is_flight_cover=False))
)
updated_model = await models.Photo.update(db, obj=photo, data=data)
return Photo(**updated_model.as_dict())
@strawberry.type @strawberry.type
@@ -142,7 +155,10 @@ class DeletePhotoMutation:
@authenticated_user_only() @authenticated_user_only()
async def delete_photo(self, info, id: int) -> Photo: async def delete_photo(self, info, id: int) -> Photo:
query = get_base_query(info.context.user_id) query = get_base_query(info.context.user_id)
photo = (await info.context.db.scalars(query.filter(models.Photo.id == id))).one() async with get_session() as db:
photo_model = (await db.scalars(query.filter(models.Photo.id == id))).one()
await db.delete(photo_model)
photo = Photo(**photo_model.as_dict())
base_path = get_photo_basepath(photo.flight_id) base_path = get_photo_basepath(photo.flight_id)
try: try:
@@ -151,6 +167,4 @@ class DeletePhotoMutation:
except Exception as e: except Exception as e:
print(e) print(e)
await info.context.db.delete(photo) return photo_model
return photo
+34 -33
View File
@@ -3,6 +3,7 @@ import strawberry
from sqlalchemy import select, or_ from sqlalchemy import select, or_
from database import models from database import models
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.dataloaders.flight import flight_by_poi_dataloader from graphql_schema.dataloaders.flight import flight_by_poi_dataloader
from graphql_schema.dataloaders.photos import poi_photos_dataloader from graphql_schema.dataloaders.photos import poi_photos_dataloader
from graphql_schema.dataloaders.poi import poi_type_dataloader from graphql_schema.dataloaders.poi import poi_type_dataloader
@@ -15,6 +16,7 @@ if TYPE_CHECKING:
from .flight import Flight from .flight import Flight
from .photo import Photo from .photo import Photo
@strawberry_sqlalchemy_type(models.PointOfInterest) @strawberry_sqlalchemy_type(models.PointOfInterest)
class PointOfInterest: class PointOfInterest:
async def load_photos(root): async def load_photos(root):
@@ -36,7 +38,6 @@ def get_base_query(user_id: int, only_my: bool = False):
select(models.PointOfInterest) select(models.PointOfInterest)
.filter(models.PointOfInterest.deleted.is_(False)) .filter(models.PointOfInterest.deleted.is_(False))
) )
if only_my: if only_my:
query = query.filter(models.PointOfInterest.created_by_id == user_id) query = query.filter(models.PointOfInterest.created_by_id == user_id)
else: else:
@@ -58,8 +59,9 @@ class PointOfInterestQueries:
get_base_query(info.context.user_id) get_base_query(info.context.user_id)
.order_by(models.PointOfInterest.id.desc()) .order_by(models.PointOfInterest.id.desc())
) )
async with get_session() as db:
return (await info.context.db.scalars(query)).all() pois = (await db.scalars(query)).all()
return [PointOfInterest(**poi.as_dict()) for poi in pois]
@strawberry.field() @strawberry.field()
@authenticated_user_only() @authenticated_user_only()
@@ -68,7 +70,9 @@ class PointOfInterestQueries:
get_base_query(info.context.user_id) get_base_query(info.context.user_id)
.filter(models.PointOfInterest.id == id) .filter(models.PointOfInterest.id == id)
) )
return (await info.context.db.scalars(query)).one() async with get_session() as db:
poi = (await db.scalars(query)).one()
return PointOfInterest(**poi.as_dict())
@strawberry.type @strawberry.type
@@ -82,21 +86,14 @@ class CreatePointOfInterestMutation:
async def create_point_of_interest(root, info, input: CreatePointOfInterestInput) -> PointOfInterest: async def create_point_of_interest(root, info, input: CreatePointOfInterestInput) -> PointOfInterest:
input_data = input.to_dict() input_data = input.to_dict()
if input.type: async with get_session() as db:
input_data['type_id'] = await handle_combobox_save( if input.type:
info.context.db, input_data['type_id'] = await handle_combobox_save(
models.PointOfInterestType, db, models.PointOfInterestType, input.type, info.context.user_id
input.type, )
info.context.user_id
)
return await models.PointOfInterest.create( poi = await models.PointOfInterest.create(db, data=dict(**input_data, created_by_id=info.context.user_id))
info.context.db, return PointOfInterest(**poi.as_dict())
data=dict(
**input_data,
created_by_id=info.context.user_id,
)
)
@strawberry.type @strawberry.type
@@ -111,20 +108,19 @@ class EditPointOfInterestMutation:
# TODO: kontrola organizace # TODO: kontrola organizace
input_data = input.to_dict() input_data = input.to_dict()
if input.type is not None: async with get_session() as db:
input_data['type_id'] = await handle_combobox_save( if input.type is not None:
info.context.db, input_data['type_id'] = await handle_combobox_save(
models.PointOfInterestType, db, models.PointOfInterestType, input.type, info.context.user_id
input.type, )
info.context.user_id
)
poi = ( poi = (
await info.context.db.scalars( await db.scalars(
get_base_query(info.context.user_id, only_my=True) get_base_query(info.context.user_id, only_my=True)
.filter(models.PointOfInterest.id == id)) .filter(models.PointOfInterest.id == id)
).one() )).one()
return await models.PointOfInterest.update(info.context.db, obj=poi, data=input_data) updated_poi = await models.PointOfInterest.update(db, obj=poi, data=input_data)
return PointOfInterest(**updated_poi.as_dict())
@strawberry.type @strawberry.type
@@ -133,6 +129,11 @@ class DeletePointOfInterestMutation:
@strawberry.mutation @strawberry.mutation
@authenticated_user_only() @authenticated_user_only()
async def delete_point_of_interest(self, info, id: int) -> PointOfInterest: async def delete_point_of_interest(self, info, id: int) -> PointOfInterest:
poi = get_base_query(info.context.user_id, only_my=True).filter(models.PointOfInterest.id == id).one() async with get_session() as db:
poi = (await db.scalars(
get_base_query(info.context.user_id, only_my=True)
.filter(models.PointOfInterest.id == id)
)).one()
return await models.PointOfInterest.update(info.context.db, obj=poi, data=dict(deleted=True)) updated_poi = await models.PointOfInterest.update(db, obj=poi, data=dict(deleted=True))
return PointOfInterest(**updated_poi.as_dict())
+7 -2
View File
@@ -3,6 +3,7 @@ import strawberry
from sqlalchemy import select, or_ from sqlalchemy import select, or_
from database import models from database import models
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from dependencies.db import get_session
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
from graphql_schema.types import ComboboxInput from graphql_schema.types import ComboboxInput
@@ -40,7 +41,9 @@ class PointOfInterestTypeQueries:
.order_by(models.PointOfInterestType.id.desc()) .order_by(models.PointOfInterestType.id.desc())
) )
return (await info.context.db.scalars(query)).all() async with get_session() as db:
poi_types = (await db.scalars(query)).all()
return [PointOfInterestType(**poi_type.as_dict()) for poi_type in poi_types]
@strawberry.field() @strawberry.field()
@authenticated_user_only() @authenticated_user_only()
@@ -49,7 +52,9 @@ class PointOfInterestTypeQueries:
get_base_query(info.context.user_id) get_base_query(info.context.user_id)
.filter(models.PointOfInterestType.id == id) .filter(models.PointOfInterestType.id == id)
) )
return (await info.context.db.scalars(query)).one() async with get_session() as db:
poi_type = (await db.scalars(query)).one()
return PointOfInterestType(**poi_type.as_dict())
# #
# @strawberry.type # @strawberry.type
+39 -31
View File
@@ -7,14 +7,14 @@ from sqlalchemy import select
from strawberry.file_uploads import Upload from strawberry.file_uploads import Upload
from config import API_URL from config import API_URL
from database import models from database import models
from database.models import User
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from decorators.error_logging import error_logging from decorators.error_logging import error_logging
from dependencies.db import get_session
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
from upload_utils import handle_file_upload, delete_file, get_public_url, resize_image from upload_utils import handle_file_upload, delete_file, get_public_url, resize_image
@strawberry_sqlalchemy_type(User, exclude_fields=['password_hashed']) @strawberry_sqlalchemy_type(models.User, exclude_fields=['password_hashed'])
class User: class User:
async def load_avatar_image_url(root): async def load_avatar_image_url(root):
if not root.avatar_image_filename: if not root.avatar_image_filename:
@@ -40,15 +40,22 @@ class UserQueries:
if len(username) == 0: if len(username) == 0:
raise GraphQLError("Username not set!") raise GraphQLError("Username not set!")
return (await info.context.db.scalars(select(models.User).filter_by(public_username=username))).one() async with get_session() as db:
user_model = (await db.scalars(select(models.User).filter_by(public_username=username))).one()
user = User(**user_model.as_dict())
return user
@strawberry.field() @strawberry.field()
@authenticated_user_only() @authenticated_user_only()
@error_logging @error_logging
async def logged_user(root, info) -> User: async def logged_user(root, info) -> User:
return (await info.context.db.scalars( async with get_session() as db:
select(models.User).filter_by(id=info.context.user_id) user_model = (await db.scalars(
)).one() select(models.User).filter_by(id=info.context.user_id)
)).one()
return User(**user_model.as_dict())
@strawberry.type @strawberry.type
@@ -66,35 +73,36 @@ class EditUserMutation:
@strawberry.mutation @strawberry.mutation
@authenticated_user_only() @authenticated_user_only()
async def edit_logged_user(root, info, input: EditUserInput) -> User: async def edit_logged_user(root, info, input: EditUserInput) -> User:
user = (await info.context.db.scalars( async with get_session() as db:
select(models.User).filter_by(id=info.context.user_id) user = (await db.scalars(
)).one() select(models.User).filter_by(id=info.context.user_id)
)).one()
user_image_path = f"/app/uploads/profile/{user.id}" user_image_path = f"/app/uploads/profile/{user.id}"
data = input.to_dict() data = input.to_dict()
if input.avatar_image: if input.avatar_image:
if user.avatar_image_filename: if user.avatar_image_filename:
delete_file(f"{user_image_path}/{user.avatar_image_filename}", silent=True) delete_file(f"{user_image_path}/{user.avatar_image_filename}", silent=True)
data['avatar_image_filename'] = await handle_file_upload(input.avatar_image, user_image_path) data['avatar_image_filename'] = await handle_file_upload(input.avatar_image, user_image_path)
info.context.background_tasks.add_task( info.context.background_tasks.add_task(
resize_image, path=user_image_path, filename=data['avatar_image_filename'], new_width=400 resize_image, path=user_image_path, filename=data['avatar_image_filename'], new_width=400
) )
if input.title_image: if input.title_image:
if user.title_image_filename: if user.title_image_filename:
delete_file(f"{user_image_path}/{user.title_image_filename}", silent=True) delete_file(f"{user_image_path}/{user.title_image_filename}", silent=True)
data['title_image_filename'] = await handle_file_upload(input.title_image, user_image_path) data['title_image_filename'] = await handle_file_upload(input.title_image, user_image_path)
info.context.background_tasks.add_task(
resize_image, path=user_image_path, filename=data['title_image_filename'], new_width=800
)
info.context.background_tasks.add_task( if input.old_password and input.new_password:
resize_image, path=user_image_path, filename=data['title_image_filename'], new_width=800 if not bcrypt.verify(input.old_password, user.password_hashed):
) raise GraphQLError("Bad password")
if input.old_password and input.new_password: data['password_hashed'] = bcrypt.hash(input.new_password)
if not bcrypt.verify(input.old_password, user.password_hashed):
raise GraphQLError("Bad password")
data['password_hashed'] = bcrypt.hash(input.new_password) user_model = await models.User.update(db, obj=user, data=data)
return User(**user_model.as_dict())
return await models.User.update(info.context.db, obj=user, data=data)
+2
View File
@@ -3,6 +3,7 @@ from .entities.aircraft import AircraftQueries
from .entities.airport import AirportQueries from .entities.airport import AirportQueries
from .entities.copilot import CopilotQueries from .entities.copilot import CopilotQueries
from .entities.flight import FlightQueries from .entities.flight import FlightQueries
from .entities.photo import PhotoQueries
from .entities.poi import PointOfInterestQueries from .entities.poi import PointOfInterestQueries
from .entities.poi_type import PointOfInterestTypeQueries from .entities.poi_type import PointOfInterestTypeQueries
from .entities.user import UserQueries from .entities.user import UserQueries
@@ -16,6 +17,7 @@ Query = merge_types('Query', (
FlightQueries, FlightQueries,
CopilotQueries, CopilotQueries,
UserQueries, UserQueries,
PhotoQueries,
PointOfInterestQueries, PointOfInterestQueries,
PointOfInterestTypeQueries, PointOfInterestTypeQueries,
)) ))
-1
View File
@@ -30,7 +30,6 @@ class LoggingExtension(SchemaExtension):
@dataclasses.dataclass @dataclasses.dataclass
class GraphQLContext(BaseContext): class GraphQLContext(BaseContext):
db: AsyncSession
user_id: int user_id: int
jwt_auth_credentials: JwtAuthorizationCredentials jwt_auth_credentials: JwtAuthorizationCredentials
jwt: JwtAccessBearerCookie jwt: JwtAccessBearerCookie
@@ -31,8 +31,8 @@ def strawberry_sqlalchemy_type(model, exclude_fields: Optional[typing.Union[List
if exclude_fields is None: if exclude_fields is None:
exclude_fields = [] exclude_fields = []
def from_sqlalchemy_model(model: BaseModel): def from_sqlalchemy_model(cls, model: BaseModel):
return model return cls(model)
def wrapper(cls): def wrapper(cls):
cls.__annotations__.update(get_annotations_for_scalars(model, exclude_fields=exclude_fields + ["deleted"])) cls.__annotations__.update(get_annotations_for_scalars(model, exclude_fields=exclude_fields + ["deleted"]))
+1 -5
View File
@@ -57,12 +57,8 @@ class App:
app.mount("/static", StaticFiles(directory="/app/static"), name="static") app.mount("/static", StaticFiles(directory="/app/static"), name="static")
def setup_graphql_endpoint(self, app: FastAPI): def setup_graphql_endpoint(self, app: FastAPI):
def setup_graphql_context( def setup_graphql_context(credentials: JwtAuthorizationCredentials = Security(self.access_security)):
credentials: JwtAuthorizationCredentials = Security(self.access_security),
db: AsyncSession = Depends(db_session)
):
return GraphQLContext( return GraphQLContext(
db=db,
user_id=credentials['id'] if credentials else None, user_id=credentials['id'] if credentials else None,
jwt_auth_credentials=credentials, jwt_auth_credentials=credentials,
jwt=self.access_security, jwt=self.access_security,
View File
+72
View File
@@ -0,0 +1,72 @@
import asyncio
import sys
from sqlalchemy import select
sys.path.insert(0, "/app/src")
from database import async_session, models
from external.elevation import elevation_api
from external.gpx_parser import GPXParser
async def add_elevation_to_photos():
async with async_session() as session:
photos = (await session.scalars(
select(models.Photo)
.filter(models.Photo.terrain_elevation.is_(None))
)).all()
coordinates = [{"lat": p.gps_latitude, "lng": p.gps_longitude} for p in photos if p.gps_latitude or p.gps_longitude]
photos_by_corrdinates = {(p.gps_latitude, p.gps_longitude): p for p in photos}
if not coordinates:
print("all done")
return
points = await elevation_api.get_elevation_for_points(coordinates)
for point in points:
photo = photos_by_corrdinates[point['lat'], point['lng']]
await models.Photo.update(db_session=session, obj=photo, data={"terrain_elevation": point['elevation']})
await session.flush()
await session.commit()
async def add_elevation_to_tracks():
async with async_session() as session:
flights = (await session.scalars(
select(models.Flight)
.filter(models.Flight.has_terrain_elevation == False)
.filter(models.Flight.gpx_track_filename.isnot(None))
)).all()
if not flights:
print("all done")
return
for flight in flights:
gpx_file = f"/app/uploads/tracks/{flight.gpx_track_filename}"
gpx = GPXParser(gpx_file)
coordinates = await gpx.get_coordinates()
elevation = await elevation_api.get_elevation_for_points(coordinates)
gpx_with_elevation = gpx.add_terrain_elevation(elevation)
output_name = f"terrain_{flight.gpx_track_filename[30:]}"
gpx.write(gpx_with_elevation, output=f"/app/uploads/tracks/{output_name}")
await models.Flight.update(
db_session=session, obj=flight, data={
"has_terrain_elevation": True,
"gpx_track_filename": output_name
}
)
await session.flush()
await session.commit()
async def run_all():
await asyncio.gather(add_elevation_to_photos(), add_elevation_to_tracks())
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(run_all())