diff --git a/backend/app/api/api_v1/routers/kintone.py b/backend/app/api/api_v1/routers/kintone.py index b1c826a..ff95f1f 100644 --- a/backend/app/api/api_v1/routers/kintone.py +++ b/backend/app/api/api_v1/routers/kintone.py @@ -9,15 +9,16 @@ import app.core.config as config import os from pathlib import Path from app.db.session import SessionLocal -from app.db.crud import get_flows_by_app,get_activedomain,get_kintoneformat +from app.db.crud import get_flows_by_app,get_kintoneformat from app.core.auth import get_current_active_user,get_current_user from app.core.apiexception import APIException +from app.db.cruddb.dbdomain import dbdomain kinton_router = r = APIRouter() def getkintoneenv(user = Depends(get_current_user)): db = SessionLocal() - domain = get_activedomain(db, user.id) + domain = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id) db.close() kintoneevn = config.KINTONE_ENV(domain) return kintoneevn diff --git a/backend/app/api/api_v1/routers/platform.py b/backend/app/api/api_v1/routers/platform.py index fcc2c9b..1d712fd 100644 --- a/backend/app/api/api_v1/routers/platform.py +++ b/backend/app/api/api_v1/routers/platform.py @@ -9,17 +9,30 @@ 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 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", - response_model=List[AppList], + "/apps",tags=["App"], + response_model=ApiReturnModel[List[AppList]|None], response_model_exclude_none=True, ) async def apps_list( @@ -28,8 +41,11 @@ async def apps_list( db=Depends(get_db), ): try: - - domain = get_activedomain(db, user.id) + + domain = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id) + if not domain: + return ApiReturnModel(data = None) + filtered_apps = [] platformapps = get_apps(db,domain.url) kintoneevn = config.KINTONE_ENV(domain) headers={config.API_V1_AUTH_KEY:kintoneevn.API_V1_AUTH_VALUE} @@ -48,15 +64,15 @@ async def apps_list( offset += limit kintone_apps_dict = {app['appId']: app for app in all_apps} - filtered_apps = [] for papp in platformapps: if papp.appid in kintone_apps_dict: papp.appname = kintone_apps_dict[papp.appid]["name"] filtered_apps.append(papp) - return filtered_apps + return ApiReturnModel(data = filtered_apps) except Exception as e: raise APIException('platform:apps',request.url._url,f"Error occurred while get apps:",e) + @r.post("/apps", response_model=AppList, response_model_exclude_none=True) async def apps_update( request: Request, @@ -192,7 +208,7 @@ async def flow_details( @r.get( "/flows/{appid}", - response_model=List[Flow], + response_model=List[Flow|None], response_model_exclude_none=True, ) async def flow_list( @@ -202,7 +218,9 @@ async def flow_list( db=Depends(get_db), ): try: - domain = get_activedomain(db, user.id) + domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id) + if not domain: + return [] print("domain=>",domain) flows = get_flows_by_app(db, domain.url, appid) return flows @@ -210,7 +228,7 @@ async def flow_list( raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e) -@r.post("/flow", response_model=Flow, response_model_exclude_none=True) +@r.post("/flow", response_model=Flow|None, response_model_exclude_none=True) async def flow_create( request: Request, flow: FlowIn, @@ -218,14 +236,16 @@ async def flow_create( db=Depends(get_db), ): try: - domain = get_activedomain(db, user.id) + domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id) + if not domain: + return None return create_flow(db, domain.url, flow) except Exception as e: raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e) @r.put( - "/flow/{flowid}", response_model=Flow, response_model_exclude_none=True + "/flow/{flowid}", response_model=Flow|None, response_model_exclude_none=True ) async def flow_edit( request: Request, @@ -235,7 +255,9 @@ async def flow_edit( db=Depends(get_db), ): try: - domain = get_activedomain(db, user.id) + domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id) + if not domain: + return None return edit_flow(db,domain.url, flow,user.id) except Exception as e: raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e) @@ -255,8 +277,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 +288,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 +305,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 +322,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[DomainOut|None], response_model_exclude_none=True, ) async def domain_delete( @@ -311,8 +338,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 +360,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 +373,48 @@ 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) - + return ApiReturnModel(data =dbdomain.get_default_domain(db, user.id)) 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 +425,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/api/api_v1/routers/users.py b/backend/app/api/api_v1/routers/users.py index 8cb1710..d22860f 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 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/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/auth.py b/backend/app/core/auth.py index 95e32ea..e632d47 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 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/core/common.py b/backend/app/core/common.py index 60ed5f6..953caaa 100644 --- a/backend/app/core/common.py +++ b/backend/app/core/common.py @@ -1,10 +1,49 @@ +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 Any, Generic, List, Type,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 ApiReturnError(BaseModel): + code:int = -1 + msg:str ="" + + +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],params:Params,**kwargs: Any) -> Type[Page[T]]: + total = kwargs.get('total', 0) + 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/crud.py b/backend/app/db/crud.py index 565479b..120405b 100644 --- a/backend/app/db/crud.py +++ b/backend/app/db/crud.py @@ -25,7 +25,7 @@ def get_allusers( return db.query(models.User).all() def get_users( - db: Session, super:bool + db: Session ) -> t.List[schemas.UserOut]: return db.query(models.User).filter(models.User.is_superuser == False) @@ -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/__init__.py b/backend/app/db/cruddb/__init__.py new file mode 100644 index 0000000..1bf9f7d --- /dev/null +++ b/backend/app/db/cruddb/__init__.py @@ -0,0 +1,2 @@ +from app.db.cruddb.dbuser import dbuser +from app.db.cruddb.dbdomain import dbdomain \ No newline at end of file diff --git a/backend/app/db/cruddb/crudbase.py b/backend/app/db/cruddb/crudbase.py new file mode 100644 index 0000000..f7b05ce --- /dev/null +++ b/backend/app/db/cruddb/crudbase.py @@ -0,0 +1,104 @@ +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 sqlalchemy import and_ ,or_ +from pydantic import BaseModel +from app.db import models + +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 == '<': + 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) -> Query: + return 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..2acf05f --- /dev/null +++ b/backend/app/db/cruddb/dbdomain.py @@ -0,0 +1,139 @@ +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 paginate(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.is_default = 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.DomainOut: + userdomain = dbuserdomain.get_user_default_domain(db,userid) + if userdomain: + return userdomain.domain + else: + return None + + 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/cruddb/dbuser.py b/backend/app/db/cruddb/dbuser.py new file mode 100644 index 0000000..41e128b --- /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(db)) + + 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_all(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 8433d46..04ba1e3 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -1,6 +1,5 @@ from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table -from sqlalchemy.ext.declarative import as_declarative -from sqlalchemy.orm import relationship +from sqlalchemy.orm import relationship,as_declarative from datetime import datetime from app.db.session import Base from app.core.security import chacha20Decrypt @@ -35,6 +34,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 +46,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") @@ -145,6 +149,7 @@ class Tenant(Base): startdate = Column(DateTime) enddate = Column(DateTime) + class Domain(Base): __tablename__ = "domain" @@ -153,6 +158,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 +168,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..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,19 +31,28 @@ 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(UserBase): - pass +class UserOut(BaseModel): + id: int + email: str + is_active: bool = True + is_superuser: bool = False + first_name: str = None + last_name: str = None 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 @@ -47,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 @@ -149,9 +164,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 +179,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 +198,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/db/session.py b/backend/app/db/session.py index d7e2f6c..148c9a4 100644 --- a/backend/app/db/session.py +++ b/backend/app/db/session.py @@ -1,6 +1,5 @@ from sqlalchemy import create_engine -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import sessionmaker +from sqlalchemy.orm import sessionmaker,declarative_base from app.core import config diff --git a/backend/app/main.py b/backend/app/main.py index e55b192..0f4c089 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 @@ -14,14 +15,22 @@ from app import tasks from fastapi.middleware.cors import CORSMiddleware import logging from app.core.apiexception import APIException, writedblog +from app.core.common import ApiReturnError from app.db.crud import create_log from fastapi.responses import JSONResponse import asyncio +from contextlib import asynccontextmanager + Base.metadata.create_all(bind=engine) +@asynccontextmanager +async def lifespan(app: FastAPI): + startup_event() + yield + app = FastAPI( - title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api" + title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api",lifespan=lifespan ) origins = [ @@ -36,6 +45,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() @@ -43,12 +54,11 @@ app.add_middleware( # request.state.db.close() # return response -@app.on_event("startup") -async def startup_event(): +def startup_event(): log_dir="log" if not os.path.exists(log_dir): os.makedirs(log_dir) - + logger = logging.getLogger("uvicorn.access") handler = logging.handlers.RotatingFileHandler(f"{log_dir}/api.log",mode="a",maxBytes = 100*1024, backupCount = 3) handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) @@ -60,7 +70,7 @@ async def api_exception_handler(request: Request, exc: APIException): loop.run_in_executor(None,writedblog,exc) return JSONResponse( status_code=exc.status_code, - content={"detail": f"{exc.detail}"}, + content= ApiReturnError(msg = f"{exc.detail}").model_dump(), ) @app.get("/api/v1") diff --git a/backend/app/tests/conftest.py b/backend/app/tests/conftest.py new file mode 100644 index 0000000..ab33ff6 --- /dev/null +++ b/backend/app/tests/conftest.py @@ -0,0 +1,145 @@ +import pytest +from sqlalchemy import create_engine, event +from sqlalchemy.orm import sessionmaker +from fastapi.testclient import TestClient +import typing as t + +from app.core import config, security +from app.db.session import Base, get_db +from app.db import models +from app.main import app + + +def get_test_db_url() -> str: + return f"{config.SQLALCHEMY_DATABASE_URI}" + + +@pytest.fixture +def test_db(): + """ + Modify the db session to automatically roll back after each test. + This is to avoid tests affecting the database state of other tests. + """ + # Connect to the test database + engine = create_engine( + get_test_db_url(), + ) + + connection = engine.connect() + trans = connection.begin() + + # Run a parent transaction that can roll back all changes + test_session_maker = sessionmaker( + autocommit=False, autoflush=False, bind=engine + ) + test_session = test_session_maker() + #test_session.begin_nested() + + # @event.listens_for(test_session, "after_transaction_end") + # def restart_savepoint(s, transaction): + # if transaction.nested and not transaction._parent.nested: + # s.expire_all() + # s.begin_nested() + + yield test_session + + # Roll back the parent transaction after the test is complete + test_session.close() + trans.rollback() + connection.close() + + +@pytest.fixture(scope="function") +def test_client(test_db): + """ + Get a TestClient instance that reads/write to the test database. + """ + + def get_test_db(): + yield test_db + + app.dependency_overrides[get_db] = get_test_db + with TestClient(app) as test_client: + yield test_client + + +# @pytest.fixture +# def test_password() -> str: +# return "securepassword" + + +# def get_password_hash() -> str: +# """ +# Password hashing can be expensive so a mock will be much faster +# """ +# return "supersecrethash" + + +# @pytest.fixture +# def test_user(test_db) -> models.User: +# """ +# Make a test user in the database +# """ + +# user = models.User( +# email="fake@email.com", +# hashed_password=get_password_hash(), +# is_active=True, +# ) +# test_db.add(user) +# test_db.commit() +# return user + + +# @pytest.fixture +# def test_superuser(test_db) -> models.User: +# """ +# Superuser for testing +# """ + +# user = models.User( +# email="fakeadmin@email.com", +# hashed_password=get_password_hash(), +# is_superuser=True, +# ) +# test_db.add(user) +# test_db.commit() +# return user + + +# def verify_password_mock(first: str, second: str) -> bool: +# return True + + +# @pytest.fixture +# def user_token_headers( +# client: TestClient, test_user, test_password, monkeypatch +# ) -> t.Dict[str, str]: +# monkeypatch.setattr(security, "verify_password", verify_password_mock) + +# login_data = { +# "username": test_user.email, +# "password": test_password, +# } +# r = client.post("/api/token", data=login_data) +# tokens = r.json() +# a_token = tokens["access_token"] +# headers = {"Authorization": f"Bearer {a_token}"} +# return headers + + +# @pytest.fixture +# def superuser_token_headers( +# client: TestClient, test_superuser, test_password, monkeypatch +# ) -> t.Dict[str, str]: +# monkeypatch.setattr(security, "verify_password", verify_password_mock) + +# login_data = { +# "username": test_superuser.email, +# "password": test_password, +# } +# r = client.post("/api/token", data=login_data) +# tokens = r.json() +# a_token = tokens["access_token"] +# headers = {"Authorization": f"Bearer {a_token}"} +# return headers diff --git a/backend/app/tests/test_main.py b/backend/app/tests/test_main.py index 024cf9e..8d844d7 100644 --- a/backend/app/tests/test_main.py +++ b/backend/app/tests/test_main.py @@ -1,4 +1,6 @@ -def test_read_main(client): - response = client.get("/api/v1") + + +def test_read_main(test_client): + response = test_client.get("/api/v1") assert response.status_code == 200 - assert response.json() == {"message": "Hello World"} + assert response.json() == {"message": "success"} diff --git a/backend/conftest.py b/backend/conftest.py deleted file mode 100644 index ecd831d..0000000 --- a/backend/conftest.py +++ /dev/null @@ -1,169 +0,0 @@ -import pytest -from sqlalchemy import create_engine, event -from sqlalchemy.orm import sessionmaker -from sqlalchemy_utils import database_exists, create_database, drop_database -from fastapi.testclient import TestClient -import typing as t - -from app.core import config, security -from app.db.session import Base, get_db -from app.db import models -from app.main import app - - -def get_test_db_url() -> str: - return f"{config.SQLALCHEMY_DATABASE_URI}_test" - - -@pytest.fixture -def test_db(): - """ - Modify the db session to automatically roll back after each test. - This is to avoid tests affecting the database state of other tests. - """ - # Connect to the test database - engine = create_engine( - get_test_db_url(), - ) - - connection = engine.connect() - trans = connection.begin() - - # Run a parent transaction that can roll back all changes - test_session_maker = sessionmaker( - autocommit=False, autoflush=False, bind=engine - ) - test_session = test_session_maker() - test_session.begin_nested() - - @event.listens_for(test_session, "after_transaction_end") - def restart_savepoint(s, transaction): - if transaction.nested and not transaction._parent.nested: - s.expire_all() - s.begin_nested() - - yield test_session - - # Roll back the parent transaction after the test is complete - test_session.close() - trans.rollback() - connection.close() - - -@pytest.fixture(scope="session", autouse=True) -def create_test_db(): - """ - Create a test database and use it for the whole test session. - """ - - test_db_url = get_test_db_url() - - # Create the test database - assert not database_exists( - test_db_url - ), "Test database already exists. Aborting tests." - create_database(test_db_url) - test_engine = create_engine(test_db_url) - Base.metadata.create_all(test_engine) - - # Run the tests - yield - - # Drop the test database - drop_database(test_db_url) - - -@pytest.fixture -def client(test_db): - """ - Get a TestClient instance that reads/write to the test database. - """ - - def get_test_db(): - yield test_db - - app.dependency_overrides[get_db] = get_test_db - - yield TestClient(app) - - -@pytest.fixture -def test_password() -> str: - return "securepassword" - - -def get_password_hash() -> str: - """ - Password hashing can be expensive so a mock will be much faster - """ - return "supersecrethash" - - -@pytest.fixture -def test_user(test_db) -> models.User: - """ - Make a test user in the database - """ - - user = models.User( - email="fake@email.com", - hashed_password=get_password_hash(), - is_active=True, - ) - test_db.add(user) - test_db.commit() - return user - - -@pytest.fixture -def test_superuser(test_db) -> models.User: - """ - Superuser for testing - """ - - user = models.User( - email="fakeadmin@email.com", - hashed_password=get_password_hash(), - is_superuser=True, - ) - test_db.add(user) - test_db.commit() - return user - - -def verify_password_mock(first: str, second: str) -> bool: - return True - - -@pytest.fixture -def user_token_headers( - client: TestClient, test_user, test_password, monkeypatch -) -> t.Dict[str, str]: - monkeypatch.setattr(security, "verify_password", verify_password_mock) - - login_data = { - "username": test_user.email, - "password": test_password, - } - r = client.post("/api/token", data=login_data) - tokens = r.json() - a_token = tokens["access_token"] - headers = {"Authorization": f"Bearer {a_token}"} - return headers - - -@pytest.fixture -def superuser_token_headers( - client: TestClient, test_superuser, test_password, monkeypatch -) -> t.Dict[str, str]: - monkeypatch.setattr(security, "verify_password", verify_password_mock) - - login_data = { - "username": test_superuser.email, - "password": test_password, - } - r = client.post("/api/token", data=login_data) - tokens = r.json() - a_token = tokens["access_token"] - headers = {"Authorization": f"Bearer {a_token}"} - return headers diff --git a/backend/requirements.txt b/backend/requirements.txt index 5e01eb3..e9ec467 100644 Binary files a/backend/requirements.txt and b/backend/requirements.txt differ diff --git a/frontend/src/components/DomainSelect.vue b/frontend/src/components/DomainSelect.vue index e598f57..35cae98 100644 --- a/frontend/src/components/DomainSelect.vue +++ b/frontend/src/components/DomainSelect.vue @@ -33,10 +33,10 @@ export default { const columns = [ { name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass }, - { name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true, classes: inactiveRowClass}, - { name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true, classes: inactiveRowClass }, - { name: 'url', label: 'URL', field: 'url', sortable: true, classes: inactiveRowClass }, - { name: 'user', label: 'アカウント', field: 'user', classes: inactiveRowClass } + { name: 'name', align: 'left', label: 'ドメイン', field: 'name', sortable: true, classes: inactiveRowClass }, + { name: 'url', label: 'URL', field: 'url',align: 'left', sortable: true, classes: inactiveRowClass }, + { name: 'user', label: 'アカウント', field: 'user',align: 'left', classes: inactiveRowClass }, + { name: 'owner', label: '所有者', field: row => row.owner.fullName, align: 'left', classes: inactiveRowClass }, ] const rows = reactive([]); @@ -51,6 +51,16 @@ export default { name: data.name, url: data.url, user: data.kintoneuser, + owner: { + id: data.owner.id, + firstName: data.owner.first_name, + lastName: data.owner.last_name, + fullNameSearch: (data.owner.last_name + data.owner.first_name).toLowerCase(), + fullName: data.owner.last_name + ' ' + data.owner.first_name, + email: data.owner.email, + isActive: data.owner.is_active, + isSuperuser: data.owner.is_superuser, + } } if (props.filterInitRowsFunc && !props.filterInitRowsFunc(item)) { return; diff --git a/frontend/src/components/ShareDomain/ShareDomainDialog.vue b/frontend/src/components/ShareDomain/ShareDomainDialog.vue new file mode 100644 index 0000000..580aee5 --- /dev/null +++ b/frontend/src/components/ShareDomain/ShareDomainDialog.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/frontend/src/components/ShareDomain/SharingUserList.vue b/frontend/src/components/ShareDomain/SharingUserList.vue new file mode 100644 index 0000000..8c244a5 --- /dev/null +++ b/frontend/src/components/ShareDomain/SharingUserList.vue @@ -0,0 +1,45 @@ + + \ No newline at end of file diff --git a/frontend/src/components/ShowDialog.vue b/frontend/src/components/ShowDialog.vue index 50641db..5f6098e 100644 --- a/frontend/src/components/ShowDialog.vue +++ b/frontend/src/components/ShowDialog.vue @@ -13,7 +13,7 @@ - + diff --git a/frontend/src/components/UserDomain/DomainCard.vue b/frontend/src/components/UserDomain/DomainCard.vue new file mode 100644 index 0000000..d6f0914 --- /dev/null +++ b/frontend/src/components/UserDomain/DomainCard.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/frontend/src/components/UserList.vue b/frontend/src/components/UserList.vue index 64b3b35..96d5646 100644 --- a/frontend/src/components/UserList.vue +++ b/frontend/src/components/UserList.vue @@ -9,8 +9,8 @@ import { api } from 'boot/axios'; const props = defineProps<{filter:string}>() const columns = [ { name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true }, - { name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true }, - { name: 'lastName', label: '苗字', field: 'lastName', align: 'left', sortable: true }, + { name: 'lastName', label: '氏名', field: 'lastName', align: 'left', sortable: true }, + { name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true }, { name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true }, ]; @@ -24,7 +24,7 @@ defineExpose({ const getUsers = async (filter = () => true) => { loading.value = true; const result = await api.get(`api/v1/users`); - rows.value = result.data.map((item) => { + rows.value = result.data.data.map((item) => { return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active } }).filter(filter); loading.value = false; diff --git a/frontend/src/components/main/domainTable.vue b/frontend/src/components/main/domainTable.vue new file mode 100644 index 0000000..23bf91d --- /dev/null +++ b/frontend/src/components/main/domainTable.vue @@ -0,0 +1,33 @@ + + diff --git a/frontend/src/pages/AppManagement.vue b/frontend/src/pages/AppManagement.vue index 5698d48..ec5fbb6 100644 --- a/frontend/src/pages/AppManagement.vue +++ b/frontend/src/pages/AppManagement.vue @@ -16,6 +16,11 @@ + @@ -49,6 +74,7 @@ diff --git a/frontend/src/pages/UserDomain.vue b/frontend/src/pages/UserDomain.vue index 105a27a..8dd1062 100644 --- a/frontend/src/pages/UserDomain.vue +++ b/frontend/src/pages/UserDomain.vue @@ -6,23 +6,14 @@ - +