diff --git a/backend/app/api/api_v1/routers/platform.py b/backend/app/api/api_v1/routers/platform.py index fcc2c9b..f5f876c 100644 --- a/backend/app/api/api_v1/routers/platform.py +++ b/backend/app/api/api_v1/routers/platform.py @@ -9,13 +9,26 @@ from app.db.schemas import * from typing import List, Optional from app.core.auth import get_current_active_user,get_current_user from app.core.apiexception import APIException -from app.core.common import ApiReturnModel +from app.core.common import ApiReturnModel,ApiReturnPage +#from fastapi_pagination import Page +from app.db.cruddb.dbdomain import dbdomain import httpx import app.core.config as config platform_router = r = APIRouter() +@r.get( + "/test", + response_model_exclude_none=True, +) +async def test( + request: Request, + user = Depends(get_current_active_user), + db=Depends(get_db), +): + dbdomain.select(db,{"tenantid":1,"name":["b","c"]}) + @r.get( "/apps", @@ -255,8 +268,8 @@ async def flow_delete( raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e) @r.get( - "/domains", - response_model=ApiReturnModel[List[Domain]], + "/domains",tags=["Domain"], + response_model=ApiReturnPage[Domain], response_model_exclude_none=True, ) async def domain_details( @@ -266,14 +279,16 @@ async def domain_details( ): try: if user.is_superuser: - domains =get_alldomains(db) + domains = dbdomain.get_domains(db) else: - domains = get_domains(db,user.id) - return ApiReturnModel(data = domains) + domains = dbdomain.get_domains_by_owner(db,user.id) + return domains except Exception as e: raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e) -@r.post("/domain", response_model=ApiReturnModel[Domain], response_model_exclude_none=True) +@r.post("/domain", tags=["Domain"], + response_model=ApiReturnModel[Domain], + response_model_exclude_none=True) async def domain_create( request: Request, domain: DomainIn, @@ -281,13 +296,15 @@ async def domain_create( db=Depends(get_db), ): try: - return ApiReturnModel(data = create_domain(db, domain,user.id)) + return ApiReturnModel(data = dbdomain.create_domain(db, domain,user.id)) except Exception as e: raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e) @r.put( - "/domain", response_model=ApiReturnModel[Domain], response_model_exclude_none=True + "/domain", tags=["Domain"], + response_model=ApiReturnModel[Domain|None], + response_model_exclude_none=True ) async def domain_edit( request: Request, @@ -296,13 +313,14 @@ async def domain_edit( db=Depends(get_db), ): try: - return ApiReturnModel(data = edit_domain(db, domain,user.id)) + return ApiReturnModel(data = dbdomain.edit_domain(db, domain,user.id)) except Exception as e: raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e) @r.delete( - "/domain/{id}", + "/domain/{id}",tags=["Domain"], + response_model=ApiReturnModel[Domain|None], response_model_exclude_none=True, ) async def domain_delete( @@ -311,8 +329,7 @@ async def domain_delete( db=Depends(get_db), ): try: - if delete_domain(db,id): - return ApiReturnModel(data = None) + return ApiReturnModel(data = dbdomain.delete_domain(db,id)) except Exception as e: raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e) @@ -334,7 +351,7 @@ async def userdomain_details( raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e) @r.post( - "/domain/{userid}", + "/domain/{userid}",tags=["Domain"], response_model=ApiReturnModel[DomainOut|None], response_model_exclude_none=True, ) @@ -347,52 +364,53 @@ async def create_userdomain( ): try: if user.is_superuser: - domain = add_admindomain(db,userid,domainid) + domain = dbdomain.add_userdomain(db,user.id,userid,domainid) else: - domain = add_userdomain(db,user.id,userid,domainid) + domain = dbdomain.add_userdomain_by_owner(db,user.id,userid,domainid) return ApiReturnModel(data = domain) except Exception as e: raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e) @r.delete( - "/domain/{domainid}/{userid}", + "/domain/{domainid}/{userid}",tags=["Domain"], + response_model=ApiReturnModel[DomainOut|None], response_model_exclude_none=True, ) -async def userdomain_delete( +async def delete_userdomain( request: Request, domainid:int, userid: int, + user=Depends(get_current_active_user), db=Depends(get_db), ): try: - if delete_userdomain(db, userid,domainid): - return ApiReturnModel(data = None) + return ApiReturnModel(data = dbdomain.delete_userdomain(db,userid,domainid)) except Exception as e: raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e) @r.get( - "/activedomain", - response_model=ApiReturnModel[ActiveDomain|None], + "/defaultdomain",tags=["Domain"], + response_model=ApiReturnModel[DomainOut|None], response_model_exclude_none=True, ) -async def get_activeuserdomain( +async def get_defaultuserdomain( request: Request, user=Depends(get_current_active_user), db=Depends(get_db), ): try: - # domain = get_activedomain(db, user.id) - domain = get_activedomain(db, user.id) - # if domain is None: - # return JSONResponse(content=None,status_code=HTTPStatus.OK) - return ApiReturnModel(data = domain) + userdomain = dbdomain.get_default_domain(db, user.id) + if userdomain: + return ApiReturnModel(data = userdomain.domain) + else: + return ApiReturnModel(data = None) except Exception as e: - raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e) - + raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while get user({user.id}) defaultdomain:",e) + @r.put( - "/activedomain/{domainid}", + "/defaultdomain/{domainid}",tags=["Domain"], response_model=ApiReturnModel[DomainOut|None], response_model_exclude_none=True, ) @@ -403,10 +421,28 @@ async def update_activeuserdomain( db=Depends(get_db), ): try: - domain = active_userdomain(db,user.id,domainid) + domain = dbdomain.set_default_domain(db,user.id,domainid) return ApiReturnModel(data= domain) except Exception as e: - raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",e) + raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while update user({user.id}) defaultdomain:",e) + + +@r.get( + "/domainshareduser/{domainid}",tags=["Domain"], + response_model=ApiReturnPage[UserOut|None], + response_model_exclude_none=True, +) +async def get_domainshareduser( + request: Request, + domainid:int, + user=Depends(get_current_active_user), + db=Depends(get_db), +): + try: + return dbdomain.get_shareddomain_users(db,user.id,domainid) + except Exception as e: + raise APIException('platform:sharedomain',request.url._url,f"Error occurred while get user({user.id}) sharedomain:",e) + @r.get( "/events", diff --git a/backend/app/core/apiexception.py b/backend/app/core/apiexception.py index 808caef..3183ccb 100644 --- a/backend/app/core/apiexception.py +++ b/backend/app/core/apiexception.py @@ -19,7 +19,7 @@ class APIException(Exception): elif hasattr(e, 'detail'): self.detail = e.detail self.status_code = e.status_code if hasattr(e, 'status_code') else 500 - content += e.detail + content += str(e.detail) else: self.detail = str(e) self.status_code = 500 diff --git a/backend/app/core/common.py b/backend/app/core/common.py index 60ed5f6..c6ba12a 100644 --- a/backend/app/core/common.py +++ b/backend/app/core/common.py @@ -1,10 +1,43 @@ +import math +from fastapi import Query +from fastapi_pagination.bases import AbstractPage,AbstractParams,RawParams from pydantic import BaseModel -from typing import Generic,TypeVar,Optional +from typing import Generic, List,TypeVar,Generic,Sequence +from fastapi_pagination import Page,utils T = TypeVar('T') class ApiReturnModel(BaseModel,Generic[T]): code:int = 0 msg:str ="OK" - data:T \ No newline at end of file + data:T + +class Params(BaseModel, AbstractParams): + page:int = Query(1,get=1, description="Page number") + size:int = Query(20,get=0, le=100,description="Page size") + + def to_raw_params(self) -> RawParams: + return RawParams( + limit=self.size, + offset=self.size*(self.page-1) + ) + +class ApiReturnPage(AbstractPage[T],Generic[T]): + code:int =0 + msg:str ="OK" + data:Sequence[T] + total:int + page:int + size:int + # next:str + # previous:str + total_pages:int + + __params_type__ =Params + + @classmethod + def create(cls,items:Sequence[T],total:int,params:Params) -> Page[T]: + total_pages = math.ceil(total/params.size) + + return utils.create_pydantic_model(cls,data=items,total=total,page=params.page,size=params.size,total_pages=total_pages) diff --git a/backend/app/db/cruddb/crudbase.py b/backend/app/db/cruddb/crudbase.py new file mode 100644 index 0000000..f4196fe --- /dev/null +++ b/backend/app/db/cruddb/crudbase.py @@ -0,0 +1,101 @@ +from sqlalchemy import asc, desc +from sqlalchemy.orm import Session +from sqlalchemy.orm.query import Query +from typing import Type, List, Optional +from app.core.common import ApiReturnPage +from fastapi_pagination.ext.sqlalchemy import paginate +from sqlalchemy import and_ ,or_ +from pydantic import BaseModel +from .. import models, schemas + +class crudbase: + def __init__(self, model: Type[models.Base]): + self.model = model + + def _apply_filters(self, query: Query, filters: dict) -> Query: + and_conditions = [] + or_conditions = [] + for column_name, value in filters.items(): + column = getattr(self.model, column_name, None) + if column: + if isinstance(value, dict): + if 'operator' in value: + operator = value['operator'] + filter_value = value['value'] + if operator == '!=': + and_conditions.append(column != filter_value) + elif operator == 'like': + and_conditions.append(column.like(f"%{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)) + else: + and_conditions.append(column == filter_value) + else: + and_conditions.append(column == value) + else: + and_conditions.append(column == value) + + if and_conditions: + query = query.filter(*and_conditions) + if or_conditions: + query = query.filter(or_(*or_conditions)) + return query + + def _apply_sorting(self, query: Query, sort_by: Optional[str], sort_order: Optional[str]) -> Query: + if sort_by: + column = getattr(self.model, sort_by, None) + if column: + if sort_order == "desc": + query = query.order_by(desc(column)) + else: + query = query.order_by(asc(column)) + return query + + def get_all(self, db: Session) -> ApiReturnPage[models.Base]: + return paginate(db.query(self.model)) + + + def get(self, db: Session, item_id: int) -> Optional[models.Base]: + return db.query(self.model).get(item_id) + + def create(self, db: Session, obj_in: BaseModel) -> models.Base: + db_obj = self.model(**obj_in.model_dump()) + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + + def update(self, db: Session, item_id: int, obj_in: BaseModel) -> Optional[models.Base]: + db_obj = db.query(self.model).filter(self.model.id == item_id).first() + if db_obj: + for key, value in obj_in.model_dump(exclude_unset=True).items(): + setattr(db_obj, key, value) + db.commit() + db.refresh(db_obj) + return db_obj + return None + + def delete(self, db: Session, item_id: int) -> Optional[models.Base]: + db_obj = db.query(self.model).get(item_id) + if db_obj: + db.delete(db_obj) + db.commit() + return db_obj + return None + + def get_by_conditions(self, db: Session, filters: Optional[dict] = None, sort_by: Optional[str] = None, + sort_order: Optional[str] = "asc") -> Query: + query = db.query(self.model) + if filters: + query = self._apply_filters(query, filters) + if sort_by: + query = self._apply_sorting(query, sort_by, sort_order) + print(str(query)) + return query \ No newline at end of file diff --git a/backend/app/db/cruddb/dbdomain.py b/backend/app/db/cruddb/dbdomain.py new file mode 100644 index 0000000..5b8178c --- /dev/null +++ b/backend/app/db/cruddb/dbdomain.py @@ -0,0 +1,135 @@ +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 dbuserdomain(crudbase): + def __init__(self): + super().__init__(model=models.UserDomain) + + def get_userdomain(self,db: Session,userid:int,domainid:int): + return super().get_by_conditions(db,{"userid":userid,"domainid":domainid}).first() + + def get_userdomain_by_domainid(self,db: Session,ownerid:int,domainid:int): + return super().get_by_conditions(db,{"domainid":domainid}) + + + def get_default_domains(self,db: Session,domainid:int): + return super().get_by_conditions(db,{"domainid":domainid,"is_default":True}).all() + + def get_user_default_domain(self,db: Session,userid:int): + return super().get_by_conditions(db,{"userid":userid,"is_default":True}).first() + + +dbuserdomain = dbuserdomain() + +class dbdomain(crudbase): + def __init__(self): + super().__init__(model=models.Domain) + + def get_domains(self,db: Session)-> ApiReturnPage[models.Base]: + return super().get_all(db) + + def get_domains_by_owner(self,db: Session,ownerid:int)-> ApiReturnPage[models.Base]: + return paginate( super().get_by_conditions(db,{"ownerid":ownerid})) + + def create_domain(self,db: Session, domain: schemas.DomainIn,userid:int): + db_domain = super().get_by_conditions(db,{"url":domain.url,"kintoneuser":domain.kintoneuser,"onwerid":userid}).first() + if not db_domain: + domain.encrypt_kintonepwd() + domain.id = None + domain.createuserid = userid + domain.updateuserid = userid + domain.ownerid = userid + return super().create(db,domain) + return db_domain + + def delete_domain(self,db: Session,id: int): + return super().delete(db,id) + + def edit_domain(self,db: Session, domain: schemas.DomainIn,userid:int) -> schemas.DomainOut: + db_domain = super().get(db,domain.id) + if db_domain: + db_domain.tenantid = domain.tenantid + db_domain.name=domain.name + db_domain.url=domain.url + if db_domain.is_active == True and domain.is_active == False: + db_userdomains = dbuserdomain.get_default_domains(db,domain.id) + for userdomain in db_userdomains: + userdomain.active = False + db.add(userdomain) + db_domain.is_active=domain.is_active + db_domain.kintoneuser=domain.kintoneuser + if domain.kintonepwd != "" and domain.kintonepwd != None: + domain.encrypt_kintonepwd() + db_domain.kintonepwd = domain.kintonepwd + db_domain.updateuserid = userid + db.add(db_domain) + db.commit() + db.refresh(db_domain) + return db_domain + return None + + def add_userdomain(self,db: Session,ownerid:int,userid:int,domainid:int) -> schemas.DomainOut: + db_domain = super().get_by_conditions(db,{"id":domainid,"is_active":True}).first() + if db_domain: + db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid) + if not db_userdomain: + user_domain = models.UserDomain(userid = userid, domainid = domainid ,createuserid = ownerid,updateuserid = ownerid) + db.add(user_domain) + db.commit() + return db_domain + return None + + def add_userdomain_by_owner(self,db: Session,ownerid:int, userid:int,domainid:int) -> schemas.DomainOut: + db_domain = super().get_by_conditions(db,{"id":domainid,"ownerid":ownerid,"is_active":True}).first() + if db_domain: + db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid) + if not db_userdomain: + user_domain = models.UserDomain(userid = userid, domainid = domainid ,createuserid =ownerid,updateuserid = ownerid) + db.add(user_domain) + db.commit() + return db_domain + return None + + def delete_userdomain(self,db: Session, userid: int,domainid: int) -> schemas.DomainOut: + db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid) + if db_userdomain: + domain = db_userdomain.domain + db.delete(db_userdomain) + db.commit() + return domain + return None + + def get_default_domain(self,db: Session, userid: int) -> schemas.UserDomain: + return dbuserdomain.get_user_default_domain(db,userid) + + def set_default_domain(self,db: Session, userid: int,domainid: int): + db_domain =super().get_by_conditions(db,{"id":domainid,"is_active":True}).first() + if db_domain: + db_default_domain = dbuserdomain.get_user_default_domain(db,userid) + db_userdomain =dbuserdomain.get_userdomain(db,userid,domainid) + if db_default_domain: + db_default_domain.is_default = False + db_default_domain.updateuserid = userid + db.add(db_default_domain) + if db_userdomain: + db_userdomain.is_default = True + db_userdomain.updateuserid = userid + db.add(db_userdomain) + db.commit() + return db_domain + + def get_shareddomain_users(self,db: Session,ownerid:int,domainid: int) -> ApiReturnPage[models.Base]: + users = db.query(models.User).join(models.UserDomain,models.UserDomain.userid == models.User.id).filter(models.UserDomain.domainid ==domainid) + return paginate(users) + +dbdomain = dbdomain() \ No newline at end of file diff --git a/backend/app/db/models.py b/backend/app/db/models.py index 8433d46..ff38fd0 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -145,6 +145,7 @@ class Tenant(Base): startdate = Column(DateTime) enddate = Column(DateTime) + class Domain(Base): __tablename__ = "domain" @@ -153,6 +154,7 @@ class Domain(Base): url = Column(String(200), nullable=False) kintoneuser = Column(String(100), nullable=False) kintonepwd = Column(String(100), nullable=False) + is_active = Column(Boolean, default=True) def decrypt_kintonepwd(self): decrypted_pwd = chacha20Decrypt(self.kintonepwd) return decrypted_pwd @@ -162,14 +164,20 @@ class Domain(Base): createuser = relationship('User',foreign_keys=[createuserid]) updateuser = relationship('User',foreign_keys=[updateuserid]) owner = relationship('User',foreign_keys=[ownerid]) - is_active = Column(Boolean, default=True) + class UserDomain(Base): __tablename__ = "userdomain" userid = Column(Integer,ForeignKey("user.id")) domainid = Column(Integer,ForeignKey("domain.id")) - active = Column(Boolean, default=False) + is_default = Column(Boolean, default=False) + createuserid = Column(Integer,ForeignKey("user.id")) + updateuserid = Column(Integer,ForeignKey("user.id")) + domain = relationship("Domain") + user = relationship("User",foreign_keys=[userid]) + createuser = relationship('User',foreign_keys=[createuserid]) + updateuser = relationship('User',foreign_keys=[updateuserid]) class Event(Base): __tablename__ = "event" diff --git a/backend/app/db/schemas.py b/backend/app/db/schemas.py index c8e7d2d..5fc1f3b 100644 --- a/backend/app/db/schemas.py +++ b/backend/app/db/schemas.py @@ -28,9 +28,14 @@ class UserBase(BaseModel): first_name: str = None last_name: str = None roles:t.List[Role] = [] + -class UserOut(UserBase): - pass +class UserOut(BaseModel): + email: str + is_active: bool = True + is_superuser: bool = False + first_name: str = None + last_name: str = None class UserCreate(UserBase): @@ -149,9 +154,11 @@ class DomainIn(BaseModel): name: str url: str kintoneuser: str - kintonepwd: str + kintonepwd: t.Optional[str] = None is_active: bool - ownerid:int + createuserid:t.Optional[int] = None + updateuserid:t.Optional[int] = None + ownerid:t.Optional[int] = None def encrypt_kintonepwd(self): encrypted_pwd = chacha20Encrypt(self.kintonepwd) @@ -162,16 +169,18 @@ class DomainOut(BaseModel): tenantid: str name: str url: str + kintoneuser: str is_active: bool ownerid:int + class ConfigDict: + orm_mode = True -class ActiveDomain(BaseModel): +class UserDomain(BaseModel): id: int - tenantid: str - name: str - url: str - is_active: bool + is_default: bool + domain:DomainOut + user:UserOut class Domain(Base): id: int @@ -179,12 +188,14 @@ class Domain(Base): name: str url: str kintoneuser: str - kintonepwd: str is_active: bool + updateuser:UserOut owner:UserOut + class ConfigDict: orm_mode = True + class Event(Base): id: int category: str diff --git a/backend/app/main.py b/backend/app/main.py index e55b192..7aaf644 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,5 +1,6 @@ import os from fastapi import FastAPI, Depends +from fastapi_pagination import add_pagination from starlette.requests import Request import uvicorn from app.api.api_v1.routers.kintone import kinton_router @@ -18,6 +19,7 @@ from app.db.crud import create_log from fastapi.responses import JSONResponse import asyncio + Base.metadata.create_all(bind=engine) app = FastAPI( @@ -36,6 +38,8 @@ app.add_middleware( allow_headers=["*"], ) +add_pagination(app) + # @app.middleware("http") # async def db_session_middleware(request: Request, call_next): # request.state.db = SessionLocal()