source merge
This commit is contained in:
@@ -9,15 +9,16 @@ import app.core.config as config
|
|||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from app.db.session import SessionLocal
|
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.auth import get_current_active_user,get_current_user
|
||||||
from app.core.apiexception import APIException
|
from app.core.apiexception import APIException
|
||||||
|
from app.db.cruddb.dbdomain import dbdomain
|
||||||
|
|
||||||
kinton_router = r = APIRouter()
|
kinton_router = r = APIRouter()
|
||||||
|
|
||||||
def getkintoneenv(user = Depends(get_current_user)):
|
def getkintoneenv(user = Depends(get_current_user)):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
domain = get_activedomain(db, user.id)
|
domain = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id)
|
||||||
db.close()
|
db.close()
|
||||||
kintoneevn = config.KINTONE_ENV(domain)
|
kintoneevn = config.KINTONE_ENV(domain)
|
||||||
return kintoneevn
|
return kintoneevn
|
||||||
|
|||||||
@@ -9,17 +9,30 @@ from app.db.schemas import *
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
from app.core.auth import get_current_active_user,get_current_user
|
from app.core.auth import get_current_active_user,get_current_user
|
||||||
from app.core.apiexception import APIException
|
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 httpx
|
||||||
import app.core.config as config
|
import app.core.config as config
|
||||||
|
|
||||||
platform_router = r = APIRouter()
|
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(
|
@r.get(
|
||||||
"/apps",
|
"/apps",tags=["App"],
|
||||||
response_model=List[AppList],
|
response_model=ApiReturnModel[List[AppList]|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def apps_list(
|
async def apps_list(
|
||||||
@@ -28,8 +41,11 @@ async def apps_list(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
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)
|
platformapps = get_apps(db,domain.url)
|
||||||
kintoneevn = config.KINTONE_ENV(domain)
|
kintoneevn = config.KINTONE_ENV(domain)
|
||||||
headers={config.API_V1_AUTH_KEY:kintoneevn.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:kintoneevn.API_V1_AUTH_VALUE}
|
||||||
@@ -48,15 +64,15 @@ async def apps_list(
|
|||||||
offset += limit
|
offset += limit
|
||||||
|
|
||||||
kintone_apps_dict = {app['appId']: app for app in all_apps}
|
kintone_apps_dict = {app['appId']: app for app in all_apps}
|
||||||
filtered_apps = []
|
|
||||||
for papp in platformapps:
|
for papp in platformapps:
|
||||||
if papp.appid in kintone_apps_dict:
|
if papp.appid in kintone_apps_dict:
|
||||||
papp.appname = kintone_apps_dict[papp.appid]["name"]
|
papp.appname = kintone_apps_dict[papp.appid]["name"]
|
||||||
filtered_apps.append(papp)
|
filtered_apps.append(papp)
|
||||||
return filtered_apps
|
return ApiReturnModel(data = filtered_apps)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:apps',request.url._url,f"Error occurred while get apps:",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)
|
@r.post("/apps", response_model=AppList, response_model_exclude_none=True)
|
||||||
async def apps_update(
|
async def apps_update(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -192,7 +208,7 @@ async def flow_details(
|
|||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/flows/{appid}",
|
"/flows/{appid}",
|
||||||
response_model=List[Flow],
|
response_model=List[Flow|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def flow_list(
|
async def flow_list(
|
||||||
@@ -202,7 +218,9 @@ async def flow_list(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
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)
|
print("domain=>",domain)
|
||||||
flows = get_flows_by_app(db, domain.url, appid)
|
flows = get_flows_by_app(db, domain.url, appid)
|
||||||
return flows
|
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)
|
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(
|
async def flow_create(
|
||||||
request: Request,
|
request: Request,
|
||||||
flow: FlowIn,
|
flow: FlowIn,
|
||||||
@@ -218,14 +236,16 @@ async def flow_create(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
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)
|
return create_flow(db, domain.url, flow)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.put(
|
@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(
|
async def flow_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -235,7 +255,9 @@ async def flow_edit(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
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)
|
return edit_flow(db,domain.url, flow,user.id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",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)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/domains",
|
"/domains",tags=["Domain"],
|
||||||
response_model=ApiReturnModel[List[Domain]],
|
response_model=ApiReturnPage[Domain],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def domain_details(
|
async def domain_details(
|
||||||
@@ -266,14 +288,16 @@ async def domain_details(
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
domains =get_alldomains(db)
|
domains = dbdomain.get_domains(db)
|
||||||
else:
|
else:
|
||||||
domains = get_domains(db,user.id)
|
domains = dbdomain.get_domains_by_owner(db,user.id)
|
||||||
return ApiReturnModel(data = domains)
|
return domains
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",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(
|
async def domain_create(
|
||||||
request: Request,
|
request: Request,
|
||||||
domain: DomainIn,
|
domain: DomainIn,
|
||||||
@@ -281,13 +305,15 @@ async def domain_create(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return ApiReturnModel(data = create_domain(db, domain,user.id))
|
return ApiReturnModel(data = dbdomain.create_domain(db, domain,user.id))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.put(
|
@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(
|
async def domain_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -296,13 +322,14 @@ async def domain_edit(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return ApiReturnModel(data = edit_domain(db, domain,user.id))
|
return ApiReturnModel(data = dbdomain.edit_domain(db, domain,user.id))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.delete(
|
@r.delete(
|
||||||
"/domain/{id}",
|
"/domain/{id}",tags=["Domain"],
|
||||||
|
response_model=ApiReturnModel[DomainOut|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def domain_delete(
|
async def domain_delete(
|
||||||
@@ -311,8 +338,7 @@ async def domain_delete(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if delete_domain(db,id):
|
return ApiReturnModel(data = dbdomain.delete_domain(db,id))
|
||||||
return ApiReturnModel(data = None)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",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)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
|
||||||
|
|
||||||
@r.post(
|
@r.post(
|
||||||
"/domain/{userid}",
|
"/domain/{userid}",tags=["Domain"],
|
||||||
response_model=ApiReturnModel[DomainOut|None],
|
response_model=ApiReturnModel[DomainOut|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
@@ -347,52 +373,48 @@ async def create_userdomain(
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
domain = add_admindomain(db,userid,domainid)
|
domain = dbdomain.add_userdomain(db,user.id,userid,domainid)
|
||||||
else:
|
else:
|
||||||
domain = add_userdomain(db,user.id,userid,domainid)
|
domain = dbdomain.add_userdomain_by_owner(db,user.id,userid,domainid)
|
||||||
return ApiReturnModel(data = domain)
|
return ApiReturnModel(data = domain)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
|
||||||
|
|
||||||
@r.delete(
|
@r.delete(
|
||||||
"/domain/{domainid}/{userid}",
|
"/domain/{domainid}/{userid}",tags=["Domain"],
|
||||||
|
response_model=ApiReturnModel[DomainOut|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def userdomain_delete(
|
async def delete_userdomain(
|
||||||
request: Request,
|
request: Request,
|
||||||
domainid:int,
|
domainid:int,
|
||||||
userid: int,
|
userid: int,
|
||||||
|
user=Depends(get_current_active_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
if delete_userdomain(db, userid,domainid):
|
return ApiReturnModel(data = dbdomain.delete_userdomain(db,userid,domainid))
|
||||||
return ApiReturnModel(data = None)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
|
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/activedomain",
|
"/defaultdomain",tags=["Domain"],
|
||||||
response_model=ApiReturnModel[ActiveDomain|None],
|
response_model=ApiReturnModel[DomainOut|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def get_activeuserdomain(
|
async def get_defaultuserdomain(
|
||||||
request: Request,
|
request: Request,
|
||||||
user=Depends(get_current_active_user),
|
user=Depends(get_current_active_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# domain = get_activedomain(db, user.id)
|
return ApiReturnModel(data =dbdomain.get_default_domain(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)
|
|
||||||
|
|
||||||
except Exception as e:
|
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(
|
@r.put(
|
||||||
"/activedomain/{domainid}",
|
"/defaultdomain/{domainid}",tags=["Domain"],
|
||||||
response_model=ApiReturnModel[DomainOut|None],
|
response_model=ApiReturnModel[DomainOut|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
@@ -403,10 +425,28 @@ async def update_activeuserdomain(
|
|||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domain = active_userdomain(db,user.id,domainid)
|
domain = dbdomain.set_default_domain(db,user.id,domainid)
|
||||||
return ApiReturnModel(data= domain)
|
return ApiReturnModel(data= domain)
|
||||||
except Exception as e:
|
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(
|
@r.get(
|
||||||
"/events",
|
"/events",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from fastapi import APIRouter, Request, Depends, Response, Security, encoders
|
from fastapi import APIRouter, Request, Depends, Response, Security, encoders
|
||||||
import typing as t
|
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.session import get_db
|
||||||
from app.db.crud import (
|
from app.db.crud import (
|
||||||
get_allusers,
|
get_allusers,
|
||||||
@@ -12,129 +13,174 @@ from app.db.crud import (
|
|||||||
assign_userrole,
|
assign_userrole,
|
||||||
get_roles,
|
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.core.auth import get_current_user,get_current_active_user, get_current_active_superuser
|
||||||
|
from app.db.cruddb import dbuser
|
||||||
|
|
||||||
users_router = r = APIRouter()
|
users_router = r = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/users",
|
"/users",tags=["User"],
|
||||||
response_model=t.List[User],
|
response_model=ApiReturnPage[User],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def users_list(
|
async def users_list(
|
||||||
response: Response,
|
request: Request,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_user),
|
current_user=Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""
|
try:
|
||||||
Get all users
|
if current_user.is_superuser:
|
||||||
"""
|
users = dbuser.get_users(db)
|
||||||
if current_user.is_superuser:
|
else:
|
||||||
users = get_allusers(db)
|
users = dbuser.get_users_not_admin(db)
|
||||||
else:
|
return users
|
||||||
users = get_users(db)
|
except Exception as e:
|
||||||
# This is necessary for react-admin to work
|
raise APIException('user:users',request.url._url,f"Error occurred while get user list",e)
|
||||||
#response.headers["Content-Range"] = f"0-9/{len(users)}"
|
|
||||||
return users
|
|
||||||
|
|
||||||
|
@r.get("/users/me", tags=["User"],
|
||||||
@r.get("/users/me", response_model=User, response_model_exclude_none=True)
|
response_model=ApiReturnModel[User],
|
||||||
|
response_model_exclude_none=True,
|
||||||
|
)
|
||||||
async def user_me(current_user=Depends(get_current_active_user)):
|
async def user_me(current_user=Depends(get_current_active_user)):
|
||||||
"""
|
return ApiReturnModel(data = current_user)
|
||||||
Get own user
|
|
||||||
"""
|
|
||||||
return current_user
|
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/users/{user_id}",
|
"/users/{user_id}",tags=["User"],
|
||||||
response_model=User,
|
response_model=ApiReturnModel[User|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def user_details(
|
async def user_details(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_superuser),
|
current_user=Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""
|
try:
|
||||||
Get any user details
|
user = dbuser.get(db, user_id)
|
||||||
"""
|
if user:
|
||||||
user = get_user(db, user_id)
|
if user.is_superuser and not current_user.is_superuser:
|
||||||
return user
|
user = None
|
||||||
# return encoders.jsonable_encoder(
|
return ApiReturnModel(data = user)
|
||||||
# user, skip_defaults=True, exclude_none=True,
|
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(
|
async def user_create(
|
||||||
request: Request,
|
request: Request,
|
||||||
user: UserCreate,
|
user: UserCreate,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_superuser),
|
current_user=Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""
|
try:
|
||||||
Create a new user
|
if user.is_superuser and not current_user.is_superuser:
|
||||||
"""
|
return ApiReturnModel(data = None)
|
||||||
return create_user(db, user)
|
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(
|
@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(
|
async def user_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
user: UserEdit,
|
user: UserEdit,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_superuser),
|
current_user=Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""
|
try:
|
||||||
Update existing user
|
if user.is_superuser and not current_user.is_superuser:
|
||||||
"""
|
return ApiReturnModel(data = None)
|
||||||
return edit_user(db, user_id, user)
|
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(
|
@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(
|
async def user_delete(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_superuser),
|
current_user=Depends(get_current_active_user),
|
||||||
):
|
):
|
||||||
"""
|
try:
|
||||||
Delete existing user
|
user = dbuser.get(db,user_id)
|
||||||
"""
|
if user.is_superuser and not current_user.is_superuser:
|
||||||
return delete_user(db, user_id)
|
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",
|
@r.post("/userrole",tags=["User"],
|
||||||
response_model=User,
|
response_model=ApiReturnModel[User],
|
||||||
response_model_exclude_none=True,)
|
response_model_exclude_none=True,)
|
||||||
async def assign_role(
|
async def assign_role(
|
||||||
request: Request,
|
request: Request,
|
||||||
userid:int,
|
user_id:int,
|
||||||
roles:t.List[int],
|
roles:t.List[int],
|
||||||
db=Depends(get_db)
|
db=Depends(get_db)
|
||||||
):
|
):
|
||||||
|
try:
|
||||||
return assign_userrole(db,userid,roles)
|
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(
|
@r.get(
|
||||||
"/roles",
|
"/roles",tags=["User"],
|
||||||
response_model=t.List[Role],
|
response_model=ApiReturnModel[t.List[RoleBase]|None],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def roles_list(
|
async def roles_list(
|
||||||
response: Response,
|
request: Request,
|
||||||
db=Depends(get_db),
|
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)
|
try:
|
||||||
return roles
|
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)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class APIException(Exception):
|
|||||||
elif hasattr(e, 'detail'):
|
elif hasattr(e, 'detail'):
|
||||||
self.detail = e.detail
|
self.detail = e.detail
|
||||||
self.status_code = e.status_code if hasattr(e, 'status_code') else 500
|
self.status_code = e.status_code if hasattr(e, 'status_code') else 500
|
||||||
content += e.detail
|
content += str(e.detail)
|
||||||
else:
|
else:
|
||||||
self.detail = str(e)
|
self.detail = str(e)
|
||||||
self.status_code = 500
|
self.status_code = 500
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from jwt import PyJWTError
|
|||||||
from app.db import models, schemas, session
|
from app.db import models, schemas, session
|
||||||
from app.db.crud import get_user_by_email, create_user,get_user
|
from app.db.crud import get_user_by_email, create_user,get_user
|
||||||
from app.core import security
|
from app.core import security
|
||||||
|
from app.db.cruddb import dbuser
|
||||||
|
|
||||||
async def get_current_user(security_scopes: SecurityScopes,
|
async def get_current_user(security_scopes: SecurityScopes,
|
||||||
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
|
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)
|
token_data = schemas.TokenData(id = id, permissions=permissions)
|
||||||
except PyJWTError:
|
except PyJWTError:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
user = get_user(db, token_data.id)
|
user = dbuser.get_user(db, token_data.id)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
return user
|
return user
|
||||||
|
|||||||
@@ -1,10 +1,49 @@
|
|||||||
|
|
||||||
|
import math
|
||||||
|
from fastapi import Query
|
||||||
|
from fastapi_pagination.bases import AbstractPage,AbstractParams,RawParams
|
||||||
from pydantic import BaseModel
|
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')
|
T = TypeVar('T')
|
||||||
|
|
||||||
class ApiReturnModel(BaseModel,Generic[T]):
|
class ApiReturnModel(BaseModel,Generic[T]):
|
||||||
code:int = 0
|
code:int = 0
|
||||||
msg:str ="OK"
|
msg:str ="OK"
|
||||||
data:T
|
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)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ def get_allusers(
|
|||||||
return db.query(models.User).all()
|
return db.query(models.User).all()
|
||||||
|
|
||||||
def get_users(
|
def get_users(
|
||||||
db: Session, super:bool
|
db: Session
|
||||||
) -> t.List[schemas.UserOut]:
|
) -> t.List[schemas.UserOut]:
|
||||||
return db.query(models.User).filter(models.User.is_superuser == False)
|
return db.query(models.User).filter(models.User.is_superuser == False)
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ def edit_user(
|
|||||||
|
|
||||||
def get_roles(
|
def get_roles(
|
||||||
db: Session
|
db: Session
|
||||||
) -> t.List[schemas.Role]:
|
) -> t.List[schemas.RoleBase]:
|
||||||
return db.query(models.Role).all()
|
return db.query(models.Role).all()
|
||||||
|
|
||||||
def assign_userrole( db: Session, user_id: int, roles: t.List[int]):
|
def assign_userrole( db: Session, user_id: int, roles: t.List[int]):
|
||||||
|
|||||||
2
backend/app/db/cruddb/__init__.py
Normal file
2
backend/app/db/cruddb/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from app.db.cruddb.dbuser import dbuser
|
||||||
|
from app.db.cruddb.dbdomain import dbdomain
|
||||||
104
backend/app/db/cruddb/crudbase.py
Normal file
104
backend/app/db/cruddb/crudbase.py
Normal file
@@ -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
|
||||||
139
backend/app/db/cruddb/dbdomain.py
Normal file
139
backend/app/db/cruddb/dbdomain.py
Normal file
@@ -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()
|
||||||
92
backend/app/db/cruddb/dbuser.py
Normal file
92
backend/app/db/cruddb/dbuser.py
Normal file
@@ -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()
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table
|
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table
|
||||||
from sqlalchemy.ext.declarative import as_declarative
|
from sqlalchemy.orm import relationship,as_declarative
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from app.db.session import Base
|
from app.db.session import Base
|
||||||
from app.core.security import chacha20Decrypt
|
from app.core.security import chacha20Decrypt
|
||||||
@@ -35,6 +34,10 @@ class User(Base):
|
|||||||
hashed_password = Column(String(200), nullable=False)
|
hashed_password = Column(String(200), nullable=False)
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
is_superuser = Column(Boolean, default=False)
|
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")
|
roles = relationship("Role",secondary=userrole,back_populates="users")
|
||||||
|
|
||||||
|
|
||||||
@@ -43,6 +46,7 @@ class Role(Base):
|
|||||||
|
|
||||||
name = Column(String(100))
|
name = Column(String(100))
|
||||||
description = Column(String(255))
|
description = Column(String(255))
|
||||||
|
level = Column(Integer)
|
||||||
users = relationship("User",secondary=userrole,back_populates="roles")
|
users = relationship("User",secondary=userrole,back_populates="roles")
|
||||||
permissions = relationship("Permission",secondary=rolepermission,back_populates="roles")
|
permissions = relationship("Permission",secondary=rolepermission,back_populates="roles")
|
||||||
|
|
||||||
@@ -145,6 +149,7 @@ class Tenant(Base):
|
|||||||
startdate = Column(DateTime)
|
startdate = Column(DateTime)
|
||||||
enddate = Column(DateTime)
|
enddate = Column(DateTime)
|
||||||
|
|
||||||
|
|
||||||
class Domain(Base):
|
class Domain(Base):
|
||||||
__tablename__ = "domain"
|
__tablename__ = "domain"
|
||||||
|
|
||||||
@@ -153,6 +158,7 @@ class Domain(Base):
|
|||||||
url = Column(String(200), nullable=False)
|
url = Column(String(200), nullable=False)
|
||||||
kintoneuser = Column(String(100), nullable=False)
|
kintoneuser = Column(String(100), nullable=False)
|
||||||
kintonepwd = Column(String(100), nullable=False)
|
kintonepwd = Column(String(100), nullable=False)
|
||||||
|
is_active = Column(Boolean, default=True)
|
||||||
def decrypt_kintonepwd(self):
|
def decrypt_kintonepwd(self):
|
||||||
decrypted_pwd = chacha20Decrypt(self.kintonepwd)
|
decrypted_pwd = chacha20Decrypt(self.kintonepwd)
|
||||||
return decrypted_pwd
|
return decrypted_pwd
|
||||||
@@ -162,14 +168,20 @@ class Domain(Base):
|
|||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
createuser = relationship('User',foreign_keys=[createuserid])
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||||
owner = relationship('User',foreign_keys=[ownerid])
|
owner = relationship('User',foreign_keys=[ownerid])
|
||||||
is_active = Column(Boolean, default=True)
|
|
||||||
|
|
||||||
class UserDomain(Base):
|
class UserDomain(Base):
|
||||||
__tablename__ = "userdomain"
|
__tablename__ = "userdomain"
|
||||||
|
|
||||||
userid = Column(Integer,ForeignKey("user.id"))
|
userid = Column(Integer,ForeignKey("user.id"))
|
||||||
domainid = Column(Integer,ForeignKey("domain.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):
|
class Event(Base):
|
||||||
__tablename__ = "event"
|
__tablename__ = "event"
|
||||||
|
|||||||
@@ -15,10 +15,14 @@ class Permission(BaseModel):
|
|||||||
function:str
|
function:str
|
||||||
privilege:str
|
privilege:str
|
||||||
|
|
||||||
class Role(BaseModel):
|
class RoleBase(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
name:str
|
name:str
|
||||||
description:str
|
description:str
|
||||||
|
level:int
|
||||||
|
|
||||||
|
|
||||||
|
class RoleWithPermission(RoleBase):
|
||||||
permissions:t.List[Permission] = []
|
permissions:t.List[Permission] = []
|
||||||
|
|
||||||
class UserBase(BaseModel):
|
class UserBase(BaseModel):
|
||||||
@@ -27,19 +31,28 @@ class UserBase(BaseModel):
|
|||||||
is_superuser: bool = False
|
is_superuser: bool = False
|
||||||
first_name: str = None
|
first_name: str = None
|
||||||
last_name: str = None
|
last_name: str = None
|
||||||
roles:t.List[Role] = []
|
roles:t.List[RoleBase] = []
|
||||||
|
|
||||||
|
|
||||||
class UserOut(UserBase):
|
class UserOut(BaseModel):
|
||||||
pass
|
id: int
|
||||||
|
email: str
|
||||||
|
is_active: bool = True
|
||||||
|
is_superuser: bool = False
|
||||||
|
first_name: str = None
|
||||||
|
last_name: str = None
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
email:str
|
email:str
|
||||||
password: str
|
password: str
|
||||||
|
hashed_password :str = None
|
||||||
first_name: str
|
first_name: str
|
||||||
last_name: str
|
last_name: str
|
||||||
is_active:bool
|
is_active:bool
|
||||||
is_superuser:bool
|
is_superuser:bool
|
||||||
|
createuserid:t.Optional[int] = None
|
||||||
|
updateuserid:t.Optional[int] = None
|
||||||
|
|
||||||
class ConfigDict:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
@@ -47,6 +60,8 @@ class UserCreate(UserBase):
|
|||||||
|
|
||||||
class UserEdit(UserBase):
|
class UserEdit(UserBase):
|
||||||
password: t.Optional[str] = None
|
password: t.Optional[str] = None
|
||||||
|
hashed_password :str = None
|
||||||
|
updateuserid:t.Optional[int] = None
|
||||||
|
|
||||||
class ConfigDict:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
@@ -149,9 +164,11 @@ class DomainIn(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
url: str
|
url: str
|
||||||
kintoneuser: str
|
kintoneuser: str
|
||||||
kintonepwd: str
|
kintonepwd: t.Optional[str] = None
|
||||||
is_active: bool
|
is_active: bool
|
||||||
ownerid:int
|
createuserid:t.Optional[int] = None
|
||||||
|
updateuserid:t.Optional[int] = None
|
||||||
|
ownerid:t.Optional[int] = None
|
||||||
|
|
||||||
def encrypt_kintonepwd(self):
|
def encrypt_kintonepwd(self):
|
||||||
encrypted_pwd = chacha20Encrypt(self.kintonepwd)
|
encrypted_pwd = chacha20Encrypt(self.kintonepwd)
|
||||||
@@ -162,16 +179,18 @@ class DomainOut(BaseModel):
|
|||||||
tenantid: str
|
tenantid: str
|
||||||
name: str
|
name: str
|
||||||
url: str
|
url: str
|
||||||
|
kintoneuser: str
|
||||||
is_active: bool
|
is_active: bool
|
||||||
ownerid:int
|
ownerid:int
|
||||||
|
|
||||||
|
class ConfigDict:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
class ActiveDomain(BaseModel):
|
class UserDomain(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
tenantid: str
|
is_default: bool
|
||||||
name: str
|
domain:DomainOut
|
||||||
url: str
|
user:UserOut
|
||||||
is_active: bool
|
|
||||||
|
|
||||||
class Domain(Base):
|
class Domain(Base):
|
||||||
id: int
|
id: int
|
||||||
@@ -179,12 +198,14 @@ class Domain(Base):
|
|||||||
name: str
|
name: str
|
||||||
url: str
|
url: str
|
||||||
kintoneuser: str
|
kintoneuser: str
|
||||||
kintonepwd: str
|
|
||||||
is_active: bool
|
is_active: bool
|
||||||
|
updateuser:UserOut
|
||||||
owner:UserOut
|
owner:UserOut
|
||||||
|
|
||||||
class ConfigDict:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class Event(Base):
|
class Event(Base):
|
||||||
id: int
|
id: int
|
||||||
category: str
|
category: str
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.orm import sessionmaker,declarative_base
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
from app.core import config
|
from app.core import config
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
from fastapi import FastAPI, Depends
|
from fastapi import FastAPI, Depends
|
||||||
|
from fastapi_pagination import add_pagination
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from app.api.api_v1.routers.kintone import kinton_router
|
from app.api.api_v1.routers.kintone import kinton_router
|
||||||
@@ -14,14 +15,22 @@ from app import tasks
|
|||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
import logging
|
import logging
|
||||||
from app.core.apiexception import APIException, writedblog
|
from app.core.apiexception import APIException, writedblog
|
||||||
|
from app.core.common import ApiReturnError
|
||||||
from app.db.crud import create_log
|
from app.db.crud import create_log
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
startup_event()
|
||||||
|
yield
|
||||||
|
|
||||||
app = FastAPI(
|
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 = [
|
origins = [
|
||||||
@@ -36,6 +45,8 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_pagination(app)
|
||||||
|
|
||||||
# @app.middleware("http")
|
# @app.middleware("http")
|
||||||
# async def db_session_middleware(request: Request, call_next):
|
# async def db_session_middleware(request: Request, call_next):
|
||||||
# request.state.db = SessionLocal()
|
# request.state.db = SessionLocal()
|
||||||
@@ -43,12 +54,11 @@ app.add_middleware(
|
|||||||
# request.state.db.close()
|
# request.state.db.close()
|
||||||
# return response
|
# return response
|
||||||
|
|
||||||
@app.on_event("startup")
|
def startup_event():
|
||||||
async def startup_event():
|
|
||||||
log_dir="log"
|
log_dir="log"
|
||||||
if not os.path.exists(log_dir):
|
if not os.path.exists(log_dir):
|
||||||
os.makedirs(log_dir)
|
os.makedirs(log_dir)
|
||||||
|
|
||||||
logger = logging.getLogger("uvicorn.access")
|
logger = logging.getLogger("uvicorn.access")
|
||||||
handler = logging.handlers.RotatingFileHandler(f"{log_dir}/api.log",mode="a",maxBytes = 100*1024, backupCount = 3)
|
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"))
|
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)
|
loop.run_in_executor(None,writedblog,exc)
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=exc.status_code,
|
status_code=exc.status_code,
|
||||||
content={"detail": f"{exc.detail}"},
|
content= ApiReturnError(msg = f"{exc.detail}").model_dump(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.get("/api/v1")
|
@app.get("/api/v1")
|
||||||
|
|||||||
145
backend/app/tests/conftest.py
Normal file
145
backend/app/tests/conftest.py
Normal file
@@ -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
|
||||||
@@ -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.status_code == 200
|
||||||
assert response.json() == {"message": "Hello World"}
|
assert response.json() == {"message": "success"}
|
||||||
|
|||||||
@@ -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
|
|
||||||
Binary file not shown.
@@ -33,10 +33,10 @@ export default {
|
|||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
|
{ 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: 'left', label: 'ドメイン', field: 'name', sortable: true, classes: inactiveRowClass },
|
||||||
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true, classes: inactiveRowClass },
|
{ name: 'url', label: 'URL', field: 'url',align: 'left', sortable: true, classes: inactiveRowClass },
|
||||||
{ name: 'url', label: 'URL', field: 'url', sortable: true, classes: inactiveRowClass },
|
{ name: 'user', label: 'アカウント', field: 'user',align: 'left', classes: inactiveRowClass },
|
||||||
{ name: 'user', label: 'アカウント', field: 'user', classes: inactiveRowClass }
|
{ name: 'owner', label: '所有者', field: row => row.owner.fullName, align: 'left', classes: inactiveRowClass },
|
||||||
]
|
]
|
||||||
const rows = reactive([]);
|
const rows = reactive([]);
|
||||||
|
|
||||||
@@ -51,6 +51,16 @@ export default {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
url: data.url,
|
url: data.url,
|
||||||
user: data.kintoneuser,
|
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)) {
|
if (props.filterInitRowsFunc && !props.filterInitRowsFunc(item)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
191
frontend/src/components/ShareDomain/ShareDomainDialog.vue
Normal file
191
frontend/src/components/ShareDomain/ShareDomainDialog.vue
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<template>
|
||||||
|
<q-dialog :auto-close="false" :model-value="visible" persistent bordered>
|
||||||
|
<q-card class="dialog-content" >
|
||||||
|
<q-toolbar class="bg-grey-4">
|
||||||
|
<q-toolbar-title>「{{domain.name}}」のドメインを共有する</q-toolbar-title>
|
||||||
|
<q-btn flat round dense icon="close" @click="close" />
|
||||||
|
</q-toolbar>
|
||||||
|
|
||||||
|
<q-card-section class="q-mx-md " >
|
||||||
|
<q-select
|
||||||
|
class="q-mt-md"
|
||||||
|
:disable="loading||!domain.domainActive"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="canSharedUserFilter"
|
||||||
|
use-input
|
||||||
|
input-debounce="0"
|
||||||
|
:options="canSharedUserFilteredOptions"
|
||||||
|
clearable
|
||||||
|
:placeholder="canSharedUserFilter ? '' : domain.domainActive ? '共有するユーザーを選択' : 'ドメインが有効でないため、共有ができない。'"
|
||||||
|
@filter="filterFn"
|
||||||
|
:display-value="canSharedUserFilter?`${canSharedUserFilter.fullName} (${canSharedUserFilter.email})`:''">
|
||||||
|
|
||||||
|
<template v-slot:after>
|
||||||
|
<q-btn :disable="!canSharedUserFilter" :loading="addLoading" label="共有する" color="primary" @click="shareTo(canSharedUserFilter as IUserDisplay)" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section avatar>{{scope.opt.id}}</q-item-section>
|
||||||
|
<q-item-section>{{scope.opt.fullName}}</q-item-section>
|
||||||
|
<q-item-section>{{scope.opt.email}}</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
<sharing-user-list class="q-mt-md" style="height: 330px" :users="sharedUsers" :loading="loading" title="現在の共有ユーザー">
|
||||||
|
<template v-slot:actions="{ row }">
|
||||||
|
<q-btn flat color="primary" padding="xs" size="1em" :loading="row.id == removingUser?.id" icon="person_off" @click="removeShareTo(row)" />
|
||||||
|
</template>
|
||||||
|
</sharing-user-list>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="right" class="text-primary">
|
||||||
|
<q-btn flat label="確定" @click="close" />
|
||||||
|
<q-btn flat label="キャンセル" @click="close" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onMounted } from 'vue';
|
||||||
|
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
|
||||||
|
import { IUser, IUserDisplay } from '../../types/UserTypes';
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean;
|
||||||
|
domain: IDomainOwnerDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: boolean): void;
|
||||||
|
(e: 'close'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const addLoading = ref(false);
|
||||||
|
const removingUser = ref<IUserDisplay>();
|
||||||
|
const loading = ref(true);
|
||||||
|
const visible = ref(props.modelValue);
|
||||||
|
|
||||||
|
const allUsers = ref<IUserDisplay[]>([]);
|
||||||
|
const sharedUsers = ref<IUserDisplay[]>([]);
|
||||||
|
const sharedUsersIdSet = new Set<number>();
|
||||||
|
|
||||||
|
const canSharedUsers = ref<IUserDisplay[]>([]);
|
||||||
|
const canSharedUserFilter = ref<IUserDisplay>();
|
||||||
|
const canSharedUserFilteredOptions = ref<IUserDisplay[]>([]);
|
||||||
|
|
||||||
|
const filterFn = (val:string, update: (cb: () => void) => void) => {
|
||||||
|
update(() => {
|
||||||
|
if (val === '') {
|
||||||
|
canSharedUserFilteredOptions.value = canSharedUsers.value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const needle = val.toLowerCase();
|
||||||
|
canSharedUserFilteredOptions.value = canSharedUsers.value.filter(v =>
|
||||||
|
v.email.toLowerCase().indexOf(needle) > -1 || v.fullNameSearch.toLowerCase().indexOf(needle) > -1);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
async (newValue) => {
|
||||||
|
visible.value = newValue;
|
||||||
|
sharedUsers.value = [];
|
||||||
|
canSharedUserFilter.value = undefined
|
||||||
|
if (newValue) {
|
||||||
|
await loadShared();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => visible.value,
|
||||||
|
(newValue) => {
|
||||||
|
emit('update:modelValue', newValue);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit('close');
|
||||||
|
};
|
||||||
|
|
||||||
|
const shareTo = async (user: IUserDisplay) => {
|
||||||
|
addLoading.value = true;
|
||||||
|
loading.value = true;
|
||||||
|
await api.post(`api/domain/${user.id}?domainid=${props.domain.id}`)
|
||||||
|
await loadShared();
|
||||||
|
canSharedUserFilter.value = undefined;
|
||||||
|
loading.value = false;
|
||||||
|
addLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeShareTo = async (user: IUserDisplay) => {
|
||||||
|
removingUser.value = user;
|
||||||
|
loading.value = true;
|
||||||
|
await api.delete(`api/domain/${props.domain.id}/${user.id}`)
|
||||||
|
await loadShared();
|
||||||
|
loading.value = false;
|
||||||
|
removingUser.value = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadShared = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
sharedUsersIdSet.clear();
|
||||||
|
|
||||||
|
const { data } = await api.get(`/api/domainshareduser/${props.domain.id}`);
|
||||||
|
sharedUsers.value = data.data.map((item: IUser) => {
|
||||||
|
const val = itemToDisplay(item);
|
||||||
|
sharedUsersIdSet.add(val.id);
|
||||||
|
return val;
|
||||||
|
});
|
||||||
|
|
||||||
|
canSharedUsers.value = allUsers.value.filter((item) => !sharedUsersIdSet.has(item.id));
|
||||||
|
canSharedUserFilteredOptions.value = canSharedUsers.value;
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getUsers();
|
||||||
|
})
|
||||||
|
|
||||||
|
const getUsers = async () => {
|
||||||
|
if (Object.keys(allUsers.value).length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
const result = await api.get(`api/v1/users`);
|
||||||
|
allUsers.value = result.data.data.map(itemToDisplay);
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemToDisplay = (item: IUser) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
firstName: item.first_name,
|
||||||
|
lastName: item.last_name,
|
||||||
|
fullNameSearch: (item.last_name + item.first_name).toLowerCase(),
|
||||||
|
fullName: item.last_name + ' ' + item.first_name,
|
||||||
|
email: item.email,
|
||||||
|
isSuperuser: item.is_superuser,
|
||||||
|
isActive: item.is_active,
|
||||||
|
} as IUserDisplay
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.dialog-content {
|
||||||
|
width: 60vw;
|
||||||
|
max-height: 80vh;
|
||||||
|
.q-select {
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
45
frontend/src/components/ShareDomain/SharingUserList.vue
Normal file
45
frontend/src/components/ShareDomain/SharingUserList.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<q-table :rows="users" :filter="filter" dense :columns="columns" row-key="id" :loading="loading" :pagination="pagination">
|
||||||
|
<template v-slot:top>
|
||||||
|
<div class="h6 text-weight-bold">{{props.title}}</div>
|
||||||
|
<q-space />
|
||||||
|
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-actions="props">
|
||||||
|
<q-td :props="props">
|
||||||
|
<slot name="actions" :row="props.row"></slot>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, PropType } from 'vue';
|
||||||
|
import { IUserDisplay } from '../../types/UserTypes';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
users: {
|
||||||
|
type: Array as PropType<IUserDisplay[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
title: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||||
|
{ name: 'fullName', label: '名前', field: 'fullName', align: 'left', sortable: true },
|
||||||
|
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||||
|
{ name: 'actions', label: '', field: 'actions', sortable: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
const filter = ref('');
|
||||||
|
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 10 });
|
||||||
|
</script>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions v-if="!disableBtn" align="right" class="text-primary">
|
<q-card-actions v-if="!disableBtn" align="right" class="text-primary">
|
||||||
<q-btn flat label="確定" :loading="okBtnLoading" :v-close-popup="okBtnAutoClose" @click="CloseDialogue('OK')" />
|
<q-btn flat label="確定" :loading="okBtnLoading" :v-close-popup="okBtnAutoClose" @click="CloseDialogue('OK')" />
|
||||||
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
<q-btn flat label="キャンセル" :disable="okBtnLoading" v-close-popup @click="CloseDialogue('Cancel')" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|||||||
69
frontend/src/components/UserDomain/DomainCard.vue
Normal file
69
frontend/src/components/UserDomain/DomainCard.vue
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<q-card :class="['domain-card', item.id == activeId ? 'default': '']">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row no-wrap">
|
||||||
|
<div class="col">
|
||||||
|
<div class="text-h6 ellipsis">{{ item.name }}</div>
|
||||||
|
<div class="text-subtitle2">{{ item.url }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!isOwnerFunc(item.owner.id)" class="col-auto">
|
||||||
|
<!-- <q-badge color="secondary" text-color="white" align="middle" class="q-mb-xs" label="他人の所有" /> -->
|
||||||
|
<q-chip square color="secondary" text-color="white" icon="people" label="他人の所有" size="sm" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row items-center no-wrap">
|
||||||
|
<div class="col">
|
||||||
|
<div class="text-grey-7 text-caption text-weight-medium">
|
||||||
|
アカウント
|
||||||
|
</div>
|
||||||
|
<div class="smaller-font-size">{{ item.user }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="text-grey-7 text-caption text-weight-medium">
|
||||||
|
所有者
|
||||||
|
</div>
|
||||||
|
<div class="smaller-font-size">{{ item.owner.fullName }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator v-if="$slots.actions" />
|
||||||
|
<slot name="actions" :item="item"></slot>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { defineProps, computed } from 'vue';
|
||||||
|
import { IDomainOwnerDisplay } from 'src/types/DomainTypes';
|
||||||
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
item: IDomainOwnerDisplay;
|
||||||
|
activeId: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const isOwnerFunc = computed(() => (ownerId: string) => {
|
||||||
|
return ownerId == authStore.userId;
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.domain-card.default {
|
||||||
|
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, 0.2),
|
||||||
|
0 10px 14px 1px rgba(0, 0, 0, 0.14),
|
||||||
|
0 4px 18px 3px rgba(0, 0, 0, 0.12),
|
||||||
|
inset 0 0 0px 2px #1976D2;
|
||||||
|
}
|
||||||
|
.domain-card {
|
||||||
|
width: 22rem;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
.smaller-font-size {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,8 +9,8 @@ import { api } from 'boot/axios';
|
|||||||
const props = defineProps<{filter:string}>()
|
const props = defineProps<{filter:string}>()
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
{ 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 },
|
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ defineExpose({
|
|||||||
const getUsers = async (filter = () => true) => {
|
const getUsers = async (filter = () => true) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const result = await api.get(`api/v1/users`);
|
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 }
|
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);
|
}).filter(filter);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
33
frontend/src/components/main/domainTable.vue
Normal file
33
frontend/src/components/main/domainTable.vue
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||||
|
|
||||||
|
<template v-slot:top>
|
||||||
|
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||||
|
<q-space />
|
||||||
|
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-name="p">
|
||||||
|
<q-td class="flex justify-between items-center" :props="p">
|
||||||
|
{{ p.row.name }}
|
||||||
|
<q-badge v-if="!p.row.domainActive" color="grey">未启用</q-badge>
|
||||||
|
<q-badge v-if="p.row.id == currendDomainId" color="primary">現在</q-badge>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-actions="p">
|
||||||
|
<q-td :props="p">
|
||||||
|
<q-btn-group flat>
|
||||||
|
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editRow(p.row)" />
|
||||||
|
<q-btn flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||||
|
</q-btn-group>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</q-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
@@ -16,6 +16,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-slot:body-cell-name="prop">
|
||||||
|
<q-td :props="prop">
|
||||||
|
<q-btn flat dense :label="prop.row.name" @click="toEditFlowPage(prop.row)" ></q-btn>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
<template v-slot:body-cell-url="prop">
|
<template v-slot:body-cell-url="prop">
|
||||||
<q-td :props="prop">
|
<q-td :props="prop">
|
||||||
<a :href="prop.row.url" target="_blank" :title="prop.row.name" >
|
<a :href="prop.row.url" target="_blank" :title="prop.row.name" >
|
||||||
@@ -25,11 +30,31 @@
|
|||||||
</template>
|
</template>
|
||||||
<template v-slot:body-cell-actions="p">
|
<template v-slot:body-cell-actions="p">
|
||||||
<q-td :props="p">
|
<q-td :props="p">
|
||||||
<q-btn-group flat>
|
<q-btn flat padding="xs" round size="1em" icon="more_vert">
|
||||||
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="toEditFlowPage(p.row)" />
|
<q-menu >
|
||||||
<q-btn disabled flat color="primary" padding="xs" size="1em" icon="history" @click="showHistory(p.row)" />
|
<q-list dense style="min-width: 100px">
|
||||||
<q-btn disabled flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
<q-item clickable v-close-popup @click="toEditFlowPage(p.row)">
|
||||||
</q-btn-group>
|
<q-item-section>
|
||||||
|
<q-icon size="1.2em" name="account_tree" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>設定</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="showHistory(p.row)">
|
||||||
|
<q-item-section>
|
||||||
|
<q-icon size="1.2em" name="history" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>履歴</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
<q-item class="text-red" clickable v-close-popup @click="removeRow(p.row)">
|
||||||
|
<q-item-section>
|
||||||
|
<q-icon size="1.2em" name="delete_outline" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>削除</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
@@ -49,6 +74,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch, reactive } from 'vue';
|
import { ref, onMounted, watch, reactive } from 'vue';
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
@@ -78,7 +104,7 @@ const columns = [
|
|||||||
{ name: 'user', label: '最後更新者', field: 'user', align: 'left', sortable: true},
|
{ name: 'user', label: '最後更新者', field: 'user', align: 'left', sortable: true},
|
||||||
{ name: 'updatetime', label: '最後更新日', field: 'updatetime', align: 'left', sortable: true},
|
{ name: 'updatetime', label: '最後更新日', field: 'updatetime', align: 'left', sortable: true},
|
||||||
{ name: 'version', label: 'バージョン', field: 'version', align: 'left', sortable: true, sort: numberStringSorting },
|
{ name: 'version', label: 'バージョン', field: 'version', align: 'left', sortable: true, sort: numberStringSorting },
|
||||||
{ name: 'actions', label: '操作', field: 'actions' }
|
{ name: 'actions', label: '', field: 'actions' }
|
||||||
];
|
];
|
||||||
|
|
||||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||||
@@ -87,6 +113,7 @@ const filter = ref('');
|
|||||||
const rows = ref<IAppDisplay[]>([]);
|
const rows = ref<IAppDisplay[]>([]);
|
||||||
const rowIds = new Set<string>();
|
const rowIds = new Set<string>();
|
||||||
|
|
||||||
|
const $q = useQuasar()
|
||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
const appDialog = ref();
|
const appDialog = ref();
|
||||||
const showSelectApp=ref(false);
|
const showSelectApp=ref(false);
|
||||||
@@ -95,12 +122,21 @@ const isAdding = ref(false);
|
|||||||
const getApps = async () => {
|
const getApps = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
rowIds.clear();
|
rowIds.clear();
|
||||||
const result = await api.get('api/apps');
|
try {
|
||||||
rows.value = result.data.map((item: IManagedApp) => {
|
const { data } = await api.get('api/apps');
|
||||||
rowIds.add(item.appid);
|
rows.value = data.data.map((item: IManagedApp) => {
|
||||||
return appToAppDisplay(item)
|
rowIds.add(item.appid);
|
||||||
}).sort((a: IAppDisplay, b: IAppDisplay) => a.sortId - b.sortId); // set default order
|
return appToAppDisplay(item)
|
||||||
loading.value = false;
|
}).sort((a: IAppDisplay, b: IAppDisplay) => a.sortId - b.sortId); // set default order
|
||||||
|
} catch (error) {
|
||||||
|
$q.notify({
|
||||||
|
icon: 'error',
|
||||||
|
color: 'negative',
|
||||||
|
message: 'アプリ一覧の読み込みに失敗しました'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
|||||||
@@ -17,9 +17,15 @@
|
|||||||
</q-input>
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body-cell-name="p">
|
<template v-slot:header-cell-active="p">
|
||||||
<q-td class="flex justify-between items-center" :props="p">
|
<q-th auto-width :props="p">
|
||||||
{{ p.row.name }}
|
<q-select class="filter-header" v-model="activeFilter" :options="activeOptions" @update:model-value="activeFilterUpdate" borderless
|
||||||
|
dense options-dense hide-bottom-space/>
|
||||||
|
</q-th>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-active="p">
|
||||||
|
<q-td auto-width :props="p">
|
||||||
<q-badge v-if="!p.row.domainActive" color="grey">未使用</q-badge>
|
<q-badge v-if="!p.row.domainActive" color="grey">未使用</q-badge>
|
||||||
<q-badge v-if="p.row.id == currentDomainId" color="primary">既定</q-badge>
|
<q-badge v-if="p.row.id == currentDomainId" color="primary">既定</q-badge>
|
||||||
</q-td>
|
</q-td>
|
||||||
@@ -27,10 +33,31 @@
|
|||||||
|
|
||||||
<template v-slot:body-cell-actions="p">
|
<template v-slot:body-cell-actions="p">
|
||||||
<q-td :props="p">
|
<q-td :props="p">
|
||||||
<q-btn-group flat>
|
<q-btn flat padding="xs" round size="1em" icon="more_vert">
|
||||||
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editRow(p.row)" />
|
<q-menu >
|
||||||
<q-btn flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
<q-list dense style="min-width: 100px">
|
||||||
</q-btn-group>
|
<q-item clickable v-close-popup @click="editRow(p.row)">
|
||||||
|
<q-item-section>
|
||||||
|
<q-icon size="1.2em" name="edit_note" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>編集</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable v-close-popup @click="openShareDg(p.row)">
|
||||||
|
<q-item-section>
|
||||||
|
<q-icon size="1.2em" name="person_add_alt" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>共有</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-separator />
|
||||||
|
<q-item class="text-red" clickable v-close-popup @click="removeRow(p.row)">
|
||||||
|
<q-item-section>
|
||||||
|
<q-icon size="1.2em" name="delete_outline" />
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>削除</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -46,15 +73,11 @@
|
|||||||
<q-card-section class="q-pt-none q-mt-none">
|
<q-card-section class="q-pt-none q-mt-none">
|
||||||
<div class="q-gutter-lg">
|
<div class="q-gutter-lg">
|
||||||
|
|
||||||
|
|
||||||
<q-input filled v-model="tenantid" label="テナントID" hint="テナントIDを入力してください。" lazy-rules
|
|
||||||
:rules="[val => val && val.length > 0 || 'テナントIDを入力してください。']" />
|
|
||||||
|
|
||||||
<q-input filled v-model="name" label="環境名 *" hint="kintoneの環境名を入力してください" lazy-rules
|
<q-input filled v-model="name" label="環境名 *" hint="kintoneの環境名を入力してください" lazy-rules
|
||||||
:rules="[val => val && val.length > 0 || 'kintoneの環境名を入力してください。']" />
|
:rules="[val => val && val.length > 0 || 'kintoneの環境名を入力してください。']" />
|
||||||
|
|
||||||
<q-input filled type="url" v-model.trim="url" label="Kintone url" hint="KintoneのURLを入力してください" lazy-rules
|
<q-input filled type="url" v-model.trim="url" label="Kintone url" hint="KintoneのURLを入力してください" lazy-rules
|
||||||
:rules="[val => val && val.length > 0, isDomain || 'KintoneのURLを入力してください']" />
|
:rules="[val => val && val.length > 0 || 'KintoneのURLを入力してください']" />
|
||||||
|
|
||||||
<q-input filled v-model="kintoneuser" label="ログイン名 *" hint="Kintoneのログイン名を入力してください" lazy-rules
|
<q-input filled v-model="kintoneuser" label="ログイン名 *" hint="Kintoneのログイン名を入力してください" lazy-rules
|
||||||
:rules="[val => val && val.length > 0 || 'Kintoneのログイン名を入力してください']" autocomplete="new-password" />
|
:rules="[val => val && val.length > 0 || 'Kintoneのログイン名を入力してください']" autocomplete="new-password" />
|
||||||
@@ -103,7 +126,7 @@
|
|||||||
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
||||||
<q-btn label="保存" type="submit" color="primary" />
|
<q-btn :loading="addEditLoading" label="保存" type="submit" color="primary" />
|
||||||
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-form>
|
</q-form>
|
||||||
@@ -113,18 +136,29 @@
|
|||||||
|
|
||||||
<q-dialog v-model="confirm" persistent>
|
<q-dialog v-model="confirm" persistent>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section v-if="deleteLoadingState == -1" class="row items-center">
|
||||||
|
<q-spinner color="primary" size="2em"/>
|
||||||
|
<span class="q-ml-sm">共有ユーザーのチェック</span>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section v-else-if="deleteLoadingState == 0" class="row items-center">
|
||||||
<q-icon name="warning" color="warning" size="2em" />
|
<q-icon name="warning" color="warning" size="2em" />
|
||||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
<q-card-section v-else class="row items-center">
|
||||||
|
<q-icon name="error" color="negative" size="2em" />
|
||||||
|
<span class="q-ml-sm">共有ユーザーが存在し、キャンセルする必要がある</span>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||||
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomain()" />
|
<q-btn v-if="deleteLoadingState > 0" label="処理に行く" color="primary" v-close-popup @click="openShareDg(editId)" />
|
||||||
|
<q-btn flat v-else label="OK" :disabled="deleteLoadingState" color="primary" v-close-popup @click="deleteDomain()" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
|
<share-domain-dialog v-model="shareDg" :domain="shareDomain" @close="closeShareDg()" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -133,39 +167,49 @@ import { ref, onMounted, computed } from 'vue';
|
|||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
import { useDomainStore } from 'stores/useDomainStore';
|
import { useDomainStore } from 'stores/useDomainStore';
|
||||||
import { IDomain, IDomainDisplay, IDomainSubmit } from '../types/DomainTypes';
|
import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
|
||||||
|
import { IDomain, IDomainOwnerDisplay, IDomainSubmit } from '../types/DomainTypes';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const domainStore = useDomainStore();
|
const domainStore = useDomainStore();
|
||||||
const inactiveRowClass = (row: IDomainDisplay) => row.domainActive ? '' : 'inactive-row';
|
const inactiveRowClass = (row: IDomainOwnerDisplay) => row.domainActive ? '' : 'inactive-row';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
|
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
|
||||||
{
|
// {
|
||||||
name: 'tenantid',
|
// name: 'tenantid',
|
||||||
required: true,
|
// required: true,
|
||||||
label: 'テナントID',
|
// label: 'テナントID',
|
||||||
field: 'tenantid',
|
// field: 'tenantid',
|
||||||
align: 'left',
|
// align: 'left',
|
||||||
sortable: true,
|
// sortable: true,
|
||||||
classes: inactiveRowClass
|
// classes: inactiveRowClass
|
||||||
},
|
// },
|
||||||
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true, classes: inactiveRowClass },
|
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true, classes: inactiveRowClass },
|
||||||
|
{ name: 'active', label: 'x', align: 'left', field: 'domainActive', classes: inactiveRowClass },
|
||||||
{ name: 'url', label: 'URL', field: 'url', align: 'left', 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: 'user', label: 'ログイン名', field: 'user', align: 'left', classes: inactiveRowClass },
|
||||||
{ name: 'actions', label: '操作', field: 'actions' }
|
{ name: 'owner', label: '所有者', field: row => row.owner.fullName, align: 'left', classes: inactiveRowClass },
|
||||||
|
{ name: 'actions', label: '', field: 'actions', classes: inactiveRowClass }
|
||||||
];
|
];
|
||||||
|
|
||||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const addEditLoading = ref(false);
|
||||||
|
const deleteLoadingState = ref<number>(-1); // -1: loading, 0: allow, > 0: user count
|
||||||
|
|
||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const rows = ref<IDomainDisplay[]>([]);
|
const rows = ref<IDomainOwnerDisplay[]>([]);
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const confirm = ref(false);
|
const confirm = ref(false);
|
||||||
const resetPsw = ref(false);
|
const resetPsw = ref(false);
|
||||||
|
|
||||||
|
const activeOptions = ['全データ', '启用', '未启用']
|
||||||
|
const activeFilter = ref('全データ');
|
||||||
|
|
||||||
|
|
||||||
const currentDomainId = computed(() => authStore.currentDomain.id);
|
const currentDomainId = computed(() => authStore.currentDomain.id);
|
||||||
const tenantid = ref(authStore.currentDomain.id);
|
// const tenantid = ref(authStore.currentDomain.id);
|
||||||
const name = ref('');
|
const name = ref('');
|
||||||
const url = ref('');
|
const url = ref('');
|
||||||
const isPwd = ref(true);
|
const isPwd = ref(true);
|
||||||
@@ -175,9 +219,24 @@ const kintonepwdBK = ref('');
|
|||||||
const domainActive = ref(true);
|
const domainActive = ref(true);
|
||||||
const isCreate = ref(true);
|
const isCreate = ref(true);
|
||||||
let editId = ref(0);
|
let editId = ref(0);
|
||||||
let ownerid = ref('');
|
const shareDg = ref(false);
|
||||||
|
const shareDomain = ref<IDomainOwnerDisplay>();
|
||||||
|
|
||||||
const getDomain = async () => {
|
const activeFilterUpdate = (selected) => {
|
||||||
|
switch (selected) {
|
||||||
|
case '启用':
|
||||||
|
getDomain((row) => row.domainActive)
|
||||||
|
break;
|
||||||
|
case '未启用':
|
||||||
|
getDomain((row) => !row.domainActive)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
getDomain()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDomain = async (filter = () => true) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const { data } = await api.get<{data:IDomain[]}>(`api/domains`);
|
const { data } = await api.get<{data:IDomain[]}>(`api/domains`);
|
||||||
rows.value = data.data.map((item) => {
|
rows.value = data.data.map((item) => {
|
||||||
@@ -189,8 +248,18 @@ const getDomain = async () => {
|
|||||||
url: item.url,
|
url: item.url,
|
||||||
user: item.kintoneuser,
|
user: item.kintoneuser,
|
||||||
password: item.kintonepwd,
|
password: item.kintonepwd,
|
||||||
|
owner: {
|
||||||
|
id: item.owner.id,
|
||||||
|
firstName: item.owner.first_name,
|
||||||
|
lastName: item.owner.last_name,
|
||||||
|
fullNameSearch: (item.owner.last_name + item.owner.first_name).toLowerCase(),
|
||||||
|
fullName: item.owner.last_name + ' ' + item.owner.first_name,
|
||||||
|
email: item.owner.email,
|
||||||
|
isActive: item.owner.is_active,
|
||||||
|
isSuperuser: item.owner.is_superuser,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
}).filter(filter);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,23 +274,31 @@ const addRow = () => {
|
|||||||
show.value = true;
|
show.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeRow = (row: IDomainDisplay) => {
|
const removeRow = async (row: IDomainOwnerDisplay) => {
|
||||||
confirm.value = true;
|
confirm.value = true;
|
||||||
|
deleteLoadingState.value = -1;
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
|
|
||||||
|
const { data } = await api.get(`/api/domainshareduser/${row.id}`);
|
||||||
|
deleteLoadingState.value = data.data.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteDomain = () => {
|
const deleteDomain = () => {
|
||||||
api.delete(`api/domain/${editId.value}`).then(() => {
|
api.delete(`api/domain/${editId.value}`).then(({ data }) => {
|
||||||
|
if (!data.data) {
|
||||||
|
// TODO dialog
|
||||||
|
}
|
||||||
getDomain();
|
getDomain();
|
||||||
// authStore.setCurrentDomain();
|
// authStore.setCurrentDomain();
|
||||||
})
|
})
|
||||||
editId.value = 0; // set in removeRow()
|
editId.value = 0; // set in removeRow()
|
||||||
|
deleteLoadingState.value = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const editRow = (row) => {
|
const editRow = (row) => {
|
||||||
isCreate.value = false
|
isCreate.value = false
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
tenantid.value = row.tenantid;
|
// tenantid.value = row.tenantid;
|
||||||
name.value = row.name;
|
name.value = row.name;
|
||||||
url.value = row.url;
|
url.value = row.url;
|
||||||
kintoneuser.value = row.user;
|
kintoneuser.value = row.user;
|
||||||
@@ -246,10 +323,11 @@ const closeDg = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
|
addEditLoading.value = true;
|
||||||
const method = editId.value !== 0 ? 'put' : 'post';
|
const method = editId.value !== 0 ? 'put' : 'post';
|
||||||
const param: IDomainSubmit = {
|
const param: IDomainSubmit = {
|
||||||
'id': editId.value,
|
'id': editId.value,
|
||||||
'tenantid': tenantid.value,
|
'tenantid': '1', // TODO: テナントIDを取得する
|
||||||
'name': name.value,
|
'name': name.value,
|
||||||
'url': url.value,
|
'url': url.value,
|
||||||
'kintoneuser': kintoneuser.value,
|
'kintoneuser': kintoneuser.value,
|
||||||
@@ -258,14 +336,31 @@ const onSubmit = () => {
|
|||||||
'ownerid': authStore.userId || ''
|
'ownerid': authStore.userId || ''
|
||||||
}
|
}
|
||||||
// for search: api.put(`api/domain`)、api.post(`api/domain`)
|
// for search: api.put(`api/domain`)、api.post(`api/domain`)
|
||||||
api[method].apply(api, [`api/domain`, param]).then(() => {
|
api[method].apply(api, [`api/domain`, param]).then(async (resp: any) => {
|
||||||
|
const res = resp.data;
|
||||||
|
if (res.data.id === currentDomainId.value && !res.data.is_active) {
|
||||||
|
await authStore.setCurrentDomain();
|
||||||
|
}
|
||||||
getDomain();
|
getDomain();
|
||||||
domainStore.loadUserDomains();
|
domainStore.loadUserDomains();
|
||||||
closeDg();
|
closeDg();
|
||||||
onReset();
|
onReset();
|
||||||
|
addEditLoading.value = false;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openShareDg = (row: IDomainOwnerDisplay|number) => {
|
||||||
|
if (typeof row === 'number') {
|
||||||
|
row = rows.value.find(item => item.id === row) as IDomainOwnerDisplay;
|
||||||
|
}
|
||||||
|
shareDomain.value = row ;
|
||||||
|
shareDg.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeShareDg = () => {
|
||||||
|
shareDg.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
name.value = '';
|
name.value = '';
|
||||||
url.value = '';
|
url.value = '';
|
||||||
@@ -273,14 +368,25 @@ const onReset = () => {
|
|||||||
kintonepwd.value = '';
|
kintonepwd.value = '';
|
||||||
isPwd.value = true;
|
isPwd.value = true;
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
ownerid.value = '';
|
|
||||||
isCreate.value = true;
|
isCreate.value = true;
|
||||||
domainActive.value = true;
|
domainActive.value = true;
|
||||||
resetPsw.value = false
|
resetPsw.value = false
|
||||||
|
addEditLoading.value = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.filter-header .q-field__native {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.filter-header .q-icon {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
.q-table td.inactive-row {
|
.q-table td.inactive-row {
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
.q-table tr > td.inactive-row:last-child {
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,23 +6,14 @@
|
|||||||
<q-breadcrumbs-el icon="assignment_ind" label="ドメイン適用" />
|
<q-breadcrumbs-el icon="assignment_ind" label="ドメイン適用" />
|
||||||
</q-breadcrumbs>
|
</q-breadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
<q-table :loading="loading" grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" row-key="name"
|
|
||||||
|
<q-table :loading="initLoading" grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" row-key="name"
|
||||||
:filter="userDomainTableFilter" virtual-scroll v-model:pagination="pagination">
|
:filter="userDomainTableFilter" virtual-scroll v-model:pagination="pagination">
|
||||||
|
|
||||||
<template v-slot:top>
|
<template v-slot:top>
|
||||||
|
|
||||||
<q-btn class="q-mx-none" color="primary" label="追加" @click="clickAddDomain()" />
|
<q-btn class="q-mx-none" color="primary" label="追加" @click="clickAddDomain()" />
|
||||||
|
|
||||||
<q-space />
|
<q-space />
|
||||||
<div class="row q-gutter-md">
|
<div class="row q-gutter-md">
|
||||||
<!-- <q-item v-if="authStore.permissions === 'admin'" tag="label" dense @click="clickSwitchUser()">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>適用するユーザ : </q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
{{ currentUserName }}
|
|
||||||
</q-item-section>
|
|
||||||
</q-item> -->
|
|
||||||
|
|
||||||
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search">
|
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search">
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon name="search" />
|
<q-icon name="search" />
|
||||||
@@ -38,69 +29,25 @@
|
|||||||
|
|
||||||
<template v-slot:item="props">
|
<template v-slot:item="props">
|
||||||
<div class="q-pa-sm">
|
<div class="q-pa-sm">
|
||||||
<q-card>
|
<domain-card :item="props.row" :active-id="activeDomainId">
|
||||||
<q-card-section>
|
<template v-slot:actions>
|
||||||
<div class="q-table__grid-item-row">
|
<q-card-actions align="right">
|
||||||
<div class="q-table__grid-item-title">Domain</div>
|
<q-chip class="no-border" v-if="isActive(props.row.id)" outline color="primary" text-color="white" icon="done">
|
||||||
<div class="q-table__grid-item-value">{{ props.row.name }}</div>
|
既定
|
||||||
</div>
|
</q-chip>
|
||||||
<div class="q-table__grid-item-row">
|
<q-btn flat v-else :loading="activeDomainLoadingId === props.row.id" :disable="deleteDomainLoadingId === props.row.id" @click="activeDomain(props.row)">既定にする</q-btn>
|
||||||
<div class="q-table__grid-item-title">URL</div>
|
<q-btn flat :disable="isNotOwner(props.row.owner.id) || activeDomainLoadingId === props.row.id" :text-color="isNotOwner(props.row.owner.id)?'grey':''" :loading="deleteDomainLoadingId === props.row.id" @click="clickDeleteConfirm(props.row)">削除</q-btn>
|
||||||
<div class="q-table__grid-item-value" style="width: 22rem;">{{ props.row.url }}</div>
|
</q-card-actions>
|
||||||
</div>
|
</template>
|
||||||
<div class="q-table__grid-item-row">
|
</domain-card>
|
||||||
<div class="q-table__grid-item-title">Account</div>
|
|
||||||
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-separator />
|
|
||||||
<q-card-actions align="right">
|
|
||||||
<div style="width: 98%;">
|
|
||||||
<div class="row items-center justify-between">
|
|
||||||
<div class="q-table__grid-item-value"
|
|
||||||
:class="isActive(props.row.id) ? 'text-positive' : 'text-negative'">{{
|
|
||||||
isActive(props.row.id)?'既定':'' }}</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<q-btn v-if="!isActive(props.row.id)" flat
|
|
||||||
@click="activeDomain(props.row)">既定にする</q-btn>
|
|
||||||
<q-btn flat @click="clickDeleteConfirm(props.row)">削除</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
||||||
<show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished">
|
<show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished" :ok-btn-loading="addUserDomainLoading" :ok-btn-auto-close="false">
|
||||||
<domain-select ref="addDomainRef" name="ドメイン" type="single" :filterInitRowsFunc="filterAddDgInitRows"></domain-select>
|
<domain-select ref="addDomainRef" name="ドメイン" type="single" :filterInitRowsFunc="filterAddDgInitRows"></domain-select>
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
|
|
||||||
<show-dialog v-model:visible="showSwitchUserDd" name="ドメイン" minWidth="35vw" @close="switchUserFinished">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense placeholder="検索" v-model="switchUserFilter">
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<div class="q-gutter-md">
|
|
||||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>他のユーザーを選択する</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-toggle v-model="useOtherUser" />
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<div v-if="useOtherUser">
|
|
||||||
<user-list ref="switchUserRef" name="ドメイン" :filter="switchUserFilter" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</show-dialog>
|
|
||||||
|
|
||||||
<q-dialog v-model="showDeleteConfirm" persistent>
|
<q-dialog v-model="showDeleteConfirm" persistent>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
@@ -123,33 +70,29 @@
|
|||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
import { useDomainStore } from 'stores/useDomainStore';
|
import { IDomainOwnerDisplay } from '../types/DomainTypes';
|
||||||
|
|
||||||
import ShowDialog from 'components/ShowDialog.vue';
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
|
import DomainCard from 'components/UserDomain/DomainCard.vue';
|
||||||
import DomainSelect from 'components/DomainSelect.vue';
|
import DomainSelect from 'components/DomainSelect.vue';
|
||||||
import UserList from 'components/UserList.vue';
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const domainStore = useDomainStore();
|
|
||||||
const pagination = ref({ sortBy: 'id', rowsPerPage: 0 });
|
const pagination = ref({ sortBy: 'id', rowsPerPage: 0 });
|
||||||
const rows = ref([] as any[]);
|
const rows = ref<IDomainOwnerDisplay[]>([]);
|
||||||
const rowIds = new Set<string>();
|
const rowIds = new Set<string>();
|
||||||
const loading = ref(true);
|
const initLoading = ref(true);
|
||||||
|
const addUserDomainLoading = ref(false);
|
||||||
|
const activeDomainLoadingId = ref<number|undefined>(undefined);
|
||||||
|
const deleteDomainLoadingId = ref<number|undefined>(undefined);
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id' },
|
{ name: 'id' },
|
||||||
{ name: 'name', required: true, label: 'Name', align: 'left', field: 'name', sortable: true },
|
{ name: 'name', required: true, label: 'Name', align: 'left', field: 'name', sortable: true },
|
||||||
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
||||||
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
|
{ name: 'kintoneuser', label: 'User', field: 'user', sortable: true },
|
||||||
{ name: 'kintonepwd' },
|
|
||||||
{ name: 'active', field: 'active' }
|
|
||||||
];
|
];
|
||||||
const userDomainTableFilter = ref();
|
const userDomainTableFilter = ref();
|
||||||
|
|
||||||
const currentUserName = ref('');
|
|
||||||
const useOtherUser = ref(false);
|
|
||||||
const otherUserId = ref('');
|
|
||||||
|
|
||||||
let editId = ref(0);
|
let editId = ref(0);
|
||||||
|
|
||||||
const showAddDomainDg = ref(false);
|
const showAddDomainDg = ref(false);
|
||||||
@@ -164,22 +107,24 @@ const clickAddDomain = () => {
|
|||||||
showAddDomainDg.value = true;
|
showAddDomainDg.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addUserDomainFinished = (val: string) => {
|
const addUserDomainFinished = async (val: string) => {
|
||||||
|
showAddDomainDg.value = true;
|
||||||
const selected = addDomainRef.value.selected;
|
const selected = addDomainRef.value.selected;
|
||||||
if (val == 'OK' && selected.length > 0) {
|
if (val == 'OK' && selected.length > 0) {
|
||||||
api.post(`api/domain/${useOtherUser.value ? otherUserId.value : authStore.userId}?domainid=${selected[0].id}`)
|
addUserDomainLoading.value = true;
|
||||||
.then(async ({ data }) => {
|
const { data } = await api.post(`api/domain/${authStore.userId}?domainid=${selected[0].id}`)
|
||||||
if (rows.value.length === 0 && data.data) {
|
if (rows.value.length === 0 && data.data) {
|
||||||
const domain = data.data;
|
const domain = data.data;
|
||||||
await authStore.setCurrentDomain({
|
await authStore.setCurrentDomain({
|
||||||
id: domain.id,
|
id: domain.id,
|
||||||
kintoneUrl: domain.url,
|
kintoneUrl: domain.url,
|
||||||
domainName: domain.name
|
domainName: domain.name
|
||||||
});
|
|
||||||
}
|
|
||||||
getDomain(useOtherUser.value ? otherUserId.value : undefined);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
await getDomain();
|
||||||
}
|
}
|
||||||
|
addUserDomainLoading.value = false;
|
||||||
|
showAddDomainDg.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showDeleteConfirm = ref(false);
|
const showDeleteConfirm = ref(false);
|
||||||
@@ -190,26 +135,25 @@ const clickDeleteConfirm = (row: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteDomainFinished = async () => {
|
const deleteDomainFinished = async () => {
|
||||||
await api.delete(`api/domain/${editId.value}/${useOtherUser.value ? otherUserId.value : authStore.userId}`).then(({ data }) => {
|
deleteDomainLoadingId.value = editId.value;
|
||||||
if (data.msg == 'OK' && authStore.currentDomain.id === editId.value) {
|
const { data } = await api.delete(`api/domain/${editId.value}/${authStore.userId}`)
|
||||||
authStore.setCurrentDomain();
|
if (data.msg == 'OK' && authStore.currentDomain.id === editId.value) {
|
||||||
}
|
authStore.setCurrentDomain();
|
||||||
getDomain(useOtherUser.value ? otherUserId.value : undefined);
|
}
|
||||||
})
|
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
|
await getDomain();
|
||||||
|
deleteDomainLoadingId.value = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const activeDomain = async (domain: any) => {
|
const activeDomain = async (domain: any) => {
|
||||||
if (useOtherUser.value) {
|
activeDomainLoadingId.value = domain.id;
|
||||||
// TODO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await authStore.setCurrentDomain({
|
await authStore.setCurrentDomain({
|
||||||
id: domain.id,
|
id: domain.id,
|
||||||
kintoneUrl: domain.url,
|
kintoneUrl: domain.url,
|
||||||
domainName: domain.name
|
domainName: domain.name
|
||||||
});
|
});
|
||||||
getDomain();
|
await getDomain();
|
||||||
|
activeDomainLoadingId.value = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
let activeDomainId = ref(0);
|
let activeDomainId = ref(0);
|
||||||
@@ -218,52 +162,58 @@ const isActive = computed(() => (id: number) => {
|
|||||||
return id == activeDomainId.value;
|
return id == activeDomainId.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isNotOwner = computed(() => (ownerId: string) => {
|
||||||
const showSwitchUserDd = ref(false);
|
return ownerId !== authStore.userId;
|
||||||
const switchUserRef = ref();
|
});
|
||||||
const switchUserFilter = ref('')
|
|
||||||
|
|
||||||
const clickSwitchUser = () => {
|
|
||||||
showSwitchUserDd.value = true;
|
|
||||||
useOtherUser.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const switchUserFinished = async (val: string) => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
if (useOtherUser.value) {
|
|
||||||
const user = switchUserRef.value.selected[0]
|
|
||||||
currentUserName.value = user.email;
|
|
||||||
otherUserId.value = user.id
|
|
||||||
await getDomain(user.id)
|
|
||||||
} else {
|
|
||||||
currentUserName.value = authStore.userInfo.email
|
|
||||||
await getDomain();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDomain = async (userId? : string) => {
|
const getDomain = async (userId? : string) => {
|
||||||
loading.value = true;
|
|
||||||
rowIds.clear();
|
rowIds.clear();
|
||||||
if (useOtherUser.value) {
|
const resp = await api.get(`api/defaultdomain`);
|
||||||
// TODO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const resp = await api.get(`api/activedomain`);
|
|
||||||
activeDomainId.value = resp?.data?.data?.id;
|
activeDomainId.value = resp?.data?.data?.id;
|
||||||
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get(`api/domain`);
|
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get(`api/domain`);
|
||||||
const domains = domainResult.data as any[];
|
const domains = domainResult.data as any[];
|
||||||
rows.value = domains.map((item) => {
|
rows.value = domains.sort((a, b) => a.id - b.id).reduce((acc, item) => {
|
||||||
rowIds.add(item.id);
|
rowIds.add(item.id);
|
||||||
return { id: item.id, name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd }
|
if (item.is_active) {
|
||||||
});
|
acc.push({
|
||||||
loading.value = false;
|
id: item.id,
|
||||||
|
tenantid: item.tenantid,
|
||||||
|
domainActive: item.is_active,
|
||||||
|
name: item.name,
|
||||||
|
url: item.url,
|
||||||
|
user: item.kintoneuser,
|
||||||
|
password: item.kintonepwd,
|
||||||
|
owner: {
|
||||||
|
id: item.owner.id,
|
||||||
|
firstName: item.owner.first_name,
|
||||||
|
lastName: item.owner.last_name,
|
||||||
|
fullNameSearch: (item.owner.last_name + item.owner.first_name).toLowerCase(),
|
||||||
|
fullName: item.owner.last_name + ' ' + item.owner.first_name,
|
||||||
|
email: item.owner.email,
|
||||||
|
isActive: item.owner.is_active,
|
||||||
|
isSuperuser: item.owner.is_superuser,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
currentUserName.value = authStore.userInfo.email
|
initLoading.value = true;
|
||||||
await getDomain();
|
await getDomain();
|
||||||
|
initLoading.value = false;
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.domain-card {
|
||||||
|
width: 22rem;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
.smaller-font-size {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -65,10 +65,10 @@
|
|||||||
<q-card-section class="q-pt-none q-mt-none">
|
<q-card-section class="q-pt-none q-mt-none">
|
||||||
<div class="q-gutter-lg">
|
<div class="q-gutter-lg">
|
||||||
|
|
||||||
<q-input filled v-model="firstName" label="氏名 *" hint="ユーザーの氏名を入力してください" lazy-rules
|
<q-input filled v-model="lastName" label="氏名 *" hint="ユーザーの氏名を入力してください" lazy-rules
|
||||||
:rules="[val => val && val.length > 0 || 'ユーザーの氏名を入力してください。']" />
|
:rules="[val => val && val.length > 0 || 'ユーザーの氏名を入力してください。']" />
|
||||||
|
|
||||||
<q-input filled v-model="lastName" label="苗字 *" hint="ユーザーの苗字を入力してください" lazy-rules
|
<q-input filled v-model="firstName" label="苗字 *" hint="ユーザーの苗字を入力してください" lazy-rules
|
||||||
:rules="[val => val && val.length > 0 || 'ユーザーの苗字を入力してください']" />
|
:rules="[val => val && val.length > 0 || 'ユーザーの苗字を入力してください']" />
|
||||||
|
|
||||||
<q-input filled type="email" v-model="email" label="電子メール *" hint="電子メール、ログインとしても使用" lazy-rules
|
<q-input filled type="email" v-model="email" label="電子メール *" hint="電子メール、ログインとしても使用" lazy-rules
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
||||||
<q-btn label="保存" type="submit" color="primary" />
|
<q-btn :loading="addEditLoading" label="保存" type="submit" color="primary" />
|
||||||
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-form>
|
</q-form>
|
||||||
@@ -158,8 +158,8 @@ import { api } from 'boot/axios';
|
|||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
{ 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 },
|
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||||
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
||||||
{ name: 'actions', label: '操作', field: 'actions' }
|
{ name: 'actions', label: '操作', field: 'actions' }
|
||||||
@@ -168,6 +168,7 @@ const columns = [
|
|||||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const addEditLoading = ref(false);
|
||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const statusFilter = ref('全データ');
|
const statusFilter = ref('全データ');
|
||||||
const rows = ref([]);
|
const rows = ref([]);
|
||||||
@@ -189,7 +190,7 @@ let editId = ref(0);
|
|||||||
const getUsers = async (filter = () => true) => {
|
const getUsers = async (filter = () => true) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const result = await api.get(`api/v1/users`);
|
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 }
|
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);
|
}).filter(filter);
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -260,6 +261,7 @@ const closeDg = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
|
addEditLoading.value = true;
|
||||||
if (editId.value !== 0) {
|
if (editId.value !== 0) {
|
||||||
api.put(`api/v1/users/${editId.value}`, {
|
api.put(`api/v1/users/${editId.value}`, {
|
||||||
'first_name': firstName.value,
|
'first_name': firstName.value,
|
||||||
@@ -302,6 +304,7 @@ const onReset = () => {
|
|||||||
isPwd.value = true;
|
isPwd.value = true;
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
isCreate.value = true;
|
isCreate.value = true;
|
||||||
resetPsw.value = false
|
resetPsw.value = false;
|
||||||
|
addEditLoading.value = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getCurrentDomain(): Promise<IDomainInfo> {
|
async getCurrentDomain(): Promise<IDomainInfo> {
|
||||||
const resp = await api.get(`api/activedomain`);
|
const resp = await api.get(`api/defaultdomain`);
|
||||||
const activedomain = resp?.data?.data;
|
const activedomain = resp?.data?.data;
|
||||||
return {
|
return {
|
||||||
id: activedomain?.id,
|
id: activedomain?.id,
|
||||||
@@ -77,7 +77,7 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
async getUserInfo():Promise<UserInfo>{
|
async getUserInfo():Promise<UserInfo>{
|
||||||
const resp = (await api.get(`api/v1/users/me`)).data;
|
const resp = (await api.get(`api/v1/users/me`)).data.data;
|
||||||
return {
|
return {
|
||||||
firstName: resp.first_name,
|
firstName: resp.first_name,
|
||||||
lastName: resp.last_name,
|
lastName: resp.last_name,
|
||||||
@@ -97,7 +97,7 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
if (domain.id === this.currentDomain.id) {
|
if (domain.id === this.currentDomain.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await api.put(`api/activedomain/${domain.id}`);
|
await api.put(`api/defaultdomain/${domain.id}`);
|
||||||
this.currentDomain = domain;
|
this.currentDomain = domain;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { IDomain } from './ActionTypes';
|
import { IUser, IUserDisplay } from './UserTypes';
|
||||||
import { IUser } from './UserTypes';
|
|
||||||
|
|
||||||
export interface IDomainInfo {
|
export interface IDomainInfo {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -32,4 +31,8 @@ export interface IDomainDisplay {
|
|||||||
user: string;
|
user: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
domainActive: boolean;
|
domainActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDomainOwnerDisplay extends IDomainDisplay {
|
||||||
|
owner: IUserDisplay
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,24 @@
|
|||||||
export interface IUser {
|
export interface IUser {
|
||||||
|
id: number;
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
email: string;
|
email: string;
|
||||||
is_active: boolean,
|
is_active: boolean,
|
||||||
is_superuser: boolean,
|
is_superuser: boolean,
|
||||||
roles: string[]
|
roles: object[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IUserDisplay {
|
||||||
|
id: number;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
fullName: string;
|
||||||
|
fullNameSearch: string;
|
||||||
|
email: string;
|
||||||
|
isActive: boolean,
|
||||||
|
isSuperuser: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUserRolesDisplay extends IUserDisplay {
|
||||||
|
roles: object[]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user