Hromada uprav kvuli ssr renderu a verejne casti

This commit is contained in:
Michal Kváček
2023-12-04 22:45:44 +01:00
parent ecb37fb961
commit 3167ea2f8a
19 changed files with 334 additions and 90 deletions
@@ -0,0 +1,50 @@
"""add photos to aircraft, poi and copilots
Revision ID: 91a9608b509d
Revises: d42172840f6c
Create Date: 2023-11-28 16:32:56.725312
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '91a9608b509d'
down_revision = 'd42172840f6c'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('copilot_has_photo',
sa.Column('copilot_id', sa.Integer(), nullable=False),
sa.Column('photo_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['copilot_id'], ['copilot.id'], ),
sa.ForeignKeyConstraint(['photo_id'], ['photo.id'], ),
sa.PrimaryKeyConstraint('copilot_id', 'photo_id')
)
op.add_column('aircraft', sa.Column('title_photo_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'aircraft', 'photo', ['title_photo_id'], ['id'])
op.add_column('flight', sa.Column('title_photo_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'flight', 'photo', ['title_photo_id'], ['id'])
op.add_column('photo', sa.Column('aircraft_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'photo', 'aircraft', ['aircraft_id'], ['id'])
op.add_column('point_of_interest', sa.Column('title_photo_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'point_of_interest', 'photo', ['title_photo_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'point_of_interest', type_='foreignkey')
op.drop_column('point_of_interest', 'title_photo_id')
op.drop_constraint(None, 'photo', type_='foreignkey')
op.drop_column('photo', 'aircraft_id')
op.drop_constraint(None, 'flight', type_='foreignkey')
op.drop_column('flight', 'title_photo_id')
op.drop_constraint(None, 'aircraft', type_='foreignkey')
op.drop_column('aircraft', 'title_photo_id')
op.drop_table('copilot_has_photo')
# ### end Alembic commands ###
@@ -0,0 +1,28 @@
"""add is_public to aircraft
Revision ID: e5682ac4355c
Revises: 91a9608b509d
Create Date: 2023-12-02 12:14:25.055308
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e5682ac4355c'
down_revision = '91a9608b509d'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('aircraft', sa.Column('is_public', sa.Boolean(), server_default='0', nullable=False))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('aircraft', 'is_public')
# ### end Alembic commands ###
@@ -0,0 +1,30 @@
"""add title photo to copilot
Revision ID: 022bf6fbcd47
Revises: e5682ac4355c
Create Date: 2023-12-02 13:05:46.267006
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '022bf6fbcd47'
down_revision = 'e5682ac4355c'
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('copilot', sa.Column('title_photo_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'copilot', 'photo', ['title_photo_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'copilot', type_='foreignkey')
op.drop_column('copilot', 'title_photo_id')
# ### end Alembic commands ###
+1 -1
View File
@@ -21,7 +21,7 @@ services:
TZ: "Europe/Prague" TZ: "Europe/Prague"
API_URL: "http://localhost:8000" API_URL: "http://localhost:8000"
APP_ENV: "development" APP_ENV: "development"
ALLOW_CORS_ORIGINS: "http://localhost:9000 http://localhost:9001 http://localhost:3000" ALLOW_CORS_ORIGINS: "http://localhost:9000 http://localhost:9001 http://localhost:9101 http://localhost:3000"
SENTRY_DSN: "https://184d1d6bd39f4fbb94804ae84e9afdd5@o472821.ingest.sentry.io/5506983" SENTRY_DSN: "https://184d1d6bd39f4fbb94804ae84e9afdd5@o472821.ingest.sentry.io/5506983"
+29 -11
View File
@@ -57,6 +57,13 @@ flight_has_copilot = Table(
Column("copilot_id", ForeignKey("copilot.id"), primary_key=True), Column("copilot_id", ForeignKey("copilot.id"), primary_key=True),
) )
copilot_has_photo = Table(
"copilot_has_photo",
BaseModel.metadata,
Column("copilot_id", ForeignKey("copilot.id"), primary_key=True),
Column("photo_id", ForeignKey("photo.id"), primary_key=True),
)
class Airport(BaseModel): class Airport(BaseModel):
__tablename__ = "airport" __tablename__ = "airport"
@@ -72,7 +79,7 @@ class Airport(BaseModel):
created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'), nullable=True) # automaticky import nebude mit ID # noqa created_by_id: Mapped[int] = mapped_column(Integer, ForeignKey('user.id'), nullable=True) # automaticky import nebude mit ID # noqa
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0') deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
metars: Mapped['Metar'] = relationship(back_populates="airport") metars: Mapped['Metar'] = relationship()
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
@@ -97,6 +104,7 @@ 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)
description: Mapped[str] = mapped_column(Text, nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False)
title_photo_id: Mapped[int] = mapped_column(Integer, ForeignKey('photo.id'), nullable=True)
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)
type_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest_type.id"), nullable=True) type_id: Mapped[int] = mapped_column(Integer, ForeignKey("point_of_interest_type.id"), nullable=True)
@@ -105,9 +113,9 @@ class PointOfInterest(BaseModel):
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')
photos: Mapped[List[Photo]] = relationship()
type: Mapped[PointOfInterestType] = relationship() type: Mapped[PointOfInterestType] = relationship()
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
title_photo: Mapped['Photo'] = relationship(foreign_keys=[title_photo_id])
class Photo(BaseModel): class Photo(BaseModel):
@@ -116,7 +124,7 @@ 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") is_flight_cover: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default="0") # TODO: odstranit
description: Mapped[str] = mapped_column(Text, nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False)
width: Mapped[int] = mapped_column(Integer, nullable=False) width: Mapped[int] = mapped_column(Integer, nullable=False)
height: Mapped[int] = mapped_column(Integer, nullable=False) height: Mapped[int] = mapped_column(Integer, nullable=False)
@@ -125,15 +133,18 @@ class Photo(BaseModel):
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) terrain_elevation: Mapped[float] = mapped_column(Float, nullable=True)
aircraft_id: Mapped[int] = mapped_column(Integer, ForeignKey("aircraft.id"), 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'))
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(foreign_keys=[flight_id]) flight: Mapped['Flight'] = relationship(foreign_keys=[flight_id])
point_of_interest: Mapped['PointOfInterest'] = relationship() point_of_interest: Mapped['PointOfInterest'] = relationship(foreign_keys=[point_of_interest_id])
adjustment: Mapped['PhotoAdjustment'] = relationship(passive_deletes=True) adjustment: Mapped['PhotoAdjustment'] = relationship(passive_deletes=True)
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
aircraft: Mapped['Aircraft'] = relationship(foreign_keys=[aircraft_id])
copilots: Mapped[List['Copilot']] = relationship(secondary=copilot_has_photo)
class PhotoAdjustment(BaseModel): class PhotoAdjustment(BaseModel):
@@ -153,7 +164,6 @@ class PhotoAdjustment(BaseModel):
crop_height: Mapped[float] = mapped_column(Float, nullable=True) crop_height: Mapped[float] = mapped_column(Float, nullable=True)
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
photo: Mapped['Photo'] = relationship() photo: Mapped['Photo'] = relationship()
@@ -162,7 +172,9 @@ class Aircraft(BaseModel):
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
call_sign: Mapped[str] = mapped_column(String(16), 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) # TODO: odstranit
title_photo_id: Mapped[int] = mapped_column(Integer, ForeignKey('photo.id'), nullable=True)
is_public: Mapped[bool] = mapped_column(Boolean, server_default='0')
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, server_default="") model: Mapped[str] = mapped_column(String(30), nullable=False, server_default="")
seats: Mapped[str] = mapped_column(Integer, nullable=False) seats: Mapped[str] = mapped_column(Integer, nullable=False)
@@ -190,7 +202,7 @@ class Aircraft(BaseModel):
# created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) # created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
# #
# created_by: Mapped['User'] = relationship() # created_by: Mapped['User'] = relationship()
# aircraft: Mapped['Aircraft'] = relationship(back_populates="notes") # aircraft: Mapped['Aircraft'] = relationship()
class Organization(BaseModel): class Organization(BaseModel):
@@ -202,7 +214,7 @@ class Organization(BaseModel):
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')
users: Mapped[Set['User']] = relationship(back_populates='organizations', secondary=user_is_in_organization) users: Mapped[Set['User']] = relationship(secondary=user_is_in_organization)
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
@@ -261,6 +273,8 @@ class Flight(BaseModel):
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) event_id: Mapped[int] = mapped_column(Integer, ForeignKey("event.id"), nullable=True)
title_photo_id: Mapped[int] = mapped_column(Integer, ForeignKey('photo.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)
@@ -285,9 +299,10 @@ class Flight(BaseModel):
track: Mapped['FlightTrack'] = relationship() track: Mapped['FlightTrack'] = relationship()
event: Mapped['Event'] = 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()
photos: Mapped[List['Photo']] = relationship(foreign_keys=[Photo.flight_id]) photos: Mapped[List['Photo']] = relationship(foreign_keys=[Photo.flight_id])
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
title_photo: Mapped['Photo'] = relationship(foreign_keys=[title_photo_id])
class Copilot(BaseModel): class Copilot(BaseModel):
@@ -295,12 +310,15 @@ class Copilot(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)
title_photo_id: Mapped[int] = mapped_column(Integer, ForeignKey('photo.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'))
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')
flights: Mapped[Set['Flight']] = relationship(secondary=flight_has_copilot) flights: Mapped[Set['Flight']] = relationship(secondary=flight_has_copilot)
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
photos: Mapped[List['Photo']] = relationship(secondary=copilot_has_photo)
title_photo: Mapped['Photo'] = relationship(foreign_keys=[title_photo_id])
class Metar(BaseModel): class Metar(BaseModel):
@@ -312,7 +330,7 @@ class Metar(BaseModel):
issued_at: Mapped[datetime] = mapped_column(DateTime) issued_at: Mapped[datetime] = mapped_column(DateTime)
deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0') deleted: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default='0')
airport: Mapped['Airport'] = relationship(back_populates="metars") airport: Mapped['Airport'] = relationship()
class License(BaseModel): class License(BaseModel):
@@ -325,7 +343,7 @@ class License(BaseModel):
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())
user: Mapped['User'] = relationship(back_populates="licences") user: Mapped['User'] = relationship()
created_by: Mapped['User'] = relationship() created_by: Mapped['User'] = relationship()
+1 -6
View File
@@ -23,12 +23,7 @@ class QueryBuilder:
query = query.filter(self.model.deleted.is_(False)) query = query.filter(self.model.deleted.is_(False))
if hasattr(self.model, "created_by_id") and created_by_id: if hasattr(self.model, "created_by_id") and created_by_id:
ownership = [self.model.created_by_id == created_by_id] query = query.filter(self.model.created_by_id == created_by_id)
if hasattr(self.model, "is_public"):
ownership.append(self.model.is_public.is_(True))
query = query.filter(or_(*ownership))
if order_by: if order_by:
query = query.order_by(*order_by) query = query.order_by(*order_by)
@@ -110,3 +110,32 @@ flight_track_dataloader = DataLoader(
).load, ).load,
cache=False cache=False
) )
copilots_in_photo_dataloader = DataLoader(
load_fn=MultiModelsDataloader(
models.Copilot,
relationship_column=models.copilot_has_photo.c.photo_id,
extra_join=[models.copilot_has_photo],
order_by=[models.Copilot.name]
).load,
cache=False
)
photo_copilots_dataloader = DataLoader(
load_fn=MultiModelsDataloader(
models.Photo,
relationship_column=models.copilot_has_photo.c.copilot_id,
extra_join=[models.copilot_has_photo],
order_by=[models.Photo.exposed_at]
).load,
cache=False
)
photos_aircraft_dataloader = DataLoader(
load_fn=MultiModelsDataloader(
models.Photo,
relationship_column=models.Photo.aircraft_id,
order_by=[models.Photo.exposed_at]
).load,
cache=False
)
@@ -9,6 +9,7 @@ def create_dataloader(model: Type[models.BaseModel], relationship_column=None, f
return DataLoader(load_fn=loader, cache=False) return DataLoader(load_fn=loader, cache=False)
user_dataloader = create_dataloader(models.User)
airport_dataloader = create_dataloader(models.Airport) airport_dataloader = create_dataloader(models.Airport)
aircraft_dataloader = create_dataloader(models.Aircraft) aircraft_dataloader = create_dataloader(models.Aircraft)
event_dataloader = create_dataloader(models.Event) event_dataloader = create_dataloader(models.Event)
@@ -18,12 +19,6 @@ poi_dataloader = create_dataloader(models.PointOfInterest)
poi_type_dataloader = create_dataloader(models.PointOfInterestType) poi_type_dataloader = create_dataloader(models.PointOfInterestType)
flight_dataloader = create_dataloader(models.Flight) flight_dataloader = create_dataloader(models.Flight)
photo_adjustment_dataloader = create_dataloader( photo_adjustment_dataloader = create_dataloader(
models.PhotoAdjustment, models.PhotoAdjustment, relationship_column=models.PhotoAdjustment.photo_id
relationship_column=models.PhotoAdjustment.photo_id
)
photo_dataloader = create_dataloader(models.Photo)
cover_photo_loader = create_dataloader(
models.Photo,
relationship_column=models.Photo.flight_id,
filters=[models.Photo.is_flight_cover.is_(True)]
) )
photo_dataloader = create_dataloader(models.Photo)
+9 -4
View File
@@ -1,5 +1,8 @@
from typing import List from typing import List, Optional
import strawberry import strawberry
from fastapi import HTTPException
from starlette.status import HTTP_401_UNAUTHORIZED
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 .resolvers.aircraft import AircraftMutationResolver, AircraftQueryResolver from .resolvers.aircraft import AircraftMutationResolver, AircraftQueryResolver
@@ -20,10 +23,12 @@ class AircraftQueries:
@strawberry.field() @strawberry.field()
@error_logging @error_logging
@authenticated_user_only() async def aircraft(root, info, id: int, public: Optional[bool] = False) -> Aircraft:
async def aircraft(root, info, id: int) -> Aircraft: if not info.context.user_id and not public:
raise HTTPException(HTTP_401_UNAUTHORIZED)
return await AircraftQueryResolver().get_one( return await AircraftQueryResolver().get_one(
id, user_id=info.context.user_id, organization_ids=info.context.organization_ids id, user_id=info.context.user_id, organization_ids=info.context.organization_ids, public=public
) )
+21 -6
View File
@@ -19,19 +19,34 @@ class FlightQueries:
@strawberry.field() @strawberry.field()
@error_logging @error_logging
async def flights(root, info, username: Optional[str] = None) -> List[Flight]: async def flights(
if not info.context.user_id and not username: root, info,
username: Optional[str] = None,
public: Optional[bool] = False,
copilot_id: Optional[int] = None,
point_of_interest_id: Optional[int] = None,
aircraft_id: Optional[int] = None,
) -> List[Flight]:
if not info.context.user_id and not public:
raise HTTPException(HTTP_401_UNAUTHORIZED) raise HTTPException(HTTP_401_UNAUTHORIZED)
return await FlightQueryResolver().get_list(user_id=info.context.user_id, username=username) return await FlightQueryResolver().get_list(
user_id=info.context.user_id,
username=username,
only_public=public,
copilot_id=copilot_id,
aircraft_id=aircraft_id,
point_of_interest_id=point_of_interest_id
)
@strawberry.field() @strawberry.field()
@error_logging @error_logging
async def flight(root, info, id: int, username: Optional[str] = None) -> Flight: async def flight(root, info, id: int, username: Optional[str] = None, public: Optional[bool] = False) -> Flight:
if not info.context.user_id and not username: if not info.context.user_id and not public:
raise HTTPException(HTTP_401_UNAUTHORIZED) raise HTTPException(HTTP_401_UNAUTHORIZED)
return await FlightQueryResolver().get_one(id, user_id=info.context.user_id, username=username) return await FlightQueryResolver().get_one(id, user_id=info.context.user_id, username=username, public=public)
@strawberry.type @strawberry.type
+18 -4
View File
@@ -1,10 +1,10 @@
from typing import List from typing import List, Optional
import strawberry import strawberry
from database import models from database import models
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 graphql_schema.entities.resolvers.base import BaseQueryResolver from graphql_schema.entities.resolvers.base import BaseQueryResolver
from graphql_schema.entities.resolvers.photo import PhotoMutationResolver from graphql_schema.entities.resolvers.photo import PhotoMutationResolver, PhotoQueryResolver
from graphql_schema.entities.types.types import Photo from graphql_schema.entities.types.types import Photo
from graphql_schema.entities.types.mutation_input import EditPhotoInput, UploadPhotoInput, AdjustmentInput from graphql_schema.entities.types.mutation_input import EditPhotoInput, UploadPhotoInput, AdjustmentInput
@@ -13,8 +13,22 @@ from graphql_schema.entities.types.mutation_input import EditPhotoInput, UploadP
class PhotoQueries: class PhotoQueries:
@strawberry.field() @strawberry.field()
@error_logging @error_logging
async def photos(root, info) -> List[Photo]: async def photos(
return await BaseQueryResolver(Photo, models.Photo).get_list(user_id=info.context.user_id) root, info,
flight_id: Optional[int] = None,
copilot_id: Optional[int] = None,
point_of_interest_id: Optional[int] = None,
aircraft_id: Optional[int] = None,
public: Optional[bool] = False,
) -> List[Photo]:
return await PhotoQueryResolver().get_list(
public=public,
flight_id=flight_id,
user_id=info.context.user_id,
copilot_id=copilot_id,
aircraft_id=aircraft_id,
point_of_interest_id=point_of_interest_id,
)
@strawberry.field() @strawberry.field()
@error_logging @error_logging
+18 -6
View File
@@ -1,5 +1,7 @@
from typing import List from typing import List
import strawberry import strawberry
from fastapi import HTTPException
from starlette.status import HTTP_401_UNAUTHORIZED
from database import models from database import models
from decorators.endpoints import authenticated_user_only from decorators.endpoints import authenticated_user_only
from database.transaction import get_session from database.transaction import get_session
@@ -14,14 +16,24 @@ from graphql_schema.entities.types.mutation_input import CreatePointOfInterestIn
class PointOfInterestQueries: class PointOfInterestQueries:
@strawberry.field() @strawberry.field()
@error_logging @error_logging
@authenticated_user_only() async def points_of_interest(root, info, public: bool = False) -> List[PointOfInterest]:
async def points_of_interest(root, info) -> List[PointOfInterest]: if not info.context.user_id and not public:
return await BaseQueryResolver(PointOfInterest, models.PointOfInterest).get_list(info.context.user_id) raise HTTPException(HTTP_401_UNAUTHORIZED)
return await BaseQueryResolver(PointOfInterest, models.PointOfInterest).get_list(
info.context.user_id,
only_public=public
)
@strawberry.field() @strawberry.field()
@authenticated_user_only() async def point_of_interest(root, info, id: int, public: bool = False) -> PointOfInterest:
async def point_of_interest(root, info, id: int) -> PointOfInterest: if not info.context.user_id and not public:
return await BaseQueryResolver(PointOfInterest, models.PointOfInterest).get_one(id, info.context.user_id) raise HTTPException(HTTP_401_UNAUTHORIZED)
return await BaseQueryResolver(PointOfInterest, models.PointOfInterest).get_one(
id, info.context.user_id,
only_public=public
)
@strawberry.type @strawberry.type
@@ -48,9 +48,6 @@ class AircraftMutationResolver(BaseMutationResolver):
async def create_new(self, data: CreateAircraftInput, user_id: int) -> Aircraft: async def create_new(self, data: CreateAircraftInput, user_id: int) -> Aircraft:
input_data = data.to_dict() input_data = data.to_dict()
if data.photo:
input_data['photo_filename'] = await handle_file_upload(data.photo, AIRCRAFT_UPLOAD_DEST_PATH)
if data.organization: if data.organization:
async with get_session() as db: async with get_session() as db:
input_data['organization_id'] = await handle_combobox_save( input_data['organization_id'] = await handle_combobox_save(
@@ -64,13 +61,6 @@ class AircraftMutationResolver(BaseMutationResolver):
async def edit(self, id: int, user_id: int, data: EditAircraftInput) -> Aircraft: async def edit(self, id: int, user_id: int, data: EditAircraftInput) -> Aircraft:
update_data = data.to_dict() update_data = data.to_dict()
aircraft = await AircraftQueryResolver().get_one(id, user_id)
if data.photo:
if aircraft.photo_filename:
delete_file(AIRCRAFT_UPLOAD_DEST_PATH + "/" + aircraft.photo_filename, silent=True)
update_data['photo_filename'] = await handle_file_upload(data.photo, AIRCRAFT_UPLOAD_DEST_PATH)
async with get_session() as db: async with get_session() as db:
if data.organization: if data.organization:
update_data['organization_id'] = await handle_combobox_save( update_data['organization_id'] = await handle_combobox_save(
@@ -46,6 +46,9 @@ class BaseQueryResolver(BaseResolver):
if only_public and hasattr(self.model, "is_public"): if only_public and hasattr(self.model, "is_public"):
query = query.filter(self.model.is_public.is_(True)) query = query.filter(self.model.is_public.is_(True))
if kwargs:
query = query.filter_by(**kwargs)
return query return query
async def get_list(self, user_id: Optional[int] = None, **kwargs) -> List[GQL_TYPE]: async def get_list(self, user_id: Optional[int] = None, **kwargs) -> List[GQL_TYPE]:
@@ -16,12 +16,13 @@ class CopilotQueryResolver(BaseQueryResolver):
only_public: Optional[bool] = False, only_public: Optional[bool] = False,
*args, **kwargs *args, **kwargs
): ):
pilot_username = kwargs.pop("pilot_username", None)
query = super().get_query(user_id, object_id, order_by, only_public, *args, **kwargs) query = super().get_query(user_id, object_id, order_by, only_public, *args, **kwargs)
if kwargs.get("pilot_username"): if pilot_username:
query = ( query = (
query.join(models.Copilot.created_by) query.join(models.Copilot.created_by)
.filter(models.User.public_username == kwargs['pilot_username']) .filter(models.User.public_username == pilot_username)
) )
return query return query
@@ -21,17 +21,38 @@ class FlightQueryResolver(BaseQueryResolver):
def __init__(self): def __init__(self):
super().__init__(graphql_type=Flight, model=models.Flight) super().__init__(graphql_type=Flight, model=models.Flight)
def get_query(self, user_id: int, object_id: Optional[int] = None, *args, **kwargs): def get_query(
self,
user_id: Optional[int] = None,
object_id: Optional[int] = None,
only_public: Optional[bool] = False,
*args,
**kwargs
):
query = super().get_query( query = super().get_query(
user_id, object_id, user_id, object_id,
order_by=[models.Flight.takeoff_datetime.desc()], order_by=[models.Flight.takeoff_datetime.desc()],
only_public=not bool(user_id) only_public=only_public
) )
if kwargs.get("aircraft_id"):
query = query.filter(models.Flight.aircraft_id == kwargs['aircraft_id'])
if kwargs.get("copilot_id"):
query = (
query.join(models.flight_has_copilot)
.filter(models.flight_has_copilot.c.copilot_id == kwargs['copilot_id'])
)
if kwargs.get("point_of_interest_id"):
query = (
query.join(models.Flight.track)
.filter(models.FlightTrack.point_of_interest_id == kwargs["point_of_interest_id"])
)
if kwargs.get('username'): if kwargs.get('username'):
query = ( query = (
query query.join(models.Flight.created_by)
.join(models.Flight.created_by)
.filter(models.User.public_username == kwargs['username']) .filter(models.User.public_username == kwargs['username'])
) )
+46 -15
View File
@@ -1,15 +1,16 @@
import os import os
import shutil import shutil
from typing import Type, Optional, List
from PIL import Image from PIL import Image
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import update, delete from sqlalchemy import select, delete, insert
from sqlalchemy.ext.asyncio import AsyncSession
from background_jobs.elevation import add_terrain_elevation_to_photo from background_jobs.elevation import add_terrain_elevation_to_photo
from background_jobs.photo import generate_thumbnail, resize_photo from background_jobs.photo import generate_thumbnail, resize_photo
from database import models from database import models
from database.transaction import get_session from database.transaction import get_session
from graphql_schema.entities.helpers.combobox import handle_combobox_save from graphql_schema.entities.helpers.combobox import handle_combobox_save
from graphql_schema.entities.resolvers.base import BaseMutationResolver from graphql_schema.entities.resolvers.base import BaseMutationResolver, BaseQueryResolver, GQL_TYPE
from graphql_schema.entities.types.mutation_input import EditPhotoInput, UploadPhotoInput, AdjustmentInput from graphql_schema.entities.types.mutation_input import EditPhotoInput, UploadPhotoInput, AdjustmentInput
from graphql_schema.entities.types.types import Photo from graphql_schema.entities.types.types import Photo
from paths import get_photo_basepath from paths import get_photo_basepath
@@ -18,6 +19,40 @@ from utils.image import PhotoEditor, parse_exif_info
from utils.upload import handle_file_upload from utils.upload import handle_file_upload
class PhotoQueryResolver(BaseQueryResolver):
def __init__(self):
super().__init__(Photo, models.Photo)
def get_query(
self,
user_id: Optional[int] = None,
object_id: Optional[int] = None,
order_by: Optional[list] = None,
only_public: Optional[bool] = False,
*args, **kwargs
):
query = super().get_query(
user_id, object_id, order_by,
aircraft_id=kwargs.get("aircraft_id"),
point_of_interest_id=kwargs.get("point_of_interest_id")
)
if kwargs.get("public"):
query = (
query.join(models.Flight, onclause=models.Photo.flight_id == models.Flight.id)
.filter(models.Flight.is_public.is_(True))
)
if kwargs.get("copilot_id"):
query = (
query.join(models.copilot_has_photo, )
.filter(models.copilot_has_photo.c.copilot_id == kwargs['copilot_id'])
)
return query
class PhotoDetailInfo(BaseModel): class PhotoDetailInfo(BaseModel):
filename: str filename: str
original_filename: str original_filename: str
@@ -29,15 +64,6 @@ class PhotoMutationResolver(BaseMutationResolver):
def __init__(self): def __init__(self):
super().__init__(Photo, models.Photo) super().__init__(Photo, models.Photo)
@staticmethod
async def _reset_flight_cover(db: AsyncSession, flight_id: int, ignored_photo_id: int):
(await db.execute(
update(models.Photo)
.filter(models.Photo.flight_id == flight_id)
.filter(models.Photo.id != ignored_photo_id)
.values(is_flight_cover=False))
)
@staticmethod @staticmethod
def _copy_original(path: str, filename: str): def _copy_original(path: str, filename: str):
original_filename = "_original_" + filename original_filename = "_original_" + filename
@@ -105,9 +131,14 @@ class PhotoMutationResolver(BaseMutationResolver):
extra_data={"description": ""} extra_data={"description": ""}
) )
if input.is_flight_cover: if input.is_aircraft:
# reset other covers flight = (await db.scalars(select(models.Flight).filter(models.Flight.id == photo.flight_id))).one()
await self._reset_flight_cover(db, photo.flight_id, id) data['aircraft_id'] = flight.aircraft_id
if input.copilots is not None:
await db.execute(delete(models.copilot_has_photo).filter_by(photo_id=id))
for copilot in input.copilots:
await db.execute(insert(models.copilot_has_photo).values(photo_id=id, copilot_id=copilot.id))
return await self._do_update(db, obj=photo, data=data) return await self._do_update(db, obj=photo, data=data)
@@ -29,7 +29,7 @@ class CreatePointOfInterestInput(BaseGraphqlInputType):
type: Optional[ComboboxInput] = None type: Optional[ComboboxInput] = None
@strawberry_sqlalchemy_input(models.PointOfInterest, exclude_fields=['id', 'type_id']) @strawberry_sqlalchemy_input(models.PointOfInterest, exclude_fields=['id', 'type_id'], all_optional=True)
class EditPointOfInterestInput(BaseGraphqlInputType): class EditPointOfInterestInput(BaseGraphqlInputType):
type: Optional[ComboboxInput] = None type: Optional[ComboboxInput] = None
@@ -68,11 +68,12 @@ class EditPhotoInput:
name: Optional[str] = None name: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
point_of_interest: Optional[ComboboxInput] = None point_of_interest: Optional[ComboboxInput] = None
is_flight_cover: Optional[bool] = None is_aircraft: Optional[bool] = None
copilots: Optional[List[ComboboxInput]] = None
def to_dict(self): def to_dict(self):
return { return {
key: getattr(self, key) for key in ('name', 'description', 'is_flight_cover') key: getattr(self, key) for key in ('name', 'description')
if getattr(self, key) is not None if getattr(self, key) is not None
} }
@@ -128,11 +129,9 @@ class TrackItemInput:
@strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['id', 'photo_filename']) @strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['id', 'photo_filename'])
class CreateAircraftInput(BaseGraphqlInputType): class CreateAircraftInput(BaseGraphqlInputType):
photo: Optional[Upload]
organization: Optional[ComboboxInput] = None organization: Optional[ComboboxInput] = None
@strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['photo_filename']) @strawberry_sqlalchemy_input(models.Aircraft, exclude_fields=['photo_filename'], all_optional=True)
class EditAircraftInput(BaseGraphqlInputType): class EditAircraftInput(BaseGraphqlInputType):
photo: Optional[Upload]
organization: Optional[ComboboxInput] = None organization: Optional[ComboboxInput] = None
+15 -7
View File
@@ -10,12 +10,12 @@ from graphql_schema.dataloaders.multi_models import (
poi_photos_dataloader, flight_by_poi_dataloader, flight_copilots_dataloader, flight_track_dataloader, poi_photos_dataloader, flight_by_poi_dataloader, flight_copilots_dataloader, flight_track_dataloader,
photos_dataloader, flights_by_aircraft_dataloader, users_in_organization_dataloader, photos_dataloader, flights_by_aircraft_dataloader, users_in_organization_dataloader,
aircrafts_from_organization_dataloader, user_organizations_dataloader, flights_by_event_dataloader, aircrafts_from_organization_dataloader, user_organizations_dataloader, flights_by_event_dataloader,
flights_by_copilot_dataloader, public_flights_by_event_dataloader, public_flights_by_copilot_dataloader flights_by_copilot_dataloader, public_flights_by_event_dataloader, public_flights_by_copilot_dataloader, photo_copilots_dataloader, photos_aircraft_dataloader, copilots_in_photo_dataloader
) )
from graphql_schema.dataloaders.single_model import ( from graphql_schema.dataloaders.single_model import (
poi_dataloader, poi_type_dataloader, event_dataloader, aircraft_dataloader, airport_dataloader, cover_photo_loader, poi_dataloader, poi_type_dataloader, event_dataloader, aircraft_dataloader, airport_dataloader,
airport_weather_info_loader, organizations_dataloader, flight_dataloader, photo_adjustment_dataloader, airport_weather_info_loader, organizations_dataloader, flight_dataloader, photo_adjustment_dataloader,
photo_dataloader photo_dataloader, user_dataloader
) )
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
from paths import ( from paths import (
@@ -76,6 +76,7 @@ class PointOfInterest:
type: Optional[PointOfInterestType] = strawberry.field(resolver=lambda root: poi_type_dataloader.load(root.type_id)) 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)) 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)) flights: List[Flight] = strawberry.field(resolver=lambda root: flight_by_poi_dataloader.load(root.id))
title_photo: Optional[Photo] = strawberry.field(resolver=lambda root: photo_dataloader.load(root.title_photo_id))
@strawberry_sqlalchemy_type(models.Photo) @strawberry_sqlalchemy_type(models.Photo)
@@ -85,6 +86,8 @@ class Photo:
point_of_interest: Optional[PointOfInterest] = strawberry.field( point_of_interest: Optional[PointOfInterest] = strawberry.field(
resolver=lambda root: poi_dataloader.load(root.point_of_interest_id) resolver=lambda root: poi_dataloader.load(root.point_of_interest_id)
) )
copilots: List[Copilot] = strawberry.field(resolver=lambda root: copilots_in_photo_dataloader.load(root.id))
is_aircraft: bool = strawberry.field(resolver=lambda root: bool(root.aircraft_id))
flight: Flight = strawberry.field(resolver=lambda root: flight_dataloader.load(root.flight_id)) flight: Flight = strawberry.field(resolver=lambda root: flight_dataloader.load(root.flight_id))
adjustment: Optional[PhotoAdjustment] = strawberry.field( adjustment: Optional[PhotoAdjustment] = strawberry.field(
resolver=lambda root: photo_adjustment_dataloader.load(root.id) resolver=lambda root: photo_adjustment_dataloader.load(root.id)
@@ -123,12 +126,13 @@ class Flight:
async def load_event(root): async def load_event(root):
return await event_dataloader.load(root.event_id) return await event_dataloader.load(root.event_id)
pilot: User = strawberry.field(resolver=lambda root: user_dataloader.load(root.created_by_id))
copilots: Optional[List[Copilot]] = strawberry.field(resolver=load_copilots) copilots: Optional[List[Copilot]] = strawberry.field(resolver=load_copilots)
event: Optional[Event] = strawberry.field(resolver=load_event) event: Optional[Event] = strawberry.field(resolver=load_event)
aircraft: Aircraft = strawberry.field(resolver=lambda root: aircraft_dataloader.load(root.aircraft_id)) aircraft: Aircraft = strawberry.field(resolver=lambda root: aircraft_dataloader.load(root.aircraft_id))
takeoff_airport: Airport = strawberry.field(resolver=lambda root: airport_dataloader.load(root.takeoff_airport_id)) takeoff_airport: Airport = strawberry.field(resolver=lambda root: airport_dataloader.load(root.takeoff_airport_id))
landing_airport: Airport = strawberry.field(resolver=lambda root: airport_dataloader.load(root.landing_airport_id)) landing_airport: Airport = strawberry.field(resolver=lambda root: airport_dataloader.load(root.landing_airport_id))
cover_photo: Optional[Photo] = strawberry.field(resolver=lambda root: cover_photo_loader.load(root.id)) title_photo: Optional[Photo] = strawberry.field(resolver=lambda root: photo_dataloader.load(root.title_photo_id))
track: List[FlightTrack] = strawberry.field(resolver=lambda root: flight_track_dataloader.load(root.id)) track: List[FlightTrack] = strawberry.field(resolver=lambda root: flight_track_dataloader.load(root.id))
takeoff_weather_info: Optional[WeatherInfo] = strawberry.field( takeoff_weather_info: Optional[WeatherInfo] = strawberry.field(
resolver=lambda root: airport_weather_info_loader.load(root.takeoff_weather_info_id) resolver=lambda root: airport_weather_info_loader.load(root.takeoff_weather_info_id)
@@ -153,17 +157,21 @@ class Copilot:
return await dataloader.load(root.id) return await dataloader.load(root.id)
flights: List[Flight] = strawberry.field(resolver=resolve_flights) flights: List[Flight] = strawberry.field(resolver=resolve_flights)
photos: List[Photo] = strawberry.field(resolver=lambda root: photo_copilots_dataloader.load(root.id))
title_photo: Optional[Photo] = strawberry.field(resolver=lambda root: photo_dataloader.load(root.title_photo_id))
@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(f"aircrafts/{root.photo_filename}") if root.photo_filename else None # resolver=lambda root: get_public_url(f"aircrafts/{root.photo_filename}") if root.photo_filename else None
) # )
flights: List[Flight] = strawberry.field(resolver=lambda root: flights_by_aircraft_dataloader.load(root.id)) flights: List[Flight] = strawberry.field(resolver=lambda root: flights_by_aircraft_dataloader.load(root.id))
organization: Optional[Organization] = strawberry.field( organization: Optional[Organization] = strawberry.field(
resolver=lambda root: organizations_dataloader.load(root.organization_id) resolver=lambda root: organizations_dataloader.load(root.organization_id)
) )
photos: List[Photo] = strawberry.field(resolver=lambda root: photos_aircraft_dataloader.load(root.id))
title_photo: Optional[Photo] = strawberry.field(resolver=lambda root: photo_dataloader.load(root.title_photo_id))
@strawberry_sqlalchemy_type(models.Organization) @strawberry_sqlalchemy_type(models.Organization)