Uprava struktury letu a fotky, uprava GPS pozic v DB

This commit is contained in:
Michal Kváček
2023-07-03 13:23:59 +02:00
parent ba5c3b6a49
commit f8245492b9
18 changed files with 277 additions and 77 deletions
-3
View File
@@ -1,12 +1,9 @@
import asyncio import asyncio
from logging.config import fileConfig from logging.config import fileConfig
from sqlalchemy import pool from sqlalchemy import pool
from sqlalchemy.engine import Connection from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context from alembic import context
import os, sys import os, sys
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -7,7 +7,6 @@ Create Date: 2023-05-06 20:57:49.053470
""" """
from alembic import op from alembic import op
import sqlalchemy as sa import sqlalchemy as sa
import database.custom_types
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = 'de421195d45c' revision = 'de421195d45c'
@@ -22,7 +21,7 @@ def upgrade() -> None:
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=128), nullable=False), sa.Column('name', sa.String(length=128), nullable=False),
sa.Column('icao_code', sa.String(length=4), nullable=False), sa.Column('icao_code', sa.String(length=4), nullable=False),
sa.Column('gps_position', database.custom_types.Point(), nullable=True), sa.Column('gps_position', sa.Integer(), nullable=True),
sa.Column('elevation', sa.Integer(), nullable=True), sa.Column('elevation', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False), sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
sa.PrimaryKeyConstraint('id') sa.PrimaryKeyConstraint('id')
@@ -94,7 +93,7 @@ def upgrade() -> None:
op.create_table('point_of_interest', op.create_table('point_of_interest',
sa.Column('id', sa.Integer(), nullable=False), sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=128), nullable=False), sa.Column('name', sa.String(length=128), nullable=False),
sa.Column('gps_position', database.custom_types.Point(), nullable=True), sa.Column('gps_position', sa.Integer(), nullable=True),
sa.Column('type_id', sa.Integer(), nullable=False), sa.Column('type_id', sa.Integer(), nullable=False),
sa.Column('is_public', sa.Boolean(), server_default='0', 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_by_id', sa.Integer(), nullable=False),
@@ -155,7 +154,7 @@ def upgrade() -> None:
sa.Column('name', sa.String(length=128), nullable=False), sa.Column('name', sa.String(length=128), nullable=False),
sa.Column('filename', sa.String(length=128), nullable=False), sa.Column('filename', sa.String(length=128), nullable=False),
sa.Column('description', sa.Text(), nullable=False), sa.Column('description', sa.Text(), nullable=False),
sa.Column('gps_position', database.custom_types.Point(), nullable=True), sa.Column('gps_position', sa.Integer(), nullable=True),
sa.Column('flight_id', sa.Integer(), nullable=False), sa.Column('flight_id', sa.Integer(), nullable=False),
sa.Column('created_by_id', sa.Integer(), 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('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
@@ -0,0 +1,30 @@
"""flight cover photo
Revision ID: 7304ac2f580b
Revises: 419cf23cbf83
Create Date: 2023-06-29 07:35:54.303608
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7304ac2f580b'
down_revision = '419cf23cbf83'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('flight', sa.Column('cover_photo_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'flight', 'photo', ['cover_photo_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'flight', type_='foreignkey')
op.drop_column('flight', 'cover_photo_id')
# ### end Alembic commands ###
@@ -0,0 +1,56 @@
"""zmena v gps, pridani poi_id do photo
Revision ID: ef51b2cd596a
Revises: 7304ac2f580b
Create Date: 2023-06-29 08:21:24.755261
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'ef51b2cd596a'
down_revision = '7304ac2f580b'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('airport', sa.Column('gps_latitude', sa.Float(), nullable=True))
op.add_column('airport', sa.Column('gps_longitude', sa.Float(), nullable=True))
op.drop_column('airport', 'gps_position')
op.add_column('flight_track', sa.Column('point_of_interest_id', sa.Integer(), nullable=False))
op.drop_constraint('flight_track_ibfk_2', 'flight_track', type_='foreignkey')
op.create_foreign_key(None, 'flight_track', 'point_of_interest', ['point_of_interest_id'], ['id'])
op.drop_column('flight_track', 'poi_id')
op.add_column('photo', sa.Column('gps_latitude', sa.Float(), nullable=True))
op.add_column('photo', sa.Column('gps_longitude', sa.Float(), nullable=True))
op.add_column('photo', sa.Column('poin_of_interest_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'photo', 'point_of_interest', ['poin_of_interest_id'], ['id'])
op.drop_column('photo', 'gps_position')
op.add_column('point_of_interest', sa.Column('gps_latitude', sa.Float(), nullable=True))
op.add_column('point_of_interest', sa.Column('gps_longitude', sa.Float(), nullable=True))
op.drop_column('point_of_interest', 'gps_position')
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('point_of_interest', sa.Column('gps_position', sa.NullType(), nullable=True))
op.drop_column('point_of_interest', 'gps_longitude')
op.drop_column('point_of_interest', 'gps_latitude')
op.add_column('photo', sa.Column('gps_position', sa.NullType(), nullable=True))
op.drop_constraint(None, 'photo', type_='foreignkey')
op.drop_column('photo', 'poin_of_interest_id')
op.drop_column('photo', 'gps_longitude')
op.drop_column('photo', 'gps_latitude')
op.add_column('flight_track', sa.Column('poi_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=False))
op.drop_constraint(None, 'flight_track', type_='foreignkey')
op.create_foreign_key('flight_track_ibfk_2', 'flight_track', 'point_of_interest', ['poi_id'], ['id'])
op.drop_column('flight_track', 'point_of_interest_id')
op.add_column('airport', sa.Column('gps_position', sa.NullType(), nullable=True))
op.drop_column('airport', 'gps_longitude')
op.drop_column('airport', 'gps_latitude')
# ### end Alembic commands ###
@@ -0,0 +1,34 @@
"""fix typo
Revision ID: 7dc6dd7d0f59
Revises: ef51b2cd596a
Create Date: 2023-06-29 15:43:44.520087
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '7dc6dd7d0f59'
down_revision = 'ef51b2cd596a'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('photo', sa.Column('point_of_interest_id', sa.Integer(), nullable=True))
op.drop_constraint('photo_ibfk_3', 'photo', type_='foreignkey')
op.create_foreign_key(None, 'photo', 'point_of_interest', ['point_of_interest_id'], ['id'])
op.drop_column('photo', 'poin_of_interest_id')
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('photo', sa.Column('poin_of_interest_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True))
op.drop_constraint(None, 'photo', type_='foreignkey')
op.create_foreign_key('photo_ibfk_3', 'photo', 'point_of_interest', ['poin_of_interest_id'], ['id'])
op.drop_column('photo', 'point_of_interest_id')
# ### end Alembic commands ###
@@ -0,0 +1,36 @@
"""uprava cover photo u letu, aircraft.call_sign
Revision ID: f19757a615cc
Revises: 7dc6dd7d0f59
Create Date: 2023-06-29 16:03:51.185228
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = 'f19757a615cc'
down_revision = '7dc6dd7d0f59'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('aircraft', sa.Column('call_sign', sa.String(length=16), nullable=False))
op.drop_column('aircraft', 'name')
op.drop_constraint('flight_ibfk_6', 'flight', type_='foreignkey')
op.drop_column('flight', 'cover_photo_id')
op.add_column('photo', sa.Column('is_flight_cover', sa.Boolean(), server_default='0', nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('photo', 'is_flight_cover')
op.add_column('flight', sa.Column('cover_photo_id', mysql.INTEGER(display_width=11), autoincrement=False, nullable=True))
op.create_foreign_key('flight_ibfk_6', 'flight', 'photo', ['cover_photo_id'], ['id'])
op.add_column('aircraft', sa.Column('name', mysql.VARCHAR(length=128), nullable=False))
op.drop_column('aircraft', 'call_sign')
# ### end Alembic commands ###
-13
View File
@@ -1,13 +0,0 @@
from sqlalchemy import func
from sqlalchemy.types import UserDefinedType
class Point(UserDefinedType):
def get_col_spec(self):
return 'POINT'
def bind_expression(self, bindvalue):
return func.ST_GeomFromText(bindvalue, type_=self)
def column_expression(self, col):
return func.ST_AsText(col, type_=self)
+17 -12
View File
@@ -1,9 +1,8 @@
from __future__ import annotations from __future__ import annotations
import datetime import datetime
from typing import Set, List from typing import Set, List
from sqlalchemy import String, DateTime, ForeignKey, Text, Integer, func, Table, Column, Boolean, select from sqlalchemy import String, DateTime, ForeignKey, Text, Integer, func, Table, Column, Boolean, select, Float
from sqlalchemy.orm import Mapped, relationship, as_declarative, mapped_column from sqlalchemy.orm import Mapped, relationship, as_declarative, mapped_column
from database.custom_types import Point
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@@ -65,7 +64,8 @@ class Airport(BaseModel):
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)
icao_code: Mapped[str] = mapped_column(String(4), nullable=False) icao_code: Mapped[str] = mapped_column(String(4), nullable=False)
gps_position: Mapped[Point] = mapped_column(Point, nullable=True) gps_latitude: Mapped[float] = mapped_column(Float, nullable=True)
gps_longitude: Mapped[float] = mapped_column(Float, nullable=True)
elevation: Mapped[int] = mapped_column(Integer, nullable=True) elevation: Mapped[int] = mapped_column(Integer, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0') deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
@@ -91,7 +91,8 @@ class PointOfInterest(BaseModel):
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)
gps_position: Mapped[Point] = mapped_column(Point, nullable=True) gps_latitude: Mapped[float] = mapped_column(Float, nullable=True)
gps_longitude: Mapped[float] = mapped_column(Float, nullable=True)
type_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest_type.id")) type_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest_type.id"))
is_public: Mapped[bool] = mapped_column(Boolean, server_default='0') is_public: Mapped[bool] = mapped_column(Boolean, server_default='0')
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id')) created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'))
@@ -108,13 +109,17 @@ class Photo(BaseModel):
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)
filename: Mapped[str] = mapped_column(String(128), nullable=False) filename: Mapped[str] = mapped_column(String(128), nullable=False)
is_flight_cover: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="0")
description: Mapped[str] = mapped_column(Text, nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False)
gps_position: Mapped[Point] = mapped_column(Point, nullable=True) gps_latitude: Mapped[float] = mapped_column(Float, nullable=True)
gps_longitude: Mapped[float] = mapped_column(Float, 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'))
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
flight: Mapped['Flight'] = relationship() flight: Mapped['Flight'] = relationship(foreign_keys=[flight_id])
point_of_interest: Mapped['PointOfInterest'] = relationship()
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
@@ -122,13 +127,13 @@ class Aircraft(BaseModel):
__tablename__ = "aircraft" __tablename__ = "aircraft"
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) call_sign: Mapped[str] = mapped_column(String(16), nullable=False)
photo_filename: Mapped[str] = mapped_column(String(128), nullable=True) photo_filename: Mapped[str] = mapped_column(String(128), nullable=True)
manufacturer: Mapped[str] = mapped_column(Text, nullable=False, server_default="") manufacturer: Mapped[str] = mapped_column(Text, nullable=False, server_default="")
model: Mapped[str] = mapped_column(String(30), nullable=False) model: Mapped[str] = mapped_column(String(30), nullable=False, server_default="")
description: Mapped[str] = mapped_column(Text, nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False, server_default="")
organization_id: Mapped[int] = mapped_column(Integer, ForeignKey('organization.id'), nullable=True) organization_id: Mapped[int] = mapped_column(Integer, ForeignKey('organization.id'), nullable=True)
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id')) created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0') deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
@@ -171,7 +176,7 @@ class FlightTrack(BaseModel):
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=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)
poi_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest.id"), nullable=False) point_of_interest_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest.id"), nullable=False)
order: Mapped[int] = mapped_column(Integer) order: Mapped[int] = mapped_column(Integer)
flight: Mapped['Flight'] = relationship() flight: Mapped['Flight'] = relationship()
@@ -205,7 +210,7 @@ class Flight(BaseModel):
landing_airport: Mapped['Airport'] = relationship(foreign_keys=[landing_airport_id]) landing_airport: Mapped['Airport'] = relationship(foreign_keys=[landing_airport_id])
copilot: Mapped['Copilot'] = relationship(back_populates="flights") copilot: Mapped['Copilot'] = relationship(back_populates="flights")
aircraft: Mapped['Aircraft'] = relationship(back_populates="flights") aircraft: Mapped['Aircraft'] = relationship(back_populates="flights")
photos: Mapped[List['Photo']] = relationship() photos: Mapped[List['Photo']] = relationship(foreign_keys=[Photo.flight_id])
user: Mapped['User'] = relationship(back_populates="flights") user: Mapped['User'] = relationship(back_populates="flights")
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
+1 -1
View File
@@ -5,4 +5,4 @@ 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.commit() await session.commit()
-17
View File
@@ -1,17 +0,0 @@
from sqlalchemy import select, desc
from database.models import Flight
from endpoints.base import BaseEndpoint
class FlightsEndpoint(BaseEndpoint):
async def resolve(self):
self.db.add(Flight(name="test"))
await self.db.flush()
data = await self.db.execute(select(Flight).order_by(desc(Flight.id)))
model = data.scalars().first()
return {
"status": model
}
+34
View File
@@ -0,0 +1,34 @@
from collections import defaultdict
from typing import List
from sqlalchemy import select
from strawberry.dataloader import DataLoader
from database import async_session
from database.models import Photo
async def load_collection(ids: List[int]):
async with async_session() as session:
models = (await session.scalars(select(Photo).filter(Photo.flight_id.in_(ids)))).all()
photos_by_flight_id = defaultdict(list)
for photo in models:
photos_by_flight_id[photo.flight_id].append(photo)
return [photos_by_flight_id[id_] for id_ in ids]
async def load(ids: List[int]):
async with async_session() as session:
models = (
await session.scalars(
select(Photo)
.filter(Photo.is_flight_cover.is_(True))
.filter(Photo.flight_id.in_(ids)))
).all()
photos = {p.id: p for p in models}
return [photos.get(id_) for id_ in ids]
photos_dataloader = DataLoader(load_fn=load_collection, cache=False)
cover_photo_loader = DataLoader(load_fn=load)
+2 -6
View File
@@ -1,20 +1,17 @@
import uuid
from typing import List, Optional from typing import List, Optional
import strawberry import strawberry
from strawberry.file_uploads import Upload from strawberry.file_uploads import Upload
from sqlalchemy import select from sqlalchemy import select
from database import models from database import models
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 from upload_utils import handle_file_upload, delete_file, get_public_url
AIRCRAFT_UPLOAD_DEST_PATH = "/app/uploads/aircrafts/" AIRCRAFT_UPLOAD_DEST_PATH = "/app/uploads/aircrafts/"
@strawberry_sqlalchemy_type(models.Aircraft) @strawberry_sqlalchemy_type(models.Aircraft)
class Aircraft: class Aircraft:
photo_url: Optional[str] = strawberry.field( photo_url: Optional[str] = strawberry.field(resolver=lambda root: get_public_url(root.photo_filename))
resolver=lambda root: f"http://localhost:8000/uploads/{root.photo_filename}" if root.photo_filename else None
)
def get_base_query(user_id: int): def get_base_query(user_id: int):
@@ -52,7 +49,6 @@ class CreateAircraftMutation:
class CreateAircraftInput: class CreateAircraftInput:
photo: Optional[Upload] photo: Optional[Upload]
@strawberry.mutation @strawberry.mutation
async def create_aircraft(root, info, input: CreateAircraftInput) -> Aircraft: async def create_aircraft(root, info, input: CreateAircraftInput) -> Aircraft:
# TODO: kontrola organizace # TODO: kontrola organizace
+13 -5
View File
@@ -2,21 +2,21 @@ from datetime import timedelta
from typing import List, Optional from typing import List, Optional
import strawberry import strawberry
from sqlalchemy import select from sqlalchemy import select
from strawberry.file_uploads import Upload
from database import models from database import models
from graphql_schema.dataloaders import copilots_dataloader from graphql_schema.dataloaders import copilots_dataloader
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
from graphql_schema.dataloaders.photos import photos_dataloader, cover_photo_loader
from graphql_schema.entities.aircraft import Aircraft from graphql_schema.entities.aircraft import Aircraft
from graphql_schema.entities.airport import Airport from graphql_schema.entities.airport import Airport
from graphql_schema.entities.copilot import CopilotType from graphql_schema.entities.copilot import CopilotType
from graphql_schema.entities.photo import Photo
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
# Bude se hodit: https://strawberry.rocks/docs/types/lazy # Bude se hodit: https://strawberry.rocks/docs/types/lazy
@strawberry_sqlalchemy_type(models.Flight) @strawberry_sqlalchemy_type(models.Flight)
class Flight: class Flight:
async def load_takeoff_airport(root): async def load_takeoff_airport(root):
@@ -31,6 +31,12 @@ class Flight:
async def load_copilot(root): async def load_copilot(root):
return await copilots_dataloader.load(root.copilot_id) return await copilots_dataloader.load(root.copilot_id)
async def load_photos(root):
return await photos_dataloader.load(root.id)
async def load_cover_photo(root):
return await cover_photo_loader.load(root.id)
def duration_min_calculated(root): def duration_min_calculated(root):
if root.duration_total: if root.duration_total:
return root.duration_total return root.duration_total
@@ -46,6 +52,9 @@ class Flight:
aircraft: Aircraft = strawberry.field(resolver=load_aircraft) aircraft: Aircraft = strawberry.field(resolver=load_aircraft)
takeoff_airport: Airport = strawberry.field(resolver=load_takeoff_airport) takeoff_airport: Airport = strawberry.field(resolver=load_takeoff_airport)
landing_airport: Airport = strawberry.field(resolver=load_landing_airport) landing_airport: Airport = strawberry.field(resolver=load_landing_airport)
cover_photo: Optional[Photo] = strawberry.field(resolver=load_cover_photo)
photos: List[Photo] = strawberry.field(resolver=load_photos)
def get_base_query(user_id: int): def get_base_query(user_id: int):
@@ -113,7 +122,6 @@ class CreateFlightMutation:
@strawberry.type @strawberry.type
class EditFlightMutation: class EditFlightMutation:
@strawberry_sqlalchemy_input(models.Flight, exclude_fields=["id"], all_optional=True) @strawberry_sqlalchemy_input(models.Flight, exclude_fields=["id"], all_optional=True)
class EditFlightInput: class EditFlightInput:
pass pass
@@ -122,4 +130,4 @@ class EditFlightMutation:
async def edit_flight(self, info, id: int, input: EditFlightInput) -> Flight: async def edit_flight(self, info, id: int, input: EditFlightInput) -> Flight:
input_data = input.to_dict() input_data = input.to_dict()
return await models.Flight.update(info.context.db, id=id, data=input_data) return await models.Flight.update(info.context.db, id=id, data=input_data)
+37 -8
View File
@@ -1,13 +1,16 @@
from typing import List, Optional from typing import List
import strawberry import strawberry
from sqlalchemy import select from sqlalchemy import select
from strawberry.file_uploads import Upload
from database import models from database import models
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 get_public_url, handle_file_upload
@strawberry_sqlalchemy_type(models.Photo) @strawberry_sqlalchemy_type(models.Photo)
class Photo: class Photo:
pass url: str = strawberry.field(resolver=lambda root: get_public_url(root.filename))
thumbnail_url: str = strawberry.field(resolver=lambda root: get_public_url(f"thumbs/{root.filename}"))
def get_base_query(user_id: int): def get_base_query(user_id: int):
@@ -27,18 +30,37 @@ class PhotoQueries:
@strawberry.type @strawberry.type
class DeletePhotoMutation: class UploadPhotoMutation:
@strawberry_sqlalchemy_input(models.Photo, exclude_fields=["id", "filename"])
class UploadPhotoInput:
photo: Upload
@strawberry.input @strawberry.mutation
class DeletePhotoInput: async def upload_photo(self, info, input: UploadPhotoInput) -> Photo:
id: int photos_dest = f"/app/uploads/photos/{input.flight_id}/"
filename = await handle_file_upload(input.photo, photos_dest)
# todo: udelat nahled do thumbs slozky
created_photo = await models.Photo.create(data={
"flight_id": input.flight_id,
"name": input.name,
"filename": filename,
"description": input.description,
"created_by_id": info.context.user_id,
}, db_session=info.context.db)
return created_photo
@strawberry.type
class EditPhotoMutation:
@strawberry_sqlalchemy_input(models.Photo, exclude_fields=[], all_optional=True) @strawberry_sqlalchemy_input(models.Photo, exclude_fields=[], all_optional=True)
class UpdatePhotoInput: class EditPhotoInput:
pass pass
@strawberry.mutation @strawberry.mutation
async def update_photo(self, info, input: UpdatePhotoInput) -> Photo: async def update_photo(self, info, input: EditPhotoInput) -> Photo:
query = get_base_query(info.context.user_id) query = get_base_query(info.context.user_id)
photo = info.context.db.scalars(query.filter(models.Photo.id == input.id)) photo = info.context.db.scalars(query.filter(models.Photo.id == input.id))
@@ -47,6 +69,13 @@ class DeletePhotoMutation:
return photo return photo
@strawberry.type
class DeletePhotoMutation:
@strawberry.input
class DeletePhotoInput:
id: int
@strawberry.mutation @strawberry.mutation
async def delete_photo(self, info, input: DeletePhotoInput) -> Photo: async def delete_photo(self, info, input: DeletePhotoInput) -> Photo:
pass pass
+4
View File
@@ -1,6 +1,7 @@
from strawberry.tools import merge_types from strawberry.tools import merge_types
from graphql_schema.entities.aircraft import CreateAircraftMutation, EditAircraftMutation, DeleteAircraftMutation from graphql_schema.entities.aircraft import CreateAircraftMutation, EditAircraftMutation, DeleteAircraftMutation
from graphql_schema.entities.flight import CreateFlightMutation, EditFlightMutation from graphql_schema.entities.flight import CreateFlightMutation, EditFlightMutation
from graphql_schema.entities.photo import UploadPhotoMutation, DeletePhotoMutation, EditPhotoMutation
Mutation = merge_types("Mutation", ( Mutation = merge_types("Mutation", (
CreateAircraftMutation, CreateAircraftMutation,
@@ -8,4 +9,7 @@ Mutation = merge_types("Mutation", (
DeleteAircraftMutation, DeleteAircraftMutation,
EditFlightMutation, EditFlightMutation,
CreateFlightMutation, CreateFlightMutation,
UploadPhotoMutation,
EditPhotoMutation,
DeletePhotoMutation
)) ))
@@ -51,7 +51,11 @@ def strawberry_sqlalchemy_input(
ignored_fields = exclude_fields + ["created_at", "created_by_id", "updated_by_id", "updated_at", "deleted"] ignored_fields = exclude_fields + ["created_at", "created_by_id", "updated_by_id", "updated_at", "deleted"]
def to_dict(self): def to_dict(self):
return {name: getattr(self, name) for name, _ in get_columns_from_model(model, ignored_fields) if getattr(self, name) is not None} return {
name: getattr(self, name)
for name, _ in get_columns_from_model(model, ignored_fields)
if getattr(self, name) is not None
}
def wrapper(cls): def wrapper(cls):
annotations = get_annotations_for_scalars( annotations = get_annotations_for_scalars(
@@ -65,10 +69,8 @@ def strawberry_sqlalchemy_input(
for col, col_type in annotations.items(): for col, col_type in annotations.items():
try: try:
print("AAAAAAAAAA", col_type)
if col_type._name == 'Optional': # noqa if col_type._name == 'Optional': # noqa
setattr(cls, col, None) setattr(cls, col, None)
print(" nastavuji")
except AttributeError: except AttributeError:
pass pass
-5
View File
@@ -8,7 +8,6 @@ from strawberry.fastapi import GraphQLRouter
from config import APP_SECRET_KEY, GRAPHIQL, APP_DEBUG from config import APP_SECRET_KEY, GRAPHIQL, APP_DEBUG
from dependencies.db import db_session from dependencies.db import db_session
from endpoints.init_data import InitDataEndpoint from endpoints.init_data import InitDataEndpoint
from endpoints.flights import FlightsEndpoint
from endpoints.login import LoginEndpoint, LoginInput, MeEndpoint from endpoints.login import LoginEndpoint, LoginInput, MeEndpoint
from endpoints.registration import RegistrationInput, RegistrationEndpoint from endpoints.registration import RegistrationInput, RegistrationEndpoint
from graphql_schema.schema import schema, GraphQLContext from graphql_schema.schema import schema, GraphQLContext
@@ -119,9 +118,5 @@ class App:
async def init_data(db: AsyncSession = Depends(db_session)): async def init_data(db: AsyncSession = Depends(db_session)):
return await InitDataEndpoint(db).on_get() return await InitDataEndpoint(db).on_get()
@self.api_router.get("/flights", status_code=200)
async def flights(db: AsyncSession = Depends(db_session)):
return await FlightsEndpoint(db).resolve()
# musi byt na konci # musi byt na konci
app.include_router(self.api_router) app.include_router(self.api_router)
+5
View File
@@ -1,9 +1,14 @@
import os import os
import uuid import uuid
from typing import Optional
from strawberry.file_uploads import Upload from strawberry.file_uploads import Upload
def get_public_url(filename: Optional[str]):
return f"http://localhost:8000/uploads/{filename}" if filename else None
def check_directories(path: str): def check_directories(path: str):
if not os.path.isdir(path): if not os.path.isdir(path):
os.makedirs(path) os.makedirs(path)