From ffe45a821d0fcc4e7b83e02b21c52c9f90e469c5 Mon Sep 17 00:00:00 2001 From: Olly Hearn Date: Sun, 14 Apr 2024 20:28:21 +0300 Subject: [PATCH] clientid works --- backend/app/views/auth/api.py | 11 +++++-- backend/app/views/auth/schemas.py | 8 ++++++ backend/app/views/auth/services.py | 35 ++++++++++++++++++++--- backend/app/views/queue/api.py | 7 +++++ backend/app/views/queue/schemas.py | 9 ++++++ backend/app/views/queue/services.py | 31 ++++++++++++++++++++ frontend/app/src/components/AuthModal.tsx | 12 +++++++- frontend/app/src/config/store.ts | 14 +++++++++ frontend/app/src/pages/AppRoutes.tsx | 8 +++++- frontend/app/src/slice/AuthApi.ts | 15 ++++++++-- 10 files changed, 140 insertions(+), 10 deletions(-) diff --git a/backend/app/views/auth/api.py b/backend/app/views/auth/api.py index d2b4a03..245ec59 100644 --- a/backend/app/views/auth/api.py +++ b/backend/app/views/auth/api.py @@ -63,8 +63,15 @@ async def register( return user -@router.get("/me", response_model=schemas.User) +@router.get("/me") async def read_users_me( current_user: Annotated[schemas.User, Depends(services.get_current_active_user)], -): +) -> schemas.User: return current_user + + +@router.get("/anon") +async def get_qnon_user( + anon_user: Annotated[schemas.AnonUser, Depends(services.get_anon_user)] +) -> schemas.AnonUser: + return anon_user diff --git a/backend/app/views/auth/schemas.py b/backend/app/views/auth/schemas.py index 1774fec..b613003 100644 --- a/backend/app/views/auth/schemas.py +++ b/backend/app/views/auth/schemas.py @@ -27,3 +27,11 @@ class Token(BaseModel): class TokenData(BaseModel): username: Union[str, None] = None + + +class AnonUser(BaseModel): + id: UUID + name: Union[str, None] = None + + class Config: + from_attributes = True diff --git a/backend/app/views/auth/services.py b/backend/app/views/auth/services.py index c4ebf3e..dfe93fa 100644 --- a/backend/app/views/auth/services.py +++ b/backend/app/views/auth/services.py @@ -1,4 +1,4 @@ -from fastapi import status, HTTPException, Depends +from fastapi import status, HTTPException, Depends, Header from fastapi.security import OAuth2PasswordBearer from sqlalchemy.orm import Session from jose import JWTError, jwt @@ -65,7 +65,7 @@ def create_user(db: Session, user_data: schemas.UserRegister) -> schemas.UserInD return schemas.UserInDB.model_validate(user) -async def get_current_user( +def get_current_user( token: Annotated[str, Depends(oauth2_scheme)], db: Annotated[Session, Depends(get_db)], ) -> schemas.UserInDB: @@ -90,7 +90,7 @@ async def get_current_user( return user -async def get_current_user_or_none( +def get_current_user_or_none( token: Annotated[str, Depends(oauth2_scheme)], db: Annotated[Session, Depends(get_db)], ) -> Union[schemas.UserInDB, None]: @@ -108,9 +108,36 @@ async def get_current_user_or_none( return user -async def get_current_active_user( +def get_current_active_user( current_user: Annotated[schemas.User, Depends(get_current_user)], ): if not current_user.is_active: raise HTTPException(status_code=400, detail="Inactive user") return current_user + + +def create_anon_user(db: Annotated[Session, Depends(get_db)]) -> schemas.AnonUser: + u = models.AnonymousUser() + db.add(u) + db.commit() + # return schemas.AnonUser.model_validate(u) + return u + + +def get_anon_user( + db: Annotated[Session, Depends(get_db)], + x_client_id: Annotated[Union[str, None], Header()] = None, +) -> schemas.AnonUser: + if x_client_id: + anon = ( + db.query(models.AnonymousUser) + .filter(models.AnonymousUser.id == x_client_id) + .first() + ) + if anon: + return anon + raise HTTPException( + status_code=status.HTTP_418_IM_A_TEAPOT, + detail={"message": "tf dude? trying to spoof your client id?"}, + ) + return create_anon_user(db) diff --git a/backend/app/views/queue/api.py b/backend/app/views/queue/api.py index 3ed6d53..419f8e3 100644 --- a/backend/app/views/queue/api.py +++ b/backend/app/views/queue/api.py @@ -46,3 +46,10 @@ async def create_queue( db: Annotated[Session, Depends(get_db)], ) -> schemas.QueueInDb: return services.create_queue(new_queue=new_queue, current_user=current_user, db=db) + + +@router.post("/{queue_id}/join") +async def join_queue( + queue_user: Annotated[schemas.QueueUser, Depends(services.join_queue)] +) -> schemas.QueueUser: + return queue_user diff --git a/backend/app/views/queue/schemas.py b/backend/app/views/queue/schemas.py index 918584b..e4479d4 100644 --- a/backend/app/views/queue/schemas.py +++ b/backend/app/views/queue/schemas.py @@ -30,3 +30,12 @@ class QueueInDb(Queue): class QueueDetail(Queue): id: UUID participants: ParticipantInfo + + +class QueueUser(BaseModel): + id: UUID + position: int + passed: bool + + class Config: + from_attributes = True diff --git a/backend/app/views/queue/services.py b/backend/app/views/queue/services.py index 0afcbb3..0e01883 100644 --- a/backend/app/views/queue/services.py +++ b/backend/app/views/queue/services.py @@ -12,6 +12,11 @@ from ..auth import schemas as auth_schemas from . import schemas +def get_queue_by_id(queue_id: UUID, db: Session) -> models.Queue: + q = db.query(models.Queue).filter(models.Queue.id == queue_id).first() + return q + + def get_user_queues( current_user: Annotated[auth_schemas.User, Depends(auth_services.get_current_user)] ) -> list[schemas.QueueInDb]: @@ -51,3 +56,29 @@ def get_detailed_queue( status_code=status.HTTP_404_NOT_FOUND, detail="Not Found", ) + + +def join_queue( + queue_id: UUID, + client: Annotated[auth_schemas.AnonUser, Depends(auth_services.get_anon_user)], + db: Annotated[Session, Depends(get_db)], +) -> schemas.QueueUser: + q = get_queue_by_id(queue_id, db) + if q: + if not q.users.filter(models.QueueUser.user_id == client.id).first(): + last_qu = q.users.order_by(models.QueueUser.position.desc()).first() + position = last_qu.position + 1 if last_qu else 0 + new_qu = models.QueueUser( + user_id=client.id, queue_id=q.id, position=position + ) + db.add(new_qu) + db.commit() + return new_qu + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="Already joined", + ) + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Not Found", + ) diff --git a/frontend/app/src/components/AuthModal.tsx b/frontend/app/src/components/AuthModal.tsx index f99738e..e43d614 100644 --- a/frontend/app/src/components/AuthModal.tsx +++ b/frontend/app/src/components/AuthModal.tsx @@ -7,12 +7,13 @@ import { import React, { useContext, useEffect, useRef, useState } from "react"; import { TokenResponse, + useGetClientQuery, useGetUserQuery, useLoginMutation, useRegisterMutation, } from "../slice/AuthApi"; import { MessageContext } from "../App"; -import { store, updateToken, updateUser } from "../config/store"; +import { store, updateClient, updateToken, updateUser } from "../config/store"; import tr from "../config/translation"; import { useNavigate } from "react-router-dom"; @@ -35,6 +36,15 @@ const AuthModal = (props: { } }, [data, isFetching, useGetUserQuery]); + const { data: clientData, isFetching: isFetchingClient } = useGetClientQuery( + {} + ); + useEffect(() => { + if (!isFetchingClient) { + store.dispatch(updateClient(clientData.id)); + } + }, [clientData, isFetchingClient, useGetClientQuery]); + const [current, setCurrent] = useState("login"); useEffect(() => { if (carousel?.current !== undefined) { diff --git a/frontend/app/src/config/store.ts b/frontend/app/src/config/store.ts index 9c99828..516f325 100644 --- a/frontend/app/src/config/store.ts +++ b/frontend/app/src/config/store.ts @@ -11,11 +11,13 @@ import { NewsApi } from "../slice/NewsApi"; export type AuthDataType = { token: string | null; + clientId: string | null; user: { name: string | null; username: string } | null; }; const initialAuthDataState: AuthDataType = { token: null, + clientId: null, user: null, }; @@ -35,6 +37,8 @@ export type StorePrototype = { export const updateToken = createAction("auth/updateToken"); export const getLocalToken = createAction("auth/getLocalToken"); +export const updateClient = createAction("auth/updateClient"); +export const getLocalClient = createAction("auth/getLocalClient"); export const updateUser = createAction("auth/updateUser"); export const logOut = createAction("auth/logOut"); @@ -58,6 +62,16 @@ export const store = configureStore({ state.token = token; } }); + builder.addCase(updateClient, (state, action) => { + state.clientId = action.payload; + localStorage.setItem("clientId", action.payload); + }); + builder.addCase(getLocalClient, (state) => { + const clientId: string | null = localStorage.getItem("clientId"); + if (clientId) { + state.clientId = clientId; + } + }); builder.addCase(updateUser, (state, action) => { state.user = action.payload; }); diff --git a/frontend/app/src/pages/AppRoutes.tsx b/frontend/app/src/pages/AppRoutes.tsx index 99e30e1..b07c733 100644 --- a/frontend/app/src/pages/AppRoutes.tsx +++ b/frontend/app/src/pages/AppRoutes.tsx @@ -1,7 +1,12 @@ import React, { ReactNode } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import MainPage from "./MainPage"; -import { getLocalToken, loadLanguage, store } from "../config/store"; +import { + getLocalClient, + getLocalToken, + loadLanguage, + store, +} from "../config/store"; import DashboardPage from "./DashboardPage"; import PropTypes from "prop-types"; import NotFoundPage from "./NotFoundPage"; @@ -12,6 +17,7 @@ import QueueCard from "../components/queue/QueueCard"; const AppRoutes = ({ children }: { children: ReactNode }) => { store.dispatch(getLocalToken()); + store.dispatch(getLocalClient()); store.dispatch(loadLanguage()); return ( diff --git a/frontend/app/src/slice/AuthApi.ts b/frontend/app/src/slice/AuthApi.ts index 52cb5ca..d583d58 100644 --- a/frontend/app/src/slice/AuthApi.ts +++ b/frontend/app/src/slice/AuthApi.ts @@ -34,6 +34,10 @@ export const AuthApi = createApi({ if (token) { headers.set("authorization", `Bearer ${token}`); } + const clientID = (getState() as RootState).auth.clientId; + if (clientID) { + headers.set("X-Client-Id", clientID); + } return headers; }, }), @@ -41,6 +45,9 @@ export const AuthApi = createApi({ getUser: builder.query({ query: () => "/me", }), + getClient: builder.query({ + query: () => "/anon", + }), login: builder.mutation({ query: (data: FormData) => ({ url: "/token", @@ -59,5 +66,9 @@ export const AuthApi = createApi({ }), }); -export const { useGetUserQuery, useLoginMutation, useRegisterMutation } = - AuthApi; +export const { + useGetUserQuery, + useGetClientQuery, + useLoginMutation, + useRegisterMutation, +} = AuthApi;