From a50fc6710470aa73a7228c2503794ecdf9c90f6e Mon Sep 17 00:00:00 2001 From: swve Date: Sun, 12 Nov 2023 23:16:34 +0100 Subject: [PATCH] feat: user init & refactors --- apps/api/app.py | 3 +- apps/api/src/core/events/database.py | 2 +- apps/api/src/{rewrite => db}/__init__.py | 0 .../{rewrite/services => }/db/activities.py | 0 .../src/{rewrite/services => }/db/blocks.py | 0 .../services => }/db/chapter_activities.py | 0 .../src/{rewrite/services => }/db/chapters.py | 0 .../{rewrite/services => }/db/collections.py | 0 .../services => }/db/course_authors.py | 0 .../services => }/db/course_chapters.py | 0 .../src/{rewrite/services => }/db/courses.py | 0 .../services => }/db/organization_settings.py | 0 .../services => }/db/organizations.py | 0 .../src/{rewrite/services => }/db/roles.py | 4 +- .../services => }/db/user_organizations.py | 0 .../src/{rewrite/services => }/db/users.py | 13 +- apps/api/src/rewrite/routers/users.py | 23 - apps/api/src/rewrite/services/db/__init__.py | 0 apps/api/src/rewrite/services/users/users.py | 43 -- apps/api/src/router.py | 5 - apps/api/src/routers/auth.py | 27 +- apps/api/src/routers/users.py | 121 +++-- apps/api/src/security/auth.py | 27 +- apps/api/src/services/dev/mocks/initial.py | 3 +- apps/api/src/services/users/users.py | 443 ++++++++---------- 25 files changed, 356 insertions(+), 358 deletions(-) rename apps/api/src/{rewrite => db}/__init__.py (100%) rename apps/api/src/{rewrite/services => }/db/activities.py (100%) rename apps/api/src/{rewrite/services => }/db/blocks.py (100%) rename apps/api/src/{rewrite/services => }/db/chapter_activities.py (100%) rename apps/api/src/{rewrite/services => }/db/chapters.py (100%) rename apps/api/src/{rewrite/services => }/db/collections.py (100%) rename apps/api/src/{rewrite/services => }/db/course_authors.py (100%) rename apps/api/src/{rewrite/services => }/db/course_chapters.py (100%) rename apps/api/src/{rewrite/services => }/db/courses.py (100%) rename apps/api/src/{rewrite/services => }/db/organization_settings.py (100%) rename apps/api/src/{rewrite/services => }/db/organizations.py (100%) rename apps/api/src/{rewrite/services => }/db/roles.py (90%) rename apps/api/src/{rewrite/services => }/db/user_organizations.py (100%) rename apps/api/src/{rewrite/services => }/db/users.py (69%) delete mode 100644 apps/api/src/rewrite/routers/users.py delete mode 100644 apps/api/src/rewrite/services/db/__init__.py delete mode 100644 apps/api/src/rewrite/services/users/users.py diff --git a/apps/api/app.py b/apps/api/app.py index dc066251..3a7f62c6 100644 --- a/apps/api/app.py +++ b/apps/api/app.py @@ -1,7 +1,7 @@ from fastapi import FastAPI, Request from config.config import LearnHouseConfig, get_learnhouse_config from src.core.events.events import shutdown_app, startup_app -from src.router import v1_router, rewrite +from src.router import v1_router from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles @@ -60,7 +60,6 @@ app.mount("/content", StaticFiles(directory="content"), name="content") # Global Routes app.include_router(v1_router) -app.include_router(rewrite) # General Routes @app.get("/") diff --git a/apps/api/src/core/events/database.py b/apps/api/src/core/events/database.py index f7b2d2d8..8c16e65b 100644 --- a/apps/api/src/core/events/database.py +++ b/apps/api/src/core/events/database.py @@ -3,7 +3,7 @@ from fastapi import FastAPI import motor.motor_asyncio from sqlmodel import Field, SQLModel, Session, create_engine -from src.rewrite.services.db import ( +from src.db import ( user_organizations, users, roles, diff --git a/apps/api/src/rewrite/__init__.py b/apps/api/src/db/__init__.py similarity index 100% rename from apps/api/src/rewrite/__init__.py rename to apps/api/src/db/__init__.py diff --git a/apps/api/src/rewrite/services/db/activities.py b/apps/api/src/db/activities.py similarity index 100% rename from apps/api/src/rewrite/services/db/activities.py rename to apps/api/src/db/activities.py diff --git a/apps/api/src/rewrite/services/db/blocks.py b/apps/api/src/db/blocks.py similarity index 100% rename from apps/api/src/rewrite/services/db/blocks.py rename to apps/api/src/db/blocks.py diff --git a/apps/api/src/rewrite/services/db/chapter_activities.py b/apps/api/src/db/chapter_activities.py similarity index 100% rename from apps/api/src/rewrite/services/db/chapter_activities.py rename to apps/api/src/db/chapter_activities.py diff --git a/apps/api/src/rewrite/services/db/chapters.py b/apps/api/src/db/chapters.py similarity index 100% rename from apps/api/src/rewrite/services/db/chapters.py rename to apps/api/src/db/chapters.py diff --git a/apps/api/src/rewrite/services/db/collections.py b/apps/api/src/db/collections.py similarity index 100% rename from apps/api/src/rewrite/services/db/collections.py rename to apps/api/src/db/collections.py diff --git a/apps/api/src/rewrite/services/db/course_authors.py b/apps/api/src/db/course_authors.py similarity index 100% rename from apps/api/src/rewrite/services/db/course_authors.py rename to apps/api/src/db/course_authors.py diff --git a/apps/api/src/rewrite/services/db/course_chapters.py b/apps/api/src/db/course_chapters.py similarity index 100% rename from apps/api/src/rewrite/services/db/course_chapters.py rename to apps/api/src/db/course_chapters.py diff --git a/apps/api/src/rewrite/services/db/courses.py b/apps/api/src/db/courses.py similarity index 100% rename from apps/api/src/rewrite/services/db/courses.py rename to apps/api/src/db/courses.py diff --git a/apps/api/src/rewrite/services/db/organization_settings.py b/apps/api/src/db/organization_settings.py similarity index 100% rename from apps/api/src/rewrite/services/db/organization_settings.py rename to apps/api/src/db/organization_settings.py diff --git a/apps/api/src/rewrite/services/db/organizations.py b/apps/api/src/db/organizations.py similarity index 100% rename from apps/api/src/rewrite/services/db/organizations.py rename to apps/api/src/db/organizations.py diff --git a/apps/api/src/rewrite/services/db/roles.py b/apps/api/src/db/roles.py similarity index 90% rename from apps/api/src/rewrite/services/db/roles.py rename to apps/api/src/db/roles.py index 794a332e..0653a411 100644 --- a/apps/api/src/rewrite/services/db/roles.py +++ b/apps/api/src/db/roles.py @@ -7,7 +7,7 @@ from sqlmodel import Field, SQLModel class RoleTypeEnum(str, Enum): ORGANIZATION = "ORGANIZATION" ORGANIZATION_API_TOKEN = "ORGANIZATION_API_TOKEN" - DEFAULT = "DEFAULT" + GLOBAL = "GLOBAL" class RoleBase(SQLModel): @@ -19,7 +19,7 @@ class RoleBase(SQLModel): class Role(RoleBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) org_id: int = Field(default=None, foreign_key="organization.id") - role_type: RoleTypeEnum = RoleTypeEnum.DEFAULT + role_type: RoleTypeEnum = RoleTypeEnum.GLOBAL role_uuid: str creation_date: str update_date: str diff --git a/apps/api/src/rewrite/services/db/user_organizations.py b/apps/api/src/db/user_organizations.py similarity index 100% rename from apps/api/src/rewrite/services/db/user_organizations.py rename to apps/api/src/db/user_organizations.py diff --git a/apps/api/src/rewrite/services/db/users.py b/apps/api/src/db/users.py similarity index 69% rename from apps/api/src/rewrite/services/db/users.py rename to apps/api/src/db/users.py index acfa3c03..726cedfc 100644 --- a/apps/api/src/rewrite/services/db/users.py +++ b/apps/api/src/db/users.py @@ -16,7 +16,18 @@ class UserCreate(UserBase): class UserUpdate(UserBase): - password: Optional[str] = None + username: str + first_name: Optional[str] + last_name: Optional[str] + email: str + avatar_image: Optional[str] = "" + bio: Optional[str] = "" + + +class UserUpdatePassword(SQLModel): + user_id: int + old_password: str + new_password: str class UserRead(UserBase): diff --git a/apps/api/src/rewrite/routers/users.py b/apps/api/src/rewrite/routers/users.py deleted file mode 100644 index bd01e15e..00000000 --- a/apps/api/src/rewrite/routers/users.py +++ /dev/null @@ -1,23 +0,0 @@ -from fastapi import APIRouter, Depends, Request -from sqlmodel import Session -from src.core.events.database import get_db_session - -from src.rewrite.services.db.users import UserCreate, UserRead -from src.rewrite.services.users.users import create_user - - -router = APIRouter() - - -@router.post("/", response_model=UserRead, tags=["users"]) -async def api_create_user( - *, - request: Request, - db_session: Session = Depends(get_db_session), - user_object: UserCreate, - org_slug: str -): - """ - Create new user - """ - return await create_user(request, db_session, None, user_object, org_slug) diff --git a/apps/api/src/rewrite/services/db/__init__.py b/apps/api/src/rewrite/services/db/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/api/src/rewrite/services/users/users.py b/apps/api/src/rewrite/services/users/users.py deleted file mode 100644 index c5e602e2..00000000 --- a/apps/api/src/rewrite/services/users/users.py +++ /dev/null @@ -1,43 +0,0 @@ -from datetime import datetime -from uuid import uuid4 -from fastapi import Request -from sqlmodel import Session -from src.rewrite.services.db.users import User, UserCreate -from src.security.security import security_hash_password -from src.services.users.schemas.users import PublicUser - - -async def create_user( - request: Request, - db_session: Session, - current_user: PublicUser | None, - user_object: UserCreate, - org_slug: str, -): - user = User.from_orm(user_object) - - # Complete the user object - user.user_uuid = f"user_{uuid4()}" - user.password = await security_hash_password(user_object.password) - user.email_verified = False - user.creation_date = str(datetime.now()) - user.update_date = str(datetime.now()) - - # Verifications - #todo: add username uniqueness verification - #todo: add email uniqueness verification - - - #todo: add user to org as member if org is not None - - # Exclude unset values - user_data = user.dict(exclude_unset=True) - for key, value in user_data.items(): - setattr(user, key, value) - - # Add user to database - db_session.add(user) - db_session.commit() - db_session.refresh(user) - - return user diff --git a/apps/api/src/router.py b/apps/api/src/router.py index 08a5325b..6931d66b 100644 --- a/apps/api/src/router.py +++ b/apps/api/src/router.py @@ -1,7 +1,5 @@ from fastapi import APIRouter, Depends -from src import rewrite from src.routers import blocks, dev, trail, users, auth, orgs, roles -from src.rewrite.routers import users as rw_users from src.routers.courses import chapters, collections, courses, activities from src.routers.install import install from src.services.dev.dev import isDevModeEnabledOrRaise @@ -9,7 +7,6 @@ from src.services.install.install import isInstallModeEnabled v1_router = APIRouter(prefix="/api/v1") -rewrite = APIRouter(prefix="/api/rewrite") # API Routes @@ -39,5 +36,3 @@ v1_router.include_router( dependencies=[Depends(isInstallModeEnabled)], ) -# Rewrite Routes -rewrite.include_router(rw_users.router, prefix="/users", tags=["users"]) diff --git a/apps/api/src/routers/auth.py b/apps/api/src/routers/auth.py index 54e4dc7a..589782e0 100644 --- a/apps/api/src/routers/auth.py +++ b/apps/api/src/routers/auth.py @@ -1,5 +1,8 @@ from fastapi import Depends, APIRouter, HTTPException, Response, status, Request from fastapi.security import OAuth2PasswordRequestForm +from sqlmodel import Session +from src.db.users import UserRead +from src.core.events.database import get_db_session from config.config import get_learnhouse_config from src.security.auth import AuthJWT, authenticate_user from src.services.users.users import PublicUser @@ -9,7 +12,7 @@ router = APIRouter() @router.post("/refresh") -def refresh(response: Response,Authorize: AuthJWT = Depends()): +def refresh(response: Response, Authorize: AuthJWT = Depends()): """ The jwt_refresh_token_required() function insures a valid refresh token is present in the request before running any code below that function. @@ -21,7 +24,12 @@ def refresh(response: Response,Authorize: AuthJWT = Depends()): current_user = Authorize.get_jwt_subject() new_access_token = Authorize.create_access_token(subject=current_user) # type: ignore - response.set_cookie(key="access_token_cookie", value=new_access_token, httponly=False, domain=get_learnhouse_config().hosting_config.cookie_config.domain) + response.set_cookie( + key="access_token_cookie", + value=new_access_token, + httponly=False, + domain=get_learnhouse_config().hosting_config.cookie_config.domain, + ) return {"access_token": new_access_token} @@ -31,8 +39,11 @@ async def login( response: Response, Authorize: AuthJWT = Depends(), form_data: OAuth2PasswordRequestForm = Depends(), + db_session: Session = Depends(get_db_session), ): - user = await authenticate_user(request, form_data.username, form_data.password) + user = await authenticate_user( + request, form_data.username, form_data.password, db_session + ) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -44,8 +55,14 @@ async def login( refresh_token = Authorize.create_refresh_token(subject=form_data.username) Authorize.set_refresh_cookies(refresh_token) # set cookies using fastapi - response.set_cookie(key="access_token_cookie", value=access_token, httponly=False, domain=get_learnhouse_config().hosting_config.cookie_config.domain) - user = PublicUser(**user.dict()) + response.set_cookie( + key="access_token_cookie", + value=access_token, + httponly=False, + domain=get_learnhouse_config().hosting_config.cookie_config.domain, + ) + + user = UserRead.from_orm(user) result = { "user": user, diff --git a/apps/api/src/routers/users.py b/apps/api/src/routers/users.py index 3b4edd1a..1fb8ebc6 100644 --- a/apps/api/src/routers/users.py +++ b/apps/api/src/routers/users.py @@ -1,10 +1,24 @@ -from fastapi import Depends, APIRouter, Request +from fastapi import APIRouter, Depends, Request +from sqlmodel import Session from src.security.auth import get_current_user -from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserWithPassword -from src.services.users.users import create_user, delete_user, get_profile_metadata, get_user_by_userid, update_user, update_user_password - - +from src.core.events.database import get_db_session +from src.db.users import ( + User, + UserCreate, + UserRead, + UserUpdate, + UserUpdatePassword, +) +from src.services.users.users import ( + create_user, + create_user_without_org, + delete_user_by_id, + read_user_by_id, + read_user_by_uuid, + update_user, + update_user_password, +) router = APIRouter() @@ -17,50 +31,95 @@ async def api_get_current_user(current_user: User = Depends(get_current_user)): """ return current_user.dict() -@router.get("/profile_metadata") -async def api_get_current_user_metadata(request: Request,current_user: User = Depends(get_current_user)): + +@router.post("/org_id/{org_id}", response_model=UserRead, tags=["users"]) +async def api_create_user_with_orgid( + *, + request: Request, + db_session: Session = Depends(get_db_session), + user_object: UserCreate, + org_id: int, +) -> UserRead: """ - Get current user + Create User with Org ID """ - return await get_profile_metadata(request , current_user.dict()) + return await create_user(request, db_session, None, user_object, org_id) - -@router.get("/user_id/{user_id}") -async def api_get_user_by_userid(request: Request,user_id: str): +@router.post("/", response_model=UserRead, tags=["users"]) +async def api_create_user_without_org( + *, + request: Request, + db_session: Session = Depends(get_db_session), + user_object: UserCreate, + org_id: int, +) -> UserRead: """ - Get single user by user_id + Create User """ - return await get_user_by_userid(request, user_id) + return await create_user_without_org(request, db_session, None, user_object) -@router.post("/") -async def api_create_user(request: Request,user_object: UserWithPassword, org_slug: str ): +@router.get("/user_id/{user_id}", response_model=UserRead, tags=["users"]) +async def api_get_user_by_id( + *, + request: Request, + db_session: Session = Depends(get_db_session), + user_id: int, +) -> UserRead: """ - Create new user + Get User by ID """ - return await create_user(request, None, user_object, org_slug) + return await read_user_by_id(request, db_session, None, user_id) -@router.delete("/user_id/{user_id}") -async def api_delete_user(request: Request, user_id: str, current_user: PublicUser = Depends(get_current_user)): +@router.get("/user_uuid/{user_uuid}", response_model=UserRead, tags=["users"]) +async def api_get_user_by_uuid( + *, + request: Request, + db_session: Session = Depends(get_db_session), + user_uuid: str, +) -> UserRead: """ - Delete user by ID + Get User by UUID """ - - return await delete_user(request, current_user, user_id) + return await read_user_by_uuid(request, db_session, None, user_uuid) -@router.put("/user_id/{user_id}") -async def api_update_user(request: Request, user_object: User, user_id: str, current_user: PublicUser = Depends(get_current_user)): +@router.put("/", response_model=UserRead, tags=["users"]) +async def api_update_user( + *, + request: Request, + db_session: Session = Depends(get_db_session), + user_object: UserUpdate, +) -> UserRead: """ - Update user by ID + Update User """ - return await update_user(request, user_id, user_object, current_user) + return await update_user(request, db_session, None, user_object) -@router.put("/password/user_id/{user_id}") -async def api_update_user_password(request: Request, user_id: str , passwordChangeForm : PasswordChangeForm, current_user: PublicUser = Depends(get_current_user)): + +@router.put("/change_password/", response_model=UserRead, tags=["users"]) +async def api_update_user_password( + *, + request: Request, + db_session: Session = Depends(get_db_session), + form: UserUpdatePassword, +) -> UserRead: """ - Update user password by ID + Update User Password """ - return await update_user_password(request,current_user, user_id, passwordChangeForm) + return await update_user_password(request, db_session, None, form) + + +@router.delete("/user_id/{user_id}", tags=["users"]) +async def api_delete_user( + *, + request: Request, + db_session: Session = Depends(get_db_session), + user_id: int, +): + """ + Delete User + """ + return await delete_user_by_id(request, db_session, None, user_id) diff --git a/apps/api/src/security/auth.py b/apps/api/src/security/auth.py index d125c15d..d6691864 100644 --- a/apps/api/src/security/auth.py +++ b/apps/api/src/security/auth.py @@ -1,3 +1,7 @@ +from sqlmodel import Session +from src.core.events.database import get_db_session +from src.db.users import User, UserRead +from src.services.users.users import security_get_user from config.config import get_learnhouse_config from pydantic import BaseModel from fastapi import Depends, HTTPException, Request, status @@ -6,7 +10,7 @@ from jose import JWTError, jwt from datetime import datetime, timedelta from src.services.dev.dev import isDevModeEnabled from src.services.users.schemas.users import AnonymousUser, PublicUser -from src.services.users.users import security_get_user, security_verify_password +from src.services.users.users import security_verify_password from src.security.security import ALGORITHM, SECRET_KEY from fastapi_jwt_auth import AuthJWT @@ -45,10 +49,13 @@ class TokenData(BaseModel): #### Classes #################################################### - - -async def authenticate_user(request: Request, email: str, password: str): - user = await security_get_user(request, email) +async def authenticate_user( + request: Request, + email: str, + password: str, + db_session: Session, +) -> User | bool: + user = await security_get_user(request, db_session, email) if not user: return False if not await security_verify_password(password, user.password): @@ -67,7 +74,11 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None): return encoded_jwt -async def get_current_user(request: Request, Authorize: AuthJWT = Depends()): +async def get_current_user( + request: Request, + Authorize: AuthJWT = Depends(), + db_session: Session = Depends(get_db_session), +): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", @@ -81,10 +92,10 @@ async def get_current_user(request: Request, Authorize: AuthJWT = Depends()): except JWTError: raise credentials_exception if username: - user = await security_get_user(request, email=token_data.username) # type: ignore # treated as an email + user = await security_get_user(request, db_session, email=token_data.username) # type: ignore # treated as an email if user is None: raise credentials_exception - return PublicUser(**user.dict()) + return UserRead(**user.dict()) else: return AnonymousUser() diff --git a/apps/api/src/services/dev/mocks/initial.py b/apps/api/src/services/dev/mocks/initial.py index 7d8552e9..d1005093 100644 --- a/apps/api/src/services/dev/mocks/initial.py +++ b/apps/api/src/services/dev/mocks/initial.py @@ -3,10 +3,11 @@ import requests from datetime import datetime from uuid import uuid4 from fastapi import Request +from src.services.users.schemas.users import UserInDB from src.security.security import security_hash_password from src.services.courses.chapters import CourseChapter, create_coursechapter from src.services.courses.activities.activities import Activity, create_activity -from src.services.users.users import PublicUser, UserInDB +from src.services.users.users import PublicUser from src.services.orgs.orgs import Organization, create_org from src.services.roles.schemas.roles import Permission, Elements, RoleInDB diff --git a/apps/api/src/services/users/users.py b/apps/api/src/services/users/users.py index 9aba3b2c..49fc22db 100644 --- a/apps/api/src/services/users/users.py +++ b/apps/api/src/services/users/users.py @@ -1,215 +1,285 @@ from datetime import datetime -from typing import Literal from uuid import uuid4 from fastapi import HTTPException, Request, status -from src.security.rbac.rbac import ( - authorization_verify_based_on_roles, - authorization_verify_if_user_is_anon, -) -from src.security.security import security_hash_password, security_verify_password -from src.services.users.schemas.users import ( - PasswordChangeForm, - PublicUser, +from sqlmodel import Session, select +from src.db.organizations import Organization +from src.db.users import ( User, - UserOrganization, - UserRolesInOrganization, - UserWithPassword, - UserInDB, + UserCreate, + UserRead, + UserUpdate, + UserUpdatePassword, ) +from src.db.user_organizations import UserOrganization +from src.security.security import security_hash_password, security_verify_password +from src.services.users.schemas.users import PublicUser async def create_user( request: Request, + db_session: Session, current_user: PublicUser | None, - user_object: UserWithPassword, - org_slug: str, + user_object: UserCreate, + org_id: int, ): - users = request.app.db["users"] + user = User.from_orm(user_object) - isUsernameAvailable = await users.find_one({"username": user_object.username}) - isEmailAvailable = await users.find_one({"email": user_object.email}) + # Complete the user object + user.user_uuid = f"user_{uuid4()}" + user.password = await security_hash_password(user_object.password) + user.email_verified = False + user.creation_date = str(datetime.now()) + user.update_date = str(datetime.now()) - if isUsernameAvailable: + # Verifications + + # Check if Organization exists + statement = select(Organization).where(Organization.id == org_id) + result = db_session.exec(statement) + + if not result.first(): raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Username already exists" + status_code=400, + detail="Organization does not exist", ) - if isEmailAvailable: + # Username + statement = select(User).where(User.username == user.username) + result = db_session.exec(statement) + + if result.first(): raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Email already exists" + status_code=400, + detail="Username already exists", ) - # Generate user_id with uuid4 - user_id = str(f"user_{uuid4()}") + # Email + statement = select(User).where(User.email == user.email) + result = db_session.exec(statement) - # Check if the requesting user is authenticated - if current_user is not None: - # Verify rights - await verify_user_rights_on_user(request, current_user, "create", user_id) - - # Set the username & hash the password - user_object.username = user_object.username.lower() - user_object.password = await security_hash_password(user_object.password) - - # Get org_id from org_slug - orgs = request.app.db["organizations"] - - # Check if the org exists - isOrgExists = await orgs.find_one({"slug": org_slug}) - - # If the org does not exist, raise an error - if not isOrgExists and (org_slug != "None"): + if result.first(): raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="You are trying to create a user in an organization that does not exist", + status_code=400, + detail="Email already exists", ) - - org_id = isOrgExists["org_id"] if org_slug != "None" else '' - # Create initial orgs list with the org_id passed in - orgs = ( - [UserOrganization(org_id=org_id, org_role="member")] - if org_slug != "None" - else [] - ) + # Exclude unset values + user_data = user.dict(exclude_unset=True) + for key, value in user_data.items(): + setattr(user, key, value) - # Give role - roles = ( - [UserRolesInOrganization(role_id="role_member", org_id=org_id)] - if org_slug != "None" - else [] - ) + # Add user to database + db_session.add(user) + db_session.commit() + db_session.refresh(user) - # Create the user - user = UserInDB( - user_id=user_id, + # Link user and organization + user_organization = UserOrganization( + user_id=user.id is not None, + org_id=int(org_id), + role_id=1, creation_date=str(datetime.now()), update_date=str(datetime.now()), - orgs=orgs, - roles=roles, - **user_object.dict(), ) - # Insert the user into the database - await users.insert_one(user.dict()) + db_session.add(user_organization) + db_session.commit() + db_session.refresh(user_organization) - return User(**user.dict()) + user = UserRead.from_orm(user) + + return user -async def read_user(request: Request, current_user: PublicUser, user_id: str): - users = request.app.db["users"] +async def create_user_without_org( + request: Request, + db_session: Session, + current_user: PublicUser | None, + user_object: UserCreate, +): + user = User.from_orm(user_object) - # Check if the user exists - isUserExists = await users.find_one({"user_id": user_id}) + # Complete the user object + user.user_uuid = f"user_{uuid4()}" + user.password = await security_hash_password(user_object.password) + user.email_verified = False + user.creation_date = str(datetime.now()) + user.update_date = str(datetime.now()) - # Verify rights - await verify_user_rights_on_user(request, current_user, "read", user_id) + # Verifications - # If the user does not exist, raise an error - if not isUserExists: + # Username + statement = select(User).where(User.username == user.username) + result = db_session.exec(statement) + + if result.first(): raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not exist" + status_code=400, + detail="Username already exists", ) - return User(**isUserExists) + # Email + statement = select(User).where(User.email == user.email) + result = db_session.exec(statement) + + if result.first(): + raise HTTPException( + status_code=400, + detail="Email already exists", + ) + + # Exclude unset values + user_data = user.dict(exclude_unset=True) + for key, value in user_data.items(): + setattr(user, key, value) + + # Add user to database + db_session.add(user) + db_session.commit() + db_session.refresh(user) + + user = UserRead.from_orm(user) + + return user async def update_user( - request: Request, user_id: str, user_object: User, current_user: PublicUser + request: Request, + db_session: Session, + current_user: PublicUser | None, + user_object: UserUpdate, ): - users = request.app.db["users"] + # Get user + statement = select(User).where(User.username == user_object.username) + user = db_session.exec(statement).first() - # Verify rights - await verify_user_rights_on_user(request, current_user, "update", user_id) - - isUserExists = await users.find_one({"user_id": user_id}) - isUsernameAvailable = await users.find_one({"username": user_object.username}) - isEmailAvailable = await users.find_one({"email": user_object.email}) - - if not isUserExists: + if not user: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not exist" + status_code=400, + detail="User does not exist", ) - # okay if username is not changed - if isUserExists["username"] == user_object.username: - user_object.username = user_object.username.lower() + # Update user + user_data = user_object.dict(exclude_unset=True) + for key, value in user_data.items(): + setattr(user, key, value) - else: - if isUsernameAvailable: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Username already used" - ) + # Update user in database + db_session.add(user) + db_session.commit() + db_session.refresh(user) - if isEmailAvailable: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Email already used" - ) + user = UserRead.from_orm(user) - updated_user = {"$set": user_object.dict()} - users.update_one({"user_id": user_id}, updated_user) - - return User(**user_object.dict()) + return user async def update_user_password( request: Request, - current_user: PublicUser, - user_id: str, - password_change_form: PasswordChangeForm, + db_session: Session, + current_user: PublicUser | None, + form: UserUpdatePassword, ): - users = request.app.db["users"] + # Get user + statement = select(User).where(User.username == form.user_id) + user = db_session.exec(statement).first() - isUserExists = await users.find_one({"user_id": user_id}) - - # Verify rights - await verify_user_rights_on_user(request, current_user, "update", user_id) - - if not isUserExists: + if not user: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not exist" + status_code=400, + detail="User does not exist", ) - if not await security_verify_password( - password_change_form.old_password, isUserExists["password"] - ): + if not await security_verify_password(form.old_password, user.password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password" ) - new_password = await security_hash_password(password_change_form.new_password) + # Update user + user.password = await security_hash_password(form.new_password) - updated_user = {"$set": {"password": new_password}} - await users.update_one({"user_id": user_id}, updated_user) + # Update user in database + db_session.add(user) + db_session.commit() + db_session.refresh(user) - return {"detail": "Password updated"} + user = UserRead.from_orm(user) + + return user -async def delete_user(request: Request, current_user: PublicUser, user_id: str): - users = request.app.db["users"] +async def read_user_by_id( + request: Request, + db_session: Session, + current_user: PublicUser | None, + user_id: int, +): + # Get user + statement = select(User).where(User.id == user_id) + user = db_session.exec(statement).first() - isUserExists = await users.find_one({"user_id": user_id}) - - # Verify is user has permission to delete the user - await verify_user_rights_on_user(request, current_user, "delete", user_id) - - if not isUserExists: + if not user: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not exist" + status_code=400, + detail="User does not exist", ) - await users.delete_one({"user_id": user_id}) + user = UserRead.from_orm(user) - return {"detail": "User deleted"} + return user + + +async def read_user_by_uuid( + request: Request, + db_session: Session, + current_user: PublicUser | None, + uuid: str, +): + # Get user + statement = select(User).where(User.user_uuid == uuid) + user = db_session.exec(statement).first() + + if not user: + raise HTTPException( + status_code=400, + detail="User does not exist", + ) + + user = UserRead.from_orm(user) + + return user + + +async def delete_user_by_id( + request: Request, + db_session: Session, + current_user: PublicUser | None, + user_id: int, +): + # Get user + statement = select(User).where(User.id == user_id) + user = db_session.exec(statement).first() + + if not user: + raise HTTPException( + status_code=400, + detail="User does not exist", + ) + + # Delete user + db_session.delete(user) + db_session.commit() + + return "User deleted" # Utils & Security functions -async def security_get_user(request: Request, email: str): - users = request.app.db["users"] - - user = await users.find_one({"email": email}) +async def security_get_user(request: Request, db_session: Session, email: str) -> User: + # Check if user exists + statement = select(User).where(User.email == email) + user = db_session.exec(statement).first() if not user: raise HTTPException( @@ -217,105 +287,6 @@ async def security_get_user(request: Request, email: str): detail="User with Email does not exist", ) - return UserInDB(**user) + user = User(**user.dict()) - -async def get_userid_by_username(request: Request, username: str): - users = request.app.db["users"] - - user = await users.find_one({"username": username}) - - if not user: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not exist" - ) - - return user["user_id"] - - -async def get_user_by_userid(request: Request, user_id: str): - users = request.app.db["users"] - - user = await users.find_one({"user_id": user_id}) - - if not user: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not exist" - ) - - user = User(**user) return user - - -async def get_profile_metadata(request: Request, user): - users = request.app.db["users"] - request.app.db["roles"] - - user = await users.find_one({"user_id": user["user_id"]}) - - if not user: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not exist" - ) - - return {"user_object": PublicUser(**user), "roles": "random"} - - -# Verification of the user's permissions on the roles - - -async def verify_user_rights_on_user( - request: Request, - current_user: PublicUser, - action: Literal["create", "read", "update", "delete"], - user_id: str, -): - users = request.app.db["users"] - user = UserInDB(**await users.find_one({"user_id": user_id})) - - if action == "create": - return True - - if action == "read": - await authorization_verify_if_user_is_anon(current_user.user_id) - - if current_user.user_id == user_id: - return True - - for org in current_user.orgs: - if org.org_id in [org.org_id for org in user.orgs]: - return True - - return False - - if action == "update": - await authorization_verify_if_user_is_anon(current_user.user_id) - - if current_user.user_id == user_id: - return True - - for org in current_user.orgs: - if org.org_id in [org.org_id for org in user.orgs]: - if org.org_role == "owner": - return True - - await authorization_verify_based_on_roles( - request, current_user.user_id, "update", user["roles"], user_id - ) - - return False - - if action == "delete": - await authorization_verify_if_user_is_anon(current_user.user_id) - - if current_user.user_id == user_id: - return True - - for org in current_user.orgs: - if org.org_id in [org.org_id for org in user.orgs]: - if org.org_role == "owner": - return True - - await authorization_verify_based_on_roles( - request, current_user.user_id, "update", user["roles"], user_id - )