Prvni pokus o verejny pristup, sprava profilu
This commit is contained in:
@@ -45,8 +45,6 @@ class BaseModel:
|
||||
return obj
|
||||
|
||||
|
||||
# TODO: doplnit GPX k letu
|
||||
|
||||
user_is_in_organization = Table(
|
||||
"user_is_in_organization",
|
||||
BaseModel.metadata,
|
||||
@@ -219,7 +217,7 @@ class Flight(BaseModel):
|
||||
weather_info_takeoff_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True)
|
||||
weather_info_landing_id: Mapped[int] = mapped_column(Integer, ForeignKey('weather_info.id'), nullable=True)
|
||||
landings: Mapped[int] = mapped_column(Integer, default=1)
|
||||
|
||||
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')
|
||||
@@ -282,7 +280,10 @@ class User(BaseModel):
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
email: Mapped[str] = mapped_column(String(128), nullable=False, unique=True)
|
||||
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
avatar_image_url: Mapped[str] = mapped_column(String(128), nullable=True)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
public_username: Mapped[str] = mapped_column(String(128), nullable=True, unique=True)
|
||||
avatar_image_filename: Mapped[str] = mapped_column(String(128), nullable=True)
|
||||
title_image_filename: Mapped[str] = mapped_column(String(128), nullable=True)
|
||||
password_hashed: Mapped[str] = mapped_column(String(60), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
from database.models import User, Flight, Copilot, Airport, Aircraft
|
||||
from endpoints.base import BaseEndpoint
|
||||
|
||||
|
||||
class InitDataEndpoint(BaseEndpoint):
|
||||
|
||||
async def on_get(self):
|
||||
user_ids = []
|
||||
copilot_ids = []
|
||||
flight_ids = []
|
||||
|
||||
users = [
|
||||
User(avatar_image_url="", name="Karel Vomacka", email="a@test.cz", password_hashed="****"),
|
||||
User(avatar_image_url="", name="Karel Novak", email="b@test.cz", password_hashed="****"),
|
||||
User(avatar_image_url="", name="Franta Pavel", email="c@test.cz", password_hashed="****"),
|
||||
]
|
||||
|
||||
airport = Airport(name="Letiste Letnany", icao_code="LKLT")
|
||||
self.db.add(airport)
|
||||
|
||||
for user in users:
|
||||
self.db.add(user)
|
||||
await self.db.flush()
|
||||
user_ids.append(user.id)
|
||||
|
||||
aircraft = Aircraft(name="OK-AUR28", type="Bristell NG5", description="", created_by=user)
|
||||
self.db.add(aircraft)
|
||||
|
||||
copilot = None
|
||||
if user.name == 'Franta Pavel':
|
||||
copilot = Copilot(name="Copilot test", created_by_id=user.id)
|
||||
self.db.add(copilot)
|
||||
await self.db.flush()
|
||||
|
||||
copilot_ids.append(copilot.id)
|
||||
|
||||
flight = Flight(name="test flight", description="Testovaci popis", duration_total=65, duration_pic=65,
|
||||
takeoff_airport=airport, landing_airport=airport, aircraft=aircraft, created_by_id=user.id,
|
||||
copilot_id=copilot.id if copilot else None)
|
||||
self.db.add(flight)
|
||||
await self.db.flush()
|
||||
flight_ids.append(flight.id)
|
||||
|
||||
return {
|
||||
"user_ids": user_ids,
|
||||
"copilot_ids": copilot_ids,
|
||||
"flight_ids": flight_ids,
|
||||
'airport_id': airport.id
|
||||
}
|
||||
|
||||
#
|
||||
#
|
||||
# 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
|
||||
# }
|
||||
@@ -4,7 +4,6 @@ from fastapi_jwt.jwt import JwtAccess, JwtRefresh
|
||||
from passlib.hash import bcrypt
|
||||
from sqlalchemy import select
|
||||
from starlette.responses import Response
|
||||
|
||||
from database.models import User
|
||||
from endpoints.base import BaseEndpoint
|
||||
from pydantic import BaseModel
|
||||
@@ -74,15 +73,3 @@ class RefreshEndpoint(BaseEndpoint):
|
||||
"access_token": access_token,
|
||||
"access_token_validity": self.access_security.access_expires_delta.total_seconds(),
|
||||
}
|
||||
|
||||
|
||||
class MeEndpoint(BaseEndpoint):
|
||||
|
||||
async def on_get(self, credentials) -> dict:
|
||||
if not credentials:
|
||||
raise HTTPException(status_code=401)
|
||||
|
||||
query = select(User).filter_by(id=credentials['id'])
|
||||
user = (await self.db.scalars(query)).first()
|
||||
|
||||
return user.as_dict()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import re
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import select
|
||||
from typing import Optional
|
||||
|
||||
@@ -15,7 +15,7 @@ from graphql_schema.entities.airport import Airport
|
||||
from graphql_schema.entities.photo import Photo
|
||||
from graphql_schema.entities.poi import PointOfInterest
|
||||
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input
|
||||
from upload_utils import get_public_url, handle_file_upload, check_directories, file_exists, delete_file
|
||||
from upload_utils import handle_file_upload, file_exists, delete_file
|
||||
from .helpers.flight import handle_aircraft_save, handle_track_edit, handle_copilot_edit, handle_weather_info
|
||||
from ..dataloaders.weather import airport_weather_info_loader
|
||||
from ..types import ComboboxInput
|
||||
@@ -95,28 +95,40 @@ class Flight:
|
||||
gpx_track_url: Optional[str] = strawberry.field(resolver=load_gpx_track_url)
|
||||
|
||||
|
||||
def get_base_query(user_id: int):
|
||||
return (
|
||||
def get_base_query(user_id: Optional[int], username: Optional[str] = None, is_auth: bool = False):
|
||||
query = (
|
||||
select(models.Flight)
|
||||
.filter(models.Flight.created_by_id == user_id)
|
||||
.filter(models.Flight.deleted.is_(False))
|
||||
.order_by(models.Flight.id.desc())
|
||||
)
|
||||
|
||||
if user_id:
|
||||
query = query.filter(models.Flight.created_by_id == user_id)
|
||||
|
||||
if username:
|
||||
query = query.filter(models.Flight.created_by.public_username == username)
|
||||
|
||||
if not is_auth:
|
||||
query = query.filter(models.Flight.is_public.is_(True))
|
||||
|
||||
return query
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class FlightQueries:
|
||||
|
||||
@strawberry.field
|
||||
async def flights(root, info) -> List[Flight]:
|
||||
query = get_base_query(info.context.user_id).order_by(models.Flight.id.desc())
|
||||
|
||||
async def flights(root, info, username: Optional[str] = None) -> List[Flight]:
|
||||
query = (
|
||||
get_base_query(user_id=info.context.user_id, username=username, is_auth=bool(info.context.user_id))
|
||||
.order_by(models.Flight.id.desc())
|
||||
)
|
||||
return (await info.context.db.scalars(query)).all()
|
||||
|
||||
@strawberry.field
|
||||
async def flight(root, info, id: int) -> Flight:
|
||||
async def flight(root, info, id: int, username: Optional[str] = None) -> Flight:
|
||||
query = (
|
||||
get_base_query(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.deleted.is_(False))
|
||||
)
|
||||
@@ -176,7 +188,7 @@ class EditFlightMutation:
|
||||
# TODO: umoznit editovat jen vlastni lety!
|
||||
|
||||
flight = (await info.context.db.scalars(
|
||||
get_base_query(info.context.user_id).filter(models.Flight.id == id)
|
||||
get_base_query(user_id=info.context.user_id, is_auth=bool(info.context.user_id)).filter(models.Flight.id == id)
|
||||
)).one()
|
||||
|
||||
data = input.to_dict()
|
||||
@@ -221,7 +233,7 @@ class DeleteFlightMutation:
|
||||
async def delete_flight(self, info, id: int) -> Flight:
|
||||
flight = (
|
||||
(await info.context.db.scalars(
|
||||
get_base_query(info.context.user_id)
|
||||
get_base_query(user_id=info.context.user_id, is_auth=True)
|
||||
.filter(models.Flight.id == id))
|
||||
)
|
||||
.one()
|
||||
|
||||
@@ -1,25 +1,90 @@
|
||||
from typing import Optional
|
||||
import strawberry
|
||||
from graphql import GraphQLError
|
||||
from passlib.hash import bcrypt
|
||||
from sqlalchemy import select
|
||||
from strawberry.file_uploads import Upload
|
||||
from database import models
|
||||
from database.models import User
|
||||
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type
|
||||
from graphql_schema.sqlalchemy_to_strawberry_type import strawberry_sqlalchemy_type, strawberry_sqlalchemy_input
|
||||
from upload_utils import handle_file_upload, delete_file
|
||||
|
||||
|
||||
@strawberry_sqlalchemy_type(User, exclude_fields=['password_hashed'])
|
||||
class UserType:
|
||||
pass
|
||||
class User:
|
||||
|
||||
async def load_avatar_image_url(root):
|
||||
if not root.avatar_image_filename:
|
||||
return None
|
||||
|
||||
@strawberry.type
|
||||
class LoginResultType:
|
||||
logged_user = UserType
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
return f"http://localhost:8000/uploads/profile/{root.id}/{root.avatar_image_filename}"
|
||||
|
||||
async def load_title_image_url(root):
|
||||
if not root.title_image_filename:
|
||||
return "http://localhost:8000/static/default-title-image.jpg"
|
||||
|
||||
return f"http://localhost:8000/uploads/profile/{root.id}/{root.title_image_filename}"
|
||||
|
||||
avatar_image_url: Optional[str] = strawberry.field(resolver=load_avatar_image_url)
|
||||
title_image_url: str = strawberry.field(resolver=load_title_image_url)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class UserQueries:
|
||||
@strawberry.field
|
||||
async def user(root, info, username: str) -> User:
|
||||
print("username")
|
||||
if len(username) == 0:
|
||||
raise GraphQLError("Username not set!")
|
||||
|
||||
return (await info.context.db.scalars(
|
||||
select(models.User).filter_by(public_username=username)
|
||||
)).one()
|
||||
|
||||
@strawberry.field
|
||||
async def logged_user(root, info) -> UserType:
|
||||
query = select(User).filter_by(id=info.context.user_id)
|
||||
return (await info.context.db.scalars(query)).first()
|
||||
async def logged_user(root, info) -> User:
|
||||
return (await info.context.db.scalars(
|
||||
select(models.User).filter_by(id=info.context.user_id)
|
||||
)).one()
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class EditUserMutation:
|
||||
@strawberry_sqlalchemy_input(
|
||||
models.User,
|
||||
exclude_fields=['id', 'email', 'avatar_image_filename', 'password_hashed', 'title_image_filename'],
|
||||
all_optional=True
|
||||
)
|
||||
class EditUserInput:
|
||||
old_password: Optional[str] = None
|
||||
new_password: Optional[str] = None
|
||||
avatar_image: Optional[Upload] = None
|
||||
title_image: Optional[Upload] = None
|
||||
|
||||
@strawberry.mutation
|
||||
async def edit_logged_user(root, info, input: EditUserInput) -> User:
|
||||
user = (await info.context.db.scalars(
|
||||
select(models.User).filter_by(id=info.context.user_id)
|
||||
)).one()
|
||||
|
||||
user_image_path = f"/app/uploads/profile/{user.id}"
|
||||
data = input.to_dict()
|
||||
if input.avatar_image:
|
||||
if user.avatar_image_filename:
|
||||
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)
|
||||
|
||||
if input.title_image:
|
||||
if user.title_image_filename:
|
||||
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)
|
||||
|
||||
if input.old_password and 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)
|
||||
|
||||
return await models.User.update(info.context.db, obj=user, data=data)
|
||||
|
||||
@@ -4,6 +4,7 @@ from graphql_schema.entities.copilot import CreateCopilotMutation, EditCopilotMu
|
||||
from graphql_schema.entities.flight import CreateFlightMutation, EditFlightMutation, DeleteFlightMutation
|
||||
from graphql_schema.entities.photo import UploadPhotoMutation, DeletePhotoMutation, EditPhotoMutation
|
||||
from graphql_schema.entities.poi import CreatePointOfInterestMutation, EditPointOfInterestMutation
|
||||
from graphql_schema.entities.user import EditUserMutation
|
||||
|
||||
Mutation = merge_types("Mutation", (
|
||||
CreateAircraftMutation,
|
||||
@@ -19,4 +20,5 @@ Mutation = merge_types("Mutation", (
|
||||
EditPointOfInterestMutation,
|
||||
CreateCopilotMutation,
|
||||
EditCopilotMutation,
|
||||
EditUserMutation,
|
||||
))
|
||||
|
||||
+9
-24
@@ -10,8 +10,7 @@ from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
from strawberry.fastapi import GraphQLRouter
|
||||
from config import APP_SECRET_KEY, GRAPHIQL, APP_DEBUG
|
||||
from dependencies.db import db_session
|
||||
from endpoints.init_data import InitDataEndpoint
|
||||
from endpoints.login import LoginEndpoint, LoginInput, MeEndpoint, RefreshEndpoint
|
||||
from endpoints.login import LoginEndpoint, LoginInput, RefreshEndpoint
|
||||
from endpoints.registration import RegistrationInput, RegistrationEndpoint
|
||||
from graphql_schema.schema import schema, GraphQLContext
|
||||
|
||||
@@ -33,6 +32,7 @@ class App:
|
||||
|
||||
self.setup_exception_handlers(app)
|
||||
self.setup_middleware(app)
|
||||
self.setup_static_paths(app)
|
||||
self.setup_routes(app)
|
||||
|
||||
return app
|
||||
@@ -43,15 +43,19 @@ class App:
|
||||
|
||||
@staticmethod
|
||||
def setup_middleware(app: FastAPI):
|
||||
print("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost:9001"],
|
||||
allow_origins=["http://localhost:9001"], # TODO: pridat pres ENV URL produkce
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def setup_static_paths(app: FastAPI):
|
||||
app.mount("/uploads", StaticFiles(directory="/app/uploads"), name="uploads")
|
||||
app.mount("/static", StaticFiles(directory="/app/static"), name="static")
|
||||
|
||||
def setup_graphql_endpoint(self, app: FastAPI):
|
||||
if APP_DEBUG:
|
||||
@self.api_router.get("/graphql/autologin")
|
||||
@@ -67,12 +71,9 @@ class App:
|
||||
credentials: JwtAuthorizationCredentials = Security(self.access_security),
|
||||
db: AsyncSession = Depends(db_session)
|
||||
):
|
||||
if not credentials:
|
||||
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED)
|
||||
|
||||
return GraphQLContext(
|
||||
jwt_auth_credentials=credentials,
|
||||
user_id=credentials['id'],
|
||||
user_id=credentials['id'] if credentials else None,
|
||||
db=db,
|
||||
jwt=self.access_security,
|
||||
background_tasks=Depends(BackgroundTasks)
|
||||
@@ -88,13 +89,6 @@ class App:
|
||||
|
||||
def setup_routes(self, app: FastAPI):
|
||||
# protected endpoints
|
||||
@self.api_router.get("/me")
|
||||
async def me(
|
||||
db: AsyncSession = Depends(db_session),
|
||||
credentials: JwtAuthorizationCredentials = Security(self.access_security)
|
||||
):
|
||||
return await MeEndpoint(db).on_get(credentials)
|
||||
|
||||
@app.post("/refresh")
|
||||
async def refresh(
|
||||
resp: Response,
|
||||
@@ -104,10 +98,7 @@ class App:
|
||||
|
||||
self.setup_graphql_endpoint(app)
|
||||
|
||||
app.mount("/uploads", StaticFiles(directory="/app/uploads"), name="uploads")
|
||||
|
||||
# public endpoints
|
||||
|
||||
@self.api_router.post("/login")
|
||||
async def login(resp: Response, user: LoginInput, db: AsyncSession = Depends(db_session)):
|
||||
return await LoginEndpoint(db, self.access_security, self.refresh_security).on_post(user, resp)
|
||||
@@ -116,11 +107,5 @@ class App:
|
||||
async def registration(user: RegistrationInput, db: AsyncSession = Depends(db_session)):
|
||||
return await RegistrationEndpoint(db).on_post(user)
|
||||
|
||||
# testing endpoint
|
||||
|
||||
@self.api_router.get("/init-data")
|
||||
async def init_data(db: AsyncSession = Depends(db_session)):
|
||||
return await InitDataEndpoint(db).on_get()
|
||||
|
||||
# musi byt na konci
|
||||
app.include_router(self.api_router)
|
||||
|
||||
Reference in New Issue
Block a user