diff --git a/backend/app/api/api_v1/routers/users.py b/backend/app/api/api_v1/routers/users.py index 8cb1710..edb26cb 100644 --- a/backend/app/api/api_v1/routers/users.py +++ b/backend/app/api/api_v1/routers/users.py @@ -1,6 +1,7 @@ from fastapi import APIRouter, Request, Depends, Response, Security, encoders import typing as t - +from app.core.common import ApiReturnModel,ApiReturnPage +from app.core.apiexception import APIException from app.db.session import get_db from app.db.crud import ( get_allusers, @@ -12,129 +13,174 @@ from app.db.crud import ( assign_userrole, get_roles, ) -from app.db.schemas import UserCreate, UserEdit, User, UserOut,Role +from app.db.schemas import UserCreate, UserEdit, User, UserOut,RoleBase,Permission from app.core.auth import get_current_user,get_current_active_user, get_current_active_superuser +from app.db.cruddb.dbuser import dbuser users_router = r = APIRouter() @r.get( - "/users", - response_model=t.List[User], + "/users",tags=["User"], + response_model=ApiReturnPage[User], response_model_exclude_none=True, ) async def users_list( - response: Response, + request: Request, db=Depends(get_db), current_user=Depends(get_current_active_user), ): - """ - Get all users - """ - if current_user.is_superuser: - users = get_allusers(db) - else: - users = get_users(db) - # This is necessary for react-admin to work - #response.headers["Content-Range"] = f"0-9/{len(users)}" - return users + try: + if current_user.is_superuser: + users = dbuser.get_users(db) + else: + users = dbuser.get_users_not_admin(db) + return users + except Exception as e: + raise APIException('user:users',request.url._url,f"Error occurred while get user list",e) - -@r.get("/users/me", response_model=User, response_model_exclude_none=True) +@r.get("/users/me", tags=["User"], + response_model=ApiReturnModel[User], + response_model_exclude_none=True, +) async def user_me(current_user=Depends(get_current_active_user)): - """ - Get own user - """ - return current_user + return ApiReturnModel(data = current_user) @r.get( - "/users/{user_id}", - response_model=User, + "/users/{user_id}",tags=["User"], + response_model=ApiReturnModel[User|None], response_model_exclude_none=True, ) async def user_details( request: Request, user_id: int, db=Depends(get_db), - current_user=Depends(get_current_active_superuser), + current_user=Depends(get_current_active_user), ): - """ - Get any user details - """ - user = get_user(db, user_id) - return user - # return encoders.jsonable_encoder( - # user, skip_defaults=True, exclude_none=True, - # ) + try: + user = dbuser.get(db, user_id) + if user: + if user.is_superuser and not current_user.is_superuser: + user = None + return ApiReturnModel(data = user) + except Exception as e: + raise APIException('user:users',request.url._url,f"Error occurred while get user({user_id}) detail:",e) -@r.post("/users", response_model=User, response_model_exclude_none=True) +@r.post("/users", tags=["User"], + response_model=ApiReturnModel[User|None], + response_model_exclude_none=True, +) async def user_create( request: Request, user: UserCreate, db=Depends(get_db), - current_user=Depends(get_current_active_superuser), + current_user=Depends(get_current_active_user), ): - """ - Create a new user - """ - return create_user(db, user) + try: + if user.is_superuser and not current_user.is_superuser: + return ApiReturnModel(data = None) + return ApiReturnModel(data =dbuser.create_user(db, user,current_user.id)) + except Exception as e: + raise APIException('user:users',request.url._url,f"Error occurred while create user({user.email}):",e) @r.put( - "/users/{user_id}", response_model=User, response_model_exclude_none=True + "/users/{user_id}", tags=["User"], + response_model=ApiReturnModel[User|None], + response_model_exclude_none=True, ) async def user_edit( request: Request, user_id: int, user: UserEdit, db=Depends(get_db), - current_user=Depends(get_current_active_superuser), + current_user=Depends(get_current_active_user), ): - """ - Update existing user - """ - return edit_user(db, user_id, user) - + try: + if user.is_superuser and not current_user.is_superuser: + return ApiReturnModel(data = None) + return ApiReturnModel(data = dbuser.edit_user(db,user_id,user,current_user.id)) + except Exception as e: + raise APIException('user:users',request.url._url,f"Error occurred while edit user({user_id}):",e) @r.delete( - "/users/{user_id}", response_model=User, response_model_exclude_none=True + "/users/{user_id}", tags=["User"], + response_model=ApiReturnModel[UserOut|None], + response_model_exclude_none=True ) async def user_delete( request: Request, user_id: int, db=Depends(get_db), - current_user=Depends(get_current_active_superuser), + current_user=Depends(get_current_active_user), ): - """ - Delete existing user - """ - return delete_user(db, user_id) + try: + user = dbuser.get(db,user_id) + if user.is_superuser and not current_user.is_superuser: + return ApiReturnModel(data = None) + return ApiReturnModel(data = dbuser.delete_user(db, user_id)) + except Exception as e: + raise APIException('user:users',request.url._url,f"Error occurred while delete user({user_id}):",e) -@r.post("/userrole", - response_model=User, +@r.post("/userrole",tags=["User"], + response_model=ApiReturnModel[User], response_model_exclude_none=True,) async def assign_role( request: Request, - userid:int, + user_id:int, roles:t.List[int], db=Depends(get_db) ): - - return assign_userrole(db,userid,roles) - + try: + return ApiReturnModel(data = dbuser.assign_userrole(db,user_id,roles)) + except Exception as e: + raise APIException('user:userrole',request.url._url,f"Error occurred while assign user({user_id}) roles({roles}):",e) @r.get( - "/roles", - response_model=t.List[Role], + "/roles",tags=["User"], + response_model=ApiReturnModel[t.List[RoleBase]|None], response_model_exclude_none=True, ) async def roles_list( - response: Response, + request: Request, db=Depends(get_db), - current_user=Security(get_current_active_user, scopes=["role_list"]), + current_user=Depends(get_current_active_user), + #current_user=Security(get_current_active_user, scopes=["role_list"]), ): - roles = get_roles(db) - return roles + try: + if current_user.is_superuser: + roles = dbuser.get_roles(db) + else: + if len(current_user.roles)>0: + roles = dbuser.get_roles_by_level(db,current_user.roles[0].level) + else: + roles = [] + return ApiReturnModel(data = roles) + except Exception as e: + raise APIException('user:roles',request.url._url,f"Error occurred while get roles:",e) + +@r.get( + "/userpermssions",tags=["User"], + response_model=ApiReturnModel[t.List[Permission]|None], + response_model_exclude_none=True, +) +async def permssions_list( + request: Request, + db=Depends(get_db), + current_user=Depends(get_current_active_user), + #current_user=Security(get_current_active_user, scopes=["role_list"]), +): + try: + if current_user.is_superuser: + permissions = dbuser.get_permissions(db) + else: + if len(current_user.roles)>0: + permissions = dbuser.get_user_permissions(db,current_user.id) + else: + permissions = [] + return ApiReturnModel(data = permissions) + except Exception as e: + raise APIException('user:userpermssions',request.url._url,f"Error occurred while get user(){current_user.id} permissions:",e) diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py index 95e32ea..d5da2d3 100644 --- a/backend/app/core/auth.py +++ b/backend/app/core/auth.py @@ -6,7 +6,7 @@ from jwt import PyJWTError from app.db import models, schemas, session from app.db.crud import get_user_by_email, create_user,get_user from app.core import security - +from app.db.cruddb.dbuser import dbuser async def get_current_user(security_scopes: SecurityScopes, db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme) @@ -35,7 +35,7 @@ async def get_current_user(security_scopes: SecurityScopes, token_data = schemas.TokenData(id = id, permissions=permissions) except PyJWTError: raise credentials_exception - user = get_user(db, token_data.id) + user = dbuser.get_user(db, token_data.id) if user is None: raise credentials_exception return user diff --git a/backend/app/db/crud.py b/backend/app/db/crud.py index b18c9de..120405b 100644 --- a/backend/app/db/crud.py +++ b/backend/app/db/crud.py @@ -78,7 +78,7 @@ def edit_user( def get_roles( db: Session -) -> t.List[schemas.Role]: +) -> t.List[schemas.RoleBase]: return db.query(models.Role).all() def assign_userrole( db: Session, user_id: int, roles: t.List[int]): diff --git a/backend/app/db/cruddb/crudbase.py b/backend/app/db/cruddb/crudbase.py index f4196fe..c3e2368 100644 --- a/backend/app/db/cruddb/crudbase.py +++ b/backend/app/db/cruddb/crudbase.py @@ -30,8 +30,12 @@ class crudbase: and_conditions.append(column == filter_value) elif operator == '>': and_conditions.append(column > filter_value) + elif operator == '>=': + and_conditions.append(column >= filter_value) elif operator == '<': and_conditions.append(column < filter_value) + elif operator == '<=': + and_conditions.append(column <= filter_value) elif operator == 'in': if isinstance(filter_value, list): or_conditions.append(column.in_(filter_value)) diff --git a/backend/app/db/cruddb/dbuser.py b/backend/app/db/cruddb/dbuser.py new file mode 100644 index 0000000..c7efe4c --- /dev/null +++ b/backend/app/db/cruddb/dbuser.py @@ -0,0 +1,92 @@ +from datetime import datetime +from fastapi import HTTPException, status +from sqlalchemy.orm import Session +from sqlalchemy import and_ +import typing as t + +from app.db.cruddb.crudbase import crudbase +from fastapi_pagination.ext.sqlalchemy import paginate +from app.core.common import ApiReturnPage + +from app.db import models, schemas +from app.core.security import chacha20Decrypt, get_password_hash + + +class dbpermission(crudbase): + def __init__(self): + super().__init__(model=models.Permission) + +dbpermission = dbpermission() + +class dbrole(crudbase): + def __init__(self): + super().__init__(model=models.Role) + +dbrole = dbrole() + +class dbuser(crudbase): + def __init__(self): + super().__init__(model=models.User) + + def get_user(self,db: Session, user_id: int) -> schemas.User: + return super().get(db,user_id) + + def get_user_by_email(self,db: Session, email: str) -> schemas.User: + return super().get_by_conditions(db,{"email":email}).first() + + def get_users(self,db: Session) -> ApiReturnPage[models.Base]: + return paginate(super().get_all()) + + def get_users_not_admin(self,db: Session) -> ApiReturnPage[models.Base]: + return paginate(super().get_by_conditions(db,{"is_superuser":False})) + + def create_user(self,db: Session, user: schemas.UserCreate,userid:int): + hashed_password = get_password_hash(user.password) + user.hashed_password = hashed_password + user.createuserid = userid + user.updateuserid = userid + del user.password + return super().create(db,user) + + def delete_user(self,db: Session, user_id: int): + return super().delete(db,user_id) + + + def edit_user(self,db: Session, user_id:int,user: schemas.UserEdit,userid: int) -> schemas.User: + if not user.password is None and user.password != "": + user.hashed_password = get_password_hash(user.password) + del user.password + user.updateuserid = userid + return super().update(db,user_id,user) + + def get_roles(self,db: Session) -> t.List[schemas.RoleBase]: + return dbrole.get_all(db).all() + + def get_roles_by_level(self,db: Session,level:int) -> t.List[schemas.RoleBase]: + return dbrole.get_by_conditions(db,{"level":{"operator":">=","value":level}}).all() + + def assign_userrole(self,db: Session, user_id: int, roles: t.List[int]): + db_user = super().get(db,user_id) + if db_user: + for role in db_user.roles: + db_user.roles.remove(role) + for roleid in roles: + role = dbrole.get(db,roleid) + if role: + db_user.roles.append(role) + db.commit() + db.refresh(db_user) + return db_user + + def get_permissions(self,db: Session,user_id: int) -> t.List[schemas.Permission]: + return dbpermission.get_by_conditions(db).all() + + def get_user_permissions(self,db: Session,user_id: int) -> t.List[schemas.Permission]: + permissions =[] + db_user = super().get(db,user_id) + if db_user: + for role in db_user.roles: + permissions += role.permissions + return list(set(permissions)) + +dbuser = dbuser() \ No newline at end of file diff --git a/backend/app/db/models.py b/backend/app/db/models.py index ff38fd0..9ef599c 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -35,6 +35,10 @@ class User(Base): hashed_password = Column(String(200), nullable=False) is_active = Column(Boolean, default=True) is_superuser = Column(Boolean, default=False) + createuserid = Column(Integer,ForeignKey("user.id")) + updateuserid = Column(Integer,ForeignKey("user.id")) + createuser = relationship('User',foreign_keys=[createuserid]) + updateuser = relationship('User',foreign_keys=[updateuserid]) roles = relationship("Role",secondary=userrole,back_populates="users") @@ -43,6 +47,7 @@ class Role(Base): name = Column(String(100)) description = Column(String(255)) + level = Column(Integer) users = relationship("User",secondary=userrole,back_populates="roles") permissions = relationship("Permission",secondary=rolepermission,back_populates="roles") diff --git a/backend/app/db/schemas.py b/backend/app/db/schemas.py index a1227b9..f592d8f 100644 --- a/backend/app/db/schemas.py +++ b/backend/app/db/schemas.py @@ -15,10 +15,14 @@ class Permission(BaseModel): function:str privilege:str -class Role(BaseModel): +class RoleBase(BaseModel): id: int name:str description:str + level:int + + +class RoleWithPermission(RoleBase): permissions:t.List[Permission] = [] class UserBase(BaseModel): @@ -27,7 +31,7 @@ class UserBase(BaseModel): is_superuser: bool = False first_name: str = None last_name: str = None - roles:t.List[Role] = [] + roles:t.List[RoleBase] = [] class UserOut(BaseModel): @@ -42,10 +46,13 @@ class UserOut(BaseModel): class UserCreate(UserBase): email:str password: str + hashed_password :str = None first_name: str last_name: str is_active:bool is_superuser:bool + createuserid:t.Optional[int] = None + updateuserid:t.Optional[int] = None class ConfigDict: orm_mode = True @@ -53,6 +60,8 @@ class UserCreate(UserBase): class UserEdit(UserBase): password: t.Optional[str] = None + hashed_password :str = None + updateuserid:t.Optional[int] = None class ConfigDict: orm_mode = True