Compare commits

..

1 Commits

Author SHA1 Message Date
15cc48cd40 test 2024-11-17 18:42:22 +09:00
55 changed files with 1027 additions and 3446 deletions

View File

@@ -25,15 +25,11 @@ async def login(
minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES
) )
if user.is_superuser: if user.is_superuser:
roles = "super" permissions = "admin"
permissions = "ALL"
else: else:
roles = ";".join(role.name for role in user.roles) permissions = "user"
perlst = [perm.privilege for role in user.roles for perm in role.permissions]
permissions =";".join(list(set(perlst)))
access_token = security.create_access_token( access_token = security.create_access_token(
data={"sub": user.id, "roles":roles,"permissions": permissions ,}, data={"sub": user.id, "permissions": permissions},
expires_delta=access_token_expires, expires_delta=access_token_expires,
) )

View File

@@ -9,16 +9,15 @@ 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_kintoneformat from app.db.crud import get_flows_by_app,get_activedomain,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 = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id) domain = get_activedomain(db, user.id)
db.close() db.close()
kintoneevn = config.KINTONE_ENV(domain) kintoneevn = config.KINTONE_ENV(domain)
return kintoneevn return kintoneevn
@@ -157,10 +156,10 @@ def getsettingfromexcel(df):
des = df.iloc[2,2] des = df.iloc[2,2]
return {"name":appname,"description":des} return {"name":appname,"description":des}
def getsettingfromkintone(app:str,env:config.KINTONE_ENV): def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app} params = {"app":app}
url = f"{env.BASE_URL}{config.API_V1_STR}/app/settings.json" url = f"{c.BASE_URL}{config.API_V1_STR}/app/settings.json"
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
@@ -172,24 +171,24 @@ def analysesettings(excel,kintone):
updatesettings[key] = excel[key] updatesettings[key] = excel[key]
return updatesettings return updatesettings
def createkintoneapp(name:str,env:config.KINTONE_ENV): def createkintoneapp(name:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
data = {"name":name} data = {"name":name}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
r = httpx.post(url,headers=headers,data=json.dumps(data)) r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json() return r.json()
def updateappsettingstokintone(app:str,updates:dict,env:config.KINTONE_ENV): def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/settings.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
data = {"app":app} data = {"app":app}
data.update(updates) data.update(updates)
r = httpx.put(url,headers=headers,data=json.dumps(data)) r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json() return r.json()
def addfieldstokintone(app:str,fields:dict,env:config.KINTONE_ENV,revision:str = None): def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
if revision != None: if revision != None:
data = {"app":app,"revision":revision,"properties":fields} data = {"app":app,"revision":revision,"properties":fields}
else: else:
@@ -198,43 +197,43 @@ def addfieldstokintone(app:str,fields:dict,env:config.KINTONE_ENV,revision:str =
r.raise_for_status() r.raise_for_status()
return r.json() return r.json()
def updatefieldstokintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV): def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
data = {"app":app,"properties":fields} data = {"app":app,"properties":fields}
r = httpx.put(url,headers=headers,data=json.dumps(data)) r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json() return r.json()
def deletefieldsfromkintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV): def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
params = {"app":app,"revision":revision,"fields":fields} params = {"app":app,"revision":revision,"fields":fields}
#r = httpx.delete(url,headers=headers,content=json.dumps(params)) #r = httpx.delete(url,headers=headers,content=json.dumps(params))
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params)) r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
return r.json() return r.json()
def deoployappfromkintone(app:str,revision:str,env:config.KINTONE_ENV): def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
data = {"apps":[{"app":app,"revision":revision}],"revert": False} data = {"apps":[{"app":app,"revision":revision}],"revert": False}
r = httpx.post(url,headers=headers,data=json.dumps(data)) r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json return r.json
# 既定項目に含めるアプリのフィールドのみ取得する # 既定項目に含めるアプリのフィールドのみ取得する
# スペース、枠線、ラベルを含まない # スペース、枠線、ラベルを含まない
def getfieldsfromkintone(app:str,env:config.KINTONE_ENV): def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app} params = {"app":app}
url = f"{env.BASE_URL}{config.API_V1_STR}/app/form/fields.json" url = f"{c.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
# フォームに配置するフィールドのみ取得する # フォームに配置するフィールドのみ取得する
# スペース、枠線、ラベルも含める # スペース、枠線、ラベルも含める
def getformfromkintone(app:str,env:config.KINTONE_ENV): def getformfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app} params = {"app":app}
url = f"{env.BASE_URL}{config.API_V1_STR}/form.json" url = f"{c.BASE_URL}{config.API_V1_STR}/form.json"
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
@@ -287,10 +286,10 @@ def analysefields(excel,kintone):
return {"update":updatefields,"add":addfields,"del":delfields} return {"update":updatefields,"add":addfields,"del":delfields}
def getprocessfromkintone(app:str,env:config.KINTONE_ENV): def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
params = {"app":app} params = {"app":app}
url = f"{env.BASE_URL}{config.API_V1_STR}/app/status.json" url = f"{c.BASE_URL}{config.API_V1_STR}/app/status.json"
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
@@ -375,24 +374,24 @@ def getkintoneorgs(c:config.KINTONE_ENV):
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
def uploadkintonefiles(file,env:config.KINTONE_ENV): def uploadkintonefiles(file,c:config.KINTONE_ENV):
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"): if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
return {'fileKey':file} return {'fileKey':file}
upload_files = {'file': open(file,'rb')} upload_files = {'file': open(file,'rb')}
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
data ={'name':'file','filename':os.path.basename(file)} data ={'name':'file','filename':os.path.basename(file)}
url = f"{env.BASE_URL}/k/v1/file.json" url = f"{c.BASE_URL}/k/v1/file.json"
r = httpx.post(url,headers=headers,data=data,files=upload_files) r = httpx.post(url,headers=headers,data=data,files=upload_files)
#{"name":data['filename'],'fileKey':r['fileKey']} #{"name":data['filename'],'fileKey':r['fileKey']}
return r.json() return r.json()
def updateappjscss(app,uploads,env:config.KINTONE_ENV): def updateappjscss(app,uploads,c:config.KINTONE_ENV):
dsjs = [] dsjs = []
dscss = [] dscss = []
#mobile側 #mobile側
mbjs = [] mbjs = []
mbcss = [] mbcss = []
customize = getappcustomize(app, env) customize = getappcustomize(app, c)
current_js = customize['desktop'].get('js', []) current_js = customize['desktop'].get('js', [])
current_css = customize['desktop'].get('css', []) current_css = customize['desktop'].get('css', [])
current_mobile_js = customize['mobile'].get('js', []) current_mobile_js = customize['mobile'].get('js', [])
@@ -431,16 +430,16 @@ def updateappjscss(app,uploads,env:config.KINTONE_ENV):
ds ={'js':dsjs,'css':dscss} ds ={'js':dsjs,'css':dscss}
mb ={'js':mbjs,'css':mbcss} mb ={'js':mbjs,'css':mbcss}
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb,'revision':customize["revision"]} data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb,'revision':customize["revision"]}
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
print(json.dumps(data)) print(json.dumps(data))
r = httpx.put(url,headers=headers,data=json.dumps(data)) r = httpx.put(url,headers=headers,data=json.dumps(data))
return r.json() return r.json()
#kintone カスタマイズ情報 #kintone カスタマイズ情報
def getappcustomize(app,env:config.KINTONE_ENV): def getappcustomize(app,c:config.KINTONE_ENV):
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
params = {"app":app} params = {"app":app}
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
@@ -452,9 +451,9 @@ def getTempPath(filename):
fpath = os.path.join(rootdir,"Temp",filename) fpath = os.path.join(rootdir,"Temp",filename)
return fpath return fpath
def createappjs(domain_url,app): def createappjs(domainid,app):
db = SessionLocal() db = SessionLocal()
flows = get_flows_by_app(db,domain_url,app) flows = get_flows_by_app(db,domainid,app)
db.close() db.close()
content={} content={}
for flow in flows: for flow in flows:
@@ -522,7 +521,7 @@ async def upload(request:Request,files:t.List[UploadFile] = File(...)):
return {"files": [file.filename for file in files]} return {"files": [file.filename for file in files]}
@r.post("/updatejscss") @r.post("/updatejscss")
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env:config.KINTONE_ENV = Depends(getkintoneenv)): async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
try: try:
jscs=[] jscs=[]
for file in files: for file in files:
@@ -543,21 +542,21 @@ async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env
raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e) raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e)
@r.get("/app") @r.get("/app")
async def app(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)): async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try: try:
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{env.BASE_URL}{config.API_V1_STR}/app.json" url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
params ={"id":app} params ={"id":app}
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
except Exception as e: except Exception as e:
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({env.DOMAIN_NAME}->{app}):",e) raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAME}->{app}):",e)
@r.get("/allapps") @r.get("/allapps")
async def allapps(request:Request,env:config.KINTONE_ENV=Depends(getkintoneenv)): async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try: try:
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{env.BASE_URL}{config.API_V1_STR}/apps.json" url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json"
offset = 0 offset = 0
limit = 100 limit = 100
all_apps = [] all_apps = []
@@ -573,17 +572,17 @@ async def allapps(request:Request,env:config.KINTONE_ENV=Depends(getkintoneenv))
return {"apps": all_apps} return {"apps": all_apps}
except Exception as e: except Exception as e:
raise APIException('kintone:allapps', request.url._url, f"Error occurred while get allapps({env.DOMAIN_NAME}):", e) raise APIException('kintone:allapps', request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAME}):", e)
@r.get("/appfields") @r.get("/appfields")
async def appfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)): async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
try: try:
return getfieldsfromkintone(app,env) return getfieldsfromkintone(app,env)
except Exception as e: except Exception as e:
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAME}->{app}):",e) raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAME}->{app}):",e)
@r.get("/allfields") @r.get("/allfields")
async def allfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)): async def allfields(request:Request,app:str,env = Depends(getkintoneenv)):
try: try:
field_resp = getfieldsfromkintone(app,env) field_resp = getfieldsfromkintone(app,env)
form_resp = getformfromkintone(app,env) form_resp = getformfromkintone(app,env)
@@ -592,38 +591,38 @@ async def allfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(get
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e) raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
@r.get("/appprocess") @r.get("/appprocess")
async def appprocess(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)): async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
try: try:
return getprocessfromkintone(app,env) return getprocessfromkintone(app,env)
except Exception as e: except Exception as e:
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAME}->{app}):",e) raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAME}->{app}):",e)
@r.get("/alljscss") @r.get("/alljscss")
async def alljscs(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)): async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try: try:
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
url = f"{env.BASE_URL}{config.API_V1_STR}/app/customize.json" url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
params = {"app":app} params = {"app":app}
r = httpx.get(url,headers=headers,params=params) r = httpx.get(url,headers=headers,params=params)
return r.json() return r.json()
except Exception as e: except Exception as e:
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({env.DOMAIN_NAME}->{app}):",e) raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAME}->{app}):",e)
@r.post("/createapp",) @r.post("/createapp",)
async def createapp(request:Request,name:str,env:config.KINTONE_ENV=Depends(getkintoneenv)): async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
try: try:
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"} headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
data = {"name":name} data = {"name":name}
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
r = httpx.post(url,headers=headers,data=json.dumps(data)) r = httpx.post(url,headers=headers,data=json.dumps(data))
result = r.json() result = r.json()
if result.get("app") != None: if result.get("app") != None:
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json" url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
data = {"apps":[result],"revert": False} data = {"apps":[result],"revert": False}
r = httpx.post(url,headers=headers,data=json.dumps(data)) r = httpx.post(url,headers=headers,data=json.dumps(data))
return r.json return r.json
except Exception as e: except Exception as e:
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({env.DOMAIN_NAME}->{name}):",e) raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAME}->{name}):",e)
@r.post("/createappfromexcel",) @r.post("/createappfromexcel",)
@@ -762,7 +761,7 @@ async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Dep
try: try:
jscs=[] jscs=[]
files=[] files=[]
files.append(createappjs(env.BASE_URL, app)) files.append(createappjs(env.DOMAIN_ID, app))
files.append(getTempPath('alc_runtime.js')) files.append(getTempPath('alc_runtime.js'))
files.append(getTempPath('alc_runtime.css')) files.append(getTempPath('alc_runtime.css'))
for file in files: for file in files:

View File

@@ -1,7 +1,5 @@
from http import HTTPStatus
from fastapi import Query, Request,Depends, APIRouter, UploadFile,HTTPException,File from fastapi import Query, Request,Depends, APIRouter, UploadFile,HTTPException,File
from fastapi.responses import JSONResponse from app.core.operation import log_operation
# from app.core.operation import log_operation
from app.db import Base,engine from app.db import Base,engine
from app.db.session import get_db from app.db.session import get_db
from app.db.crud import * from app.db.crud import *
@@ -9,44 +7,27 @@ 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,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",tags=["App"], "/apps",
response_model=ApiReturnModel[List[AppList]|None], response_model=List[AppList],
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def apps_list( async def apps_list(
request: Request, request: Request,
user = Depends(get_current_active_user), user = Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
platformapps = get_apps(db)
domain = get_activedomain(db, user.id)
domain = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id)
if not domain:
return ApiReturnModel(data = None)
filtered_apps = []
platformapps = get_apps(db,domain.url)
kintoneevn = config.KINTONE_ENV(domain) 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}
url = f"{kintoneevn.BASE_URL}{config.API_V1_STR}/apps.json" url = f"{kintoneevn.BASE_URL}{config.API_V1_STR}/apps.json"
@@ -63,21 +44,25 @@ async def apps_list(
break break
offset += limit offset += limit
kintone_apps_dict = {app['appId']: app for app in all_apps} tempapps = platformapps.copy()
for papp in platformapps: for papp in tempapps:
if papp.appid in kintone_apps_dict: exist = False
papp.appname = kintone_apps_dict[papp.appid]["name"] for kapp in all_apps:
filtered_apps.append(papp) if kapp['appId'] == papp.appid:
return ApiReturnModel(data = filtered_apps) exist = True
break
if not exist:
platformapps.remove(papp)
return platformapps
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,
app: AppVersion, app: AppVersion,
user=Depends(get_current_active_user), user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
@@ -85,20 +70,6 @@ async def apps_update(
except Exception as e: except Exception as e:
raise APIException('platform:apps',request.url._url,f"Error occurred while get create app :",e) raise APIException('platform:apps',request.url._url,f"Error occurred while get create app :",e)
@r.delete(
"/apps/{domainurl}/{appid}", response_model_exclude_none=True
)
async def apps_delete(
request: Request,
domainurl:str,
appid: str,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
return delete_apps(db, domainurl,appid)
except Exception as e:
raise APIException('platform:apps',request.url._url,f"Error occurred while delete apps({domainurl}:{appid}):",e)
@r.get( @r.get(
"/appsettings/{id}", "/appsettings/{id}",
@@ -208,19 +179,17 @@ async def flow_details(
@r.get( @r.get(
"/flows/{appid}", "/flows/{appid}",
response_model=List[Flow|None], response_model=List[Flow],
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def flow_list( async def flow_list(
request: Request, request: Request,
appid: str, appid: str,
user=Depends(get_current_active_user), user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id) domain = 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
@@ -228,37 +197,30 @@ 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|None, response_model_exclude_none=True) @r.post("/flow", response_model=Flow, response_model_exclude_none=True)
async def flow_create( async def flow_create(
request: Request, request: Request,
flow: FlowIn, flow: FlowBase,
user=Depends(get_current_active_user), user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id) domain = 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|None, response_model_exclude_none=True "/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
) )
async def flow_edit( async def flow_edit(
request: Request, request: Request,
flowid: str, flow: FlowBase,
flow: FlowIn,
user=Depends(get_current_active_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id) return edit_flow(db, flow)
if not domain:
return None
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)
@@ -277,60 +239,49 @@ 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",tags=["Domain"], "/domains/{tenantid}",
response_model=ApiReturnPage[Domain], response_model=List[Domain],
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def domain_details( async def domain_details(
request: Request, request: Request,
user=Depends(get_current_active_user), tenantid:str,
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
if user.is_superuser: domains = get_domains(db,tenantid)
domains = dbdomain.get_domains(db)
else:
domains = dbdomain.get_domains_by_owner(db,user.id)
return 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", tags=["Domain"], @r.post("/domain", response_model=Domain, response_model_exclude_none=True)
response_model=ApiReturnModel[Domain],
response_model_exclude_none=True)
async def domain_create( async def domain_create(
request: Request, request: Request,
domain: DomainIn, domain: DomainBase,
user=Depends(get_current_active_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
return ApiReturnModel(data = dbdomain.create_domain(db, domain,user.id)) return create_domain(db, domain)
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", tags=["Domain"], "/domain", response_model=Domain, response_model_exclude_none=True
response_model=ApiReturnModel[Domain|None],
response_model_exclude_none=True
) )
async def domain_edit( async def domain_edit(
request: Request, request: Request,
domain: DomainIn, domain: DomainBase,
user=Depends(get_current_active_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
return ApiReturnModel(data = dbdomain.edit_domain(db, domain,user.id)) return edit_domain(db, domain)
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}",tags=["Domain"], "/domain/{id}", response_model=Domain, response_model_exclude_none=True
response_model=ApiReturnModel[DomainOut|None],
response_model_exclude_none=True,
) )
async def domain_delete( async def domain_delete(
request: Request, request: Request,
@@ -338,19 +289,19 @@ async def domain_delete(
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
return ApiReturnModel(data = dbdomain.delete_domain(db,id)) return delete_domain(db,id)
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)
@r.get( @r.get(
"/domain", "/domain",
response_model=List[Domain], # response_model=List[Domain],
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def userdomain_details( async def userdomain_details(
request: Request, request: Request,
userId: Optional[int] = Query(None, alias="userId"), userId: Optional[int] = Query(None, alias="userId"),
user=Depends(get_current_active_user), user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
@@ -360,93 +311,71 @@ 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}",tags=["Domain"], "/domain/{userid}",
response_model=ApiReturnModel[DomainOut|None],
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def create_userdomain( async def create_userdomain(
request: Request, request: Request,
userid: int, userid: int,
domainid:int , domainids:List[int] ,
user=Depends(get_current_active_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
if user.is_superuser: domain = add_userdomain(db, userid,domainids)
domain = dbdomain.add_userdomain(db,user.id,userid,domainid) return domain
else:
domain = dbdomain.add_userdomain_by_owner(db,user.id,userid,domainid)
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}",tags=["Domain"], "/domain/{domainid}/{userid}", response_model_exclude_none=True
response_model=ApiReturnModel[DomainOut|None],
response_model_exclude_none=True,
) )
async def delete_userdomain( async def userdomain_delete(
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:
return ApiReturnModel(data = dbdomain.delete_userdomain(db,userid,domainid)) return delete_userdomain(db, userid,domainid)
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(
"/defaultdomain",tags=["Domain"], "/activedomain",
response_model=ApiReturnModel[DomainOut|None], response_model=Domain,
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def get_defaultuserdomain( async def get_useractivedomain(
request: Request, request: Request,
user=Depends(get_current_active_user), userId: Optional[int] = Query(None, alias="userId"),
user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
return ApiReturnModel(data =dbdomain.get_default_domain(db, user.id)) # domain = get_activedomain(db, user.id)
domain = get_activedomain(db, userId if userId is not None else user.id)
return domain
except Exception as e: except Exception as e:
raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while get user({user.id}) defaultdomain:",e) raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
@r.put( @r.put(
"/defaultdomain/{domainid}",tags=["Domain"], "/activedomain/{domainid}",
response_model=ApiReturnModel[DomainOut|None],
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def update_activeuserdomain( async def update_activeuserdomain(
request: Request, request: Request,
domainid:int, domainid:int,
user=Depends(get_current_active_user), userId: Optional[int] = Query(None, alias="userId"),
user=Depends(get_current_user),
db=Depends(get_db), db=Depends(get_db),
): ):
try: try:
domain = dbdomain.set_default_domain(db,user.id,domainid) domain = active_userdomain(db, userId if userId is not None else user.id,domainid)
return ApiReturnModel(data= domain) return domain
except Exception as e: except Exception as e:
raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while update user({user.id}) defaultdomain:",e) raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",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",

View File

@@ -1,186 +1,107 @@
from fastapi import APIRouter, Request, Depends, Response, Security, encoders from fastapi import APIRouter, Request, Depends, Response, 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_users, get_users,
get_user, get_user,
create_user, create_user,
delete_user, delete_user,
edit_user, edit_user,
assign_userrole,
get_roles,
) )
from app.db.schemas import UserCreate, UserEdit, User, UserOut,RoleBase,Permission from app.db.schemas import UserCreate, UserEdit, User, UserOut
from app.core.auth import get_current_user,get_current_active_user, get_current_active_superuser from app.core.auth import 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",tags=["User"], "/users",
response_model=ApiReturnPage[User], response_model=t.List[User],
response_model_exclude_none=True, response_model_exclude_none=True,
) )
async def users_list( async def users_list(
request: Request, response: Response,
db=Depends(get_db), db=Depends(get_db),
current_user=Depends(get_current_active_user), current_user=Depends(get_current_active_superuser),
): ):
try: """
if current_user.is_superuser: Get all users
users = dbuser.get_users(db) """
else: users = get_users(db)
users = dbuser.get_users_not_admin(db) # This is necessary for react-admin to work
response.headers["Content-Range"] = f"0-9/{len(users)}"
return users return users
except Exception as e:
raise APIException('user:users',request.url._url,f"Error occurred while get user list",e)
@r.get("/users/me", tags=["User"],
response_model=ApiReturnModel[User], @r.get("/users/me", response_model=User, response_model_exclude_none=True)
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}",tags=["User"], "/users/{user_id}",
response_model=ApiReturnModel[User|None], response_model=User,
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_user), current_user=Depends(get_current_active_superuser),
): ):
try: """
user = dbuser.get(db, user_id) Get any user details
if user: """
if user.is_superuser and not current_user.is_superuser: user = get_user(db, user_id)
user = None return user
return ApiReturnModel(data = user) # return encoders.jsonable_encoder(
except Exception as e: # user, skip_defaults=True, exclude_none=True,
raise APIException('user:users',request.url._url,f"Error occurred while get user({user_id}) detail:",e) # )
@r.post("/users", tags=["User"], @r.post("/users", response_model=User, response_model_exclude_none=True)
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_user), current_user=Depends(get_current_active_superuser),
): ):
try: """
if user.is_superuser and not current_user.is_superuser: Create a new user
return ApiReturnModel(data = None) """
return ApiReturnModel(data =dbuser.create_user(db, user,current_user.id)) return create_user(db, user)
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}", tags=["User"], "/users/{user_id}", response_model=User, response_model_exclude_none=True
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_user), current_user=Depends(get_current_active_superuser),
): ):
try: """
if user.is_superuser and not current_user.is_superuser: Update existing user
return ApiReturnModel(data = None) """
return ApiReturnModel(data = dbuser.edit_user(db,user_id,user,current_user.id)) return edit_user(db, user_id, user)
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}", tags=["User"], "/users/{user_id}", response_model=User, response_model_exclude_none=True
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_user), current_user=Depends(get_current_active_superuser),
): ):
try: """
user = dbuser.get(db,user_id) Delete existing user
if user.is_superuser and not current_user.is_superuser: """
return ApiReturnModel(data = None) return delete_user(db, user_id)
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",tags=["User"],
response_model=ApiReturnModel[User],
response_model_exclude_none=True,)
async def assign_role(
request: Request,
user_id:int,
roles:t.List[int],
db=Depends(get_db)
):
try:
return ApiReturnModel(data = dbuser.assign_userrole(db,user_id,roles))
except Exception as e:
raise APIException('user:userrole',request.url._url,f"Error occurred while assign user({user_id}) roles({roles}):",e)
@r.get(
"/roles",tags=["User"],
response_model=ApiReturnModel[t.List[RoleBase]|None],
response_model_exclude_none=True,
)
async def roles_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:
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)

View File

@@ -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 += str(e.detail) content += e.detail
else: else:
self.detail = str(e) self.detail = str(e)
self.status_code = 500 self.status_code = 500

View File

@@ -1,14 +1,13 @@
from fastapi.security import SecurityScopes
import jwt import jwt
from fastapi import Depends, HTTPException, Request, Security, status from fastapi import Depends, HTTPException, status
from jwt import PyJWTError 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(
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme) db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
): ):
credentials_exception = HTTPException( credentials_exception = HTTPException(
@@ -17,25 +16,17 @@ async def get_current_user(security_scopes: SecurityScopes,
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
try: try:
payload = jwt.decode( payload = jwt.decode(
token, security.SECRET_KEY, algorithms=[security.ALGORITHM] token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
) )
id: int = payload.get("sub") id: int = payload.get("sub")
if id is None: if id is None:
raise credentials_exception raise credentials_exception
permissions: str = payload.get("permissions") permissions: str = payload.get("permissions")
if not permissions =="ALL":
for scope in security_scopes.scopes:
if scope not in permissions.split(";"):
raise HTTPException(
status_code=403, detail="The user doesn't have enough privileges"
)
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 = dbuser.get_user(db, token_data.id) user = get_user(db, token_data.id)
if user is None: if user is None:
raise credentials_exception raise credentials_exception
return user return user

View File

@@ -1,49 +0,0 @@
import math
from fastapi import Query
from fastapi_pagination.bases import AbstractPage,AbstractParams,RawParams
from pydantic import BaseModel
from typing import Any, Generic, List, Type,TypeVar,Generic,Sequence
from fastapi_pagination import Page,utils
T = TypeVar('T')
class ApiReturnModel(BaseModel,Generic[T]):
code:int = 0
msg:str ="OK"
data:T
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)

View File

@@ -0,0 +1,28 @@
from functools import wraps
from app.db.crud import create_operationlog
from app.db.schemas import OperationCreate
from fastapi.encoders import jsonable_encoder
def log_operation(operation):
def decorator(func):
@wraps(func)
async def wrapper(*args,**kwargs):
db = kwargs.get('db')
request = kwargs.get('request')
result = await func(*args,**kwargs)
detial = f"Request: {request.method} {request.url}\
Request Headers: {request.headers}\
Request Body: {request.body()}\
Result: {jsonable_encoder(result,exclude_unset=True)}"
function = func.__name__
db_operation = OperationCreate(tenantid ="t",
domainurl="d",
userid=3,
operation=operation,
function=function,
detail=detial)
create_operationlog(db,db_operation)
return result
return wrapper
return decorator

View File

@@ -1,4 +1,3 @@
from datetime import datetime
from fastapi import HTTPException, status from fastapi import HTTPException, status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import and_ from sqlalchemy import and_
@@ -19,15 +18,10 @@ def get_user_by_email(db: Session, email: str) -> schemas.UserBase:
return db.query(models.User).filter(models.User.email == email).first() return db.query(models.User).filter(models.User.email == email).first()
def get_allusers(
db: Session
) -> t.List[schemas.UserOut]:
return db.query(models.User).all()
def get_users( def get_users(
db: Session db: Session, skip: int = 0, limit: int = 100
) -> t.List[schemas.UserOut]: ) -> t.List[schemas.UserOut]:
return db.query(models.User).filter(models.User.is_superuser == False) return db.query(models.User).offset(skip).limit(limit).all()
def create_user(db: Session, user: schemas.UserCreate): def create_user(db: Session, user: schemas.UserCreate):
@@ -75,48 +69,27 @@ def edit_user(
db.refresh(db_user) db.refresh(db_user)
return db_user return db_user
def get_roles(
db: Session
) -> t.List[schemas.RoleBase]:
return db.query(models.Role).all()
def assign_userrole( db: Session, user_id: int, roles: t.List[int]):
db_user = db.query(models.User).get(user_id)
if db_user:
for role in db_user.roles:
db_user.roles.remove(role)
for roleid in roles:
role = db.query(models.Role).get(roleid)
if role:
db_user.roles.append(role)
db.commit()
db.refresh(db_user)
return db_user
def get_apps( def get_apps(
db: Session, db: Session
domainurl:str
) -> t.List[schemas.AppList]: ) -> t.List[schemas.AppList]:
return db.query(models.App).filter(models.App.domainurl == domainurl).all() return db.query(models.App).all()
def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int): def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
db_app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first() app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first()
if not db_app: if app:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found") app.version = app.version + 1
db_app = app
db_app.version = db_app.version + 1 appver = app.version
appversion = models.AppVersion( else:
appver = 1
db_app = models.App(
domainurl = appedit.domainurl, domainurl = appedit.domainurl,
appid=appedit.appid, appid=appedit.appid,
appname=db_app.appname, appname=appedit.appname,
version = db_app.version, version = 1,
versionname = appedit.versionname, updateuser= userid
comment = appedit.comment,
updateuserid = userid,
createuserid = userid
) )
db.add(appversion)
db.add(db_app) db.add(db_app)
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == appedit.domainurl,models.App.appid == appedit.appid)) flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == appedit.domainurl,models.App.appid == appedit.appid))
@@ -129,9 +102,7 @@ def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
name = flow.name, name = flow.name,
content = flow.content, content = flow.content,
createuser = userid, createuser = userid,
version = db_app.version, version = appver
updateuserid = userid,
createuserid = userid
) )
db.add(db_flowhistory) db.add(db_flowhistory)
@@ -139,17 +110,6 @@ def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
db.refresh(db_app) db.refresh(db_app)
return db_app return db_app
def delete_apps(db: Session, domainurl: str,appid: str ):
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid ==appid)).first()
if not db_app:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="App not found")
db.delete(db_app)
db_flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid ==appid))
for flow in db_flows:
db.delete(flow)
db.commit()
return db_app
def get_appsetting(db: Session, id: int): def get_appsetting(db: Session, id: int):
app = db.query(models.AppSetting).get(id) app = db.query(models.AppSetting).get(id)
if not app: if not app:
@@ -205,28 +165,16 @@ def get_actions(db: Session):
return actions return actions
def create_flow(db: Session, domainurl: str, flow: schemas.FlowIn,userid:int): def create_flow(db: Session, domainurl: str, flow: schemas.FlowBase):
db_flow = models.Flow( db_flow = models.Flow(
flowid=flow.flowid, flowid=flow.flowid,
appid=flow.appid, appid=flow.appid,
eventid=flow.eventid, eventid=flow.eventid,
domainurl=domainurl, domainurl=domainurl,
name=flow.name, name=flow.name,
content=flow.content, content=flow.content
createuserid = userid,
updateuserid = userid
) )
db.add(db_flow) db.add(db_flow)
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid == flow.appid)).first()
if not db_app:
db_app = models.App(
domainurl = domainurl,
appid=flow.appid,
appname=flow.appname,
version = 0,
createuserid= userid,
updateuserid = userid
)
db.commit() db.commit()
db.refresh(db_flow) db.refresh(db_flow)
return db_flow return db_flow
@@ -241,19 +189,15 @@ def delete_flow(db: Session, flowid: str):
def edit_flow( def edit_flow(
db: Session, domainurl: str, flow: schemas.FlowIn,userid:int db: Session, flow: schemas.FlowBase
) -> schemas.Flow: ) -> schemas.Flow:
db_flow = get_flow(db, flow.flowid) db_flow = get_flow(db, flow.flowid)
if not db_flow: if not db_flow:
#見つからない時新規作成 raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
return create_flow(db,domainurl,flow,userid) update_data = flow.dict(exclude_unset=True)
db_flow.appid =flow.appid for key, value in update_data.items():
db_flow.eventid=flow.eventid setattr(db_flow, key, value)
db_flow.domainurl=domainurl
db_flow.name=flow.name
db_flow.content=flow.content
db_flow.updateuserid = userid
db.add(db_flow) db.add(db_flow)
db.commit() db.commit()
@@ -269,8 +213,8 @@ def get_flows(db: Session, flowid: str):
def get_flow(db: Session, flowid: str): def get_flow(db: Session, flowid: str):
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first() flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
# if not flow: if not flow:
# raise HTTPException(status_code=404, detail="Data not found") raise HTTPException(status_code=404, detail="Data not found")
return flow return flow
def get_flows_by_app(db: Session,domainurl: str, appid: str): def get_flows_by_app(db: Session,domainurl: str, appid: str):
@@ -279,80 +223,48 @@ def get_flows_by_app(db: Session,domainurl: str, appid: str):
raise Exception("Data not found") raise Exception("Data not found")
return flows return flows
def create_domain(db: Session, domain: schemas.DomainIn,userid:int): def create_domain(db: Session, domain: schemas.DomainBase):
domain.encrypt_kintonepwd() domain.encrypt_kintonepwd()
db_domain = models.Domain( db_domain = models.Domain(
tenantid = domain.tenantid, tenantid = domain.tenantid,
name=domain.name, name=domain.name,
url=domain.url, url=domain.url,
is_active=domain.is_active,
kintoneuser=domain.kintoneuser, kintoneuser=domain.kintoneuser,
kintonepwd=domain.kintonepwd, kintonepwd=domain.kintonepwd
createuserid = userid,
updateuserid = userid,
ownerid = domain.ownerid
) )
db.add(db_domain) db.add(db_domain)
#add_userdomain(db,userid,db_domain.id)
db.commit() db.commit()
db.refresh(db_domain) db.refresh(db_domain)
return db_domain return db_domain
def delete_domain(db: Session,id: int): def delete_domain(db: Session,id: int):
db_domain = db.query(models.Domain).get(id) db_domain = db.query(models.Domain).get(id)
#if not db_domain: if not db_domain:
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found") raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
if db_domain:
db.delete(db_domain) db.delete(db_domain)
db.commit() db.commit()
return True return db_domain
def edit_domain( def edit_domain(
db: Session, domain: schemas.DomainIn,userid:int db: Session, domain: schemas.DomainBase
) -> schemas.Domain: ) -> schemas.Domain:
if domain.kintonepwd != "":
domain.encrypt_kintonepwd() domain.encrypt_kintonepwd()
db_domain = db.query(models.Domain).get(domain.id) db_domain = db.query(models.Domain).get(domain.id)
if not db_domain: if not db_domain:
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found") raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
db_domain.tenantid = domain.tenantid update_data = domain.dict(exclude_unset=True)
db_domain.name=domain.name
db_domain.url=domain.url for key, value in update_data.items():
if db_domain.is_active == True and domain.is_active == False: if key != "id" and not (key == "kintonepwd" and (value is None or value == "")):
db_userdomains = db.query(models.UserDomain).filter(and_(models.UserDomain.domainid == db_domain.id,models.UserDomain.active == True)).all() setattr(db_domain, key, value)
for userdomain in db_userdomains: print(str(db_domain))
userdomain.active = False
db.add(userdomain)
db_domain.is_active=domain.is_active
db_domain.kintoneuser=domain.kintoneuser
if domain.kintonepwd != "":
db_domain.kintonepwd = domain.kintonepwd
db_domain.updateuserid = userid
db_domain.ownerid = domain.ownerid
db.add(db_domain) db.add(db_domain)
db.commit() db.commit()
db.refresh(db_domain) db.refresh(db_domain)
return db_domain return db_domain
def add_userdomain(db: Session, userid:int,domainids:list[str]):
def add_admindomain(db: Session,userid:int,domainid:int):
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.is_active)).first()
if db_domain:
user_domain = models.UserDomain(userid = userid, domainid = domainid )
db.add(user_domain)
db.commit()
return db_domain
def add_userdomain(db: Session,ownerid:int, userid:int,domainid:int):
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.ownerid == ownerid,models.Domain.is_active)).first()
if db_domain:
user_domain = models.UserDomain(userid = userid, domainid = domainid )
db.add(user_domain)
db.commit()
return db_domain
def add_userdomains(db: Session, userid:int,domainids:list[str]):
dbCommits = list(map(lambda domainid: models.UserDomain(userid = userid, domainid = domainid ), domainids)) dbCommits = list(map(lambda domainid: models.UserDomain(userid = userid, domainid = domainid ), domainids))
db.bulk_save_objects(dbCommits) db.bulk_save_objects(dbCommits)
db.commit() db.commit()
@@ -360,20 +272,16 @@ def add_userdomains(db: Session, userid:int,domainids:list[str]):
def delete_userdomain(db: Session, userid: int,domainid: int): def delete_userdomain(db: Session, userid: int,domainid: int):
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first() db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
#if not db_domain: if not db_domain:
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found") raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
if db_domain:
db.delete(db_domain) db.delete(db_domain)
db.commit() db.commit()
return True return db_domain
def active_userdomain(db: Session, userid: int,domainid: int): def active_userdomain(db: Session, userid: int,domainid: int):
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.is_active)).first()
if db_domain:
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all() db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
# if not db_userdomains: if not db_userdomains:
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found") raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
for domain in db_userdomains: for domain in db_userdomains:
if domain.domainid == domainid: if domain.domainid == domainid:
domain.active = True domain.active = True
@@ -381,37 +289,30 @@ def active_userdomain(db: Session, userid: int,domainid: int):
domain.active = False domain.active = False
db.add(domain) db.add(domain)
db.commit() db.commit()
return db_domain return db_userdomains
def get_activedomain(db: Session, userid: int): def get_activedomain(db: Session, userid: int):
# user_domains = (db.query(models.Domain,models.UserDomain.active) db_domain = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(and_(models.UserDomain.userid == userid,models.UserDomain.active == True)).first()
# .join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ) # if not db_domain:
# .filter(models.UserDomain.userid == userid)
# .all())
db_domain=(db.query(models.Domain).filter(models.Domain.is_active)
.join(models.UserDomain,models.UserDomain.domainid == models.Domain.id).filter(and_(models.UserDomain.active,models.UserDomain.userid == userid)).first())
# if len(user_domains)==1:
# db_domain = user_domains[0][0];
# else:
# db_domain = next((domain for domain,active in user_domains if active),None)
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found") # raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
return db_domain return db_domain
def get_domain(db: Session, userid: str): def get_domain(db: Session, userid: str):
domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all() domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all()
# if not domains: if not domains:
# raise HTTPException(status_code=404, detail="Data not found") raise HTTPException(status_code=404, detail="Data not found")
# for domain in domains: # for domain in domains:
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd) # decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
# domain.kintonepwd = decrypted_pwd # domain.kintonepwd = decrypted_pwd
return domains return domains
def get_alldomains(db: Session): def get_domains(db: Session,tenantid:str):
domains = db.query(models.Domain).all() domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
return domains if not domains:
raise HTTPException(status_code=404, detail="Data not found")
def get_domains(db: Session,userid:int): # for domain in domains:
domains = db.query(models.Domain).filter(models.Domain.ownerid == userid ).all() # decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
# domain.kintonepwd = decrypted_pwd
return domains return domains
def get_events(db: Session): def get_events(db: Session):
@@ -464,3 +365,17 @@ def create_log(db: Session, error:schemas.ErrorCreate):
def get_kintoneformat(db: Session): def get_kintoneformat(db: Session):
formats = db.query(models.KintoneFormat).order_by(models.KintoneFormat.id).all() formats = db.query(models.KintoneFormat).order_by(models.KintoneFormat.id).all()
return formats return formats
def create_operationlog(db: Session, log: schemas.OperationCreate):
db_log = models.OperationLog(
tenantid=log.tenantid,
domainurl=log.domainurl,
userid = log.userid,
operation = log.operation,
function = log.function,
detail = log.detail
)
db.add(db_log)
db.commit()
db.refresh(db_log)
return db_log

View File

@@ -1,2 +0,0 @@
from app.db.cruddb.dbuser import dbuser
from app.db.cruddb.dbdomain import dbdomain

View File

@@ -1,104 +0,0 @@
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

View File

@@ -1,139 +0,0 @@
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()

View File

@@ -1,92 +0,0 @@
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()

View File

@@ -1,7 +1,8 @@
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
from sqlalchemy.orm import relationship,as_declarative from sqlalchemy.ext.declarative import as_declarative
from sqlalchemy.orm import relationship
from datetime import datetime from datetime import datetime
from app.db.session import Base
from app.core.security import chacha20Decrypt from app.core.security import chacha20Decrypt
@as_declarative() @as_declarative()
@@ -10,21 +11,6 @@ class Base:
create_time = Column(DateTime, default=datetime.now) create_time = Column(DateTime, default=datetime.now)
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now) update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
userrole = Table(
"userrole",
Base.metadata,
Column("userid",Integer,ForeignKey("user.id")),
Column("roleid",Integer,ForeignKey("role.id")),
)
rolepermission = Table(
"rolepermission",
Base.metadata,
Column("roleid",Integer,ForeignKey("role.id")),
Column("permissionid",Integer,ForeignKey("permission.id")),
)
class User(Base): class User(Base):
__tablename__ = "user" __tablename__ = "user"
@@ -34,30 +20,6 @@ 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")
class Role(Base):
__tablename__ = "role"
name = Column(String(100))
description = Column(String(255))
level = Column(Integer)
users = relationship("User",secondary=userrole,back_populates="roles")
permissions = relationship("Permission",secondary=rolepermission,back_populates="roles")
class Permission(Base):
__tablename__ = "permission"
menu = Column(String(100))
function = Column(String(255))
privilege = Column(String(100))
roles = relationship("Role",secondary=rolepermission,back_populates="permissions")
class App(Base): class App(Base):
__tablename__ = "app" __tablename__ = "app"
@@ -66,25 +28,8 @@ class App(Base):
appname = Column(String(200), nullable=False) appname = Column(String(200), nullable=False)
appid = Column(String(100), index=True, nullable=False) appid = Column(String(100), index=True, nullable=False)
version = Column(Integer) version = Column(Integer)
createuserid = Column(Integer,ForeignKey("user.id")) updateuser = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id")) user = relationship('User')
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
class AppVersion(Base):
__tablename__ = "appversion"
domainurl = Column(String(200), nullable=False)
appname = Column(String(200), nullable=False)
appid = Column(String(100), index=True, nullable=False)
version = Column(Integer)
versionname = Column(String(200), nullable=False)
comment = Column(String(200), nullable=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])
class AppSetting(Base): class AppSetting(Base):
__tablename__ = "appsetting" __tablename__ = "appsetting"
@@ -120,10 +65,6 @@ class Flow(Base):
domainurl = Column(String(200)) domainurl = Column(String(200))
name = Column(String(200)) name = Column(String(200))
content = Column(String) content = Column(String)
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
class FlowHistory(Base): class FlowHistory(Base):
__tablename__ = "flowhistory" __tablename__ = "flowhistory"
@@ -134,11 +75,8 @@ class FlowHistory(Base):
domainurl = Column(String(200)) domainurl = Column(String(200))
name = Column(String(200)) name = Column(String(200))
content = Column(String) content = Column(String)
createuser = Column(Integer,ForeignKey("user.id"))
version = Column(Integer) version = Column(Integer)
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
class Tenant(Base): class Tenant(Base):
__tablename__ = "tenant" __tablename__ = "tenant"
@@ -149,7 +87,6 @@ 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"
@@ -158,16 +95,9 @@ 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
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
ownerid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
owner = relationship('User',foreign_keys=[ownerid])
class UserDomain(Base): class UserDomain(Base):
@@ -175,13 +105,7 @@ class UserDomain(Base):
userid = Column(Integer,ForeignKey("user.id")) userid = Column(Integer,ForeignKey("user.id"))
domainid = Column(Integer,ForeignKey("domain.id")) domainid = Column(Integer,ForeignKey("domain.id"))
is_default = Column(Boolean, default=False) active = 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"

View File

@@ -8,51 +8,25 @@ class Base(BaseModel):
create_time: datetime create_time: datetime
update_time: datetime update_time: datetime
class Permission(BaseModel):
id: int
menu:str
function:str
privilege:str
class RoleBase(BaseModel):
id: int
name:str
description:str
level:int
class RoleWithPermission(RoleBase):
permissions:t.List[Permission] = []
class UserBase(BaseModel): class UserBase(BaseModel):
email: str email: str
is_active: bool = True is_active: bool = True
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[RoleBase] = []
class UserOut(BaseModel): class UserOut(UserBase):
id: int pass
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
@@ -60,8 +34,6 @@ 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
@@ -82,17 +54,14 @@ class AppList(Base):
domainurl: str domainurl: str
appname: str appname: str
appid:str appid:str
updateuser: UserOut
version:int version:int
user:UserOut
class AppVersion(BaseModel): class AppVersion(BaseModel):
domainurl: str domainurl: str
appname: str appname: str
versionname: str
comment:str
appid:str appid:str
class TokenData(BaseModel): class TokenData(BaseModel):
id:int = 0 id:int = 0
email: str = None email: str = None
@@ -137,11 +106,9 @@ class Action(BaseModel):
class ConfigDict: class ConfigDict:
orm_mode = True orm_mode = True
class FlowIn(BaseModel): class FlowBase(BaseModel):
flowid: str flowid: str
# domainurl:str
appid: str appid: str
appname:str
eventid: str eventid: str
name: str = None name: str = None
content: str = None content: str = None
@@ -158,54 +125,28 @@ class Flow(Base):
class ConfigDict: class ConfigDict:
orm_mode = True orm_mode = True
class DomainIn(BaseModel): class DomainBase(BaseModel):
id: int id: int
tenantid: str tenantid: str
name: str name: str
url: str url: str
kintoneuser: str kintoneuser: str
kintonepwd: t.Optional[str] = None kintonepwd: str
is_active: bool
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)
self.kintonepwd = encrypted_pwd self.kintonepwd = encrypted_pwd
class DomainOut(BaseModel):
id: int
tenantid: str
name: str
url: str
kintoneuser: str
is_active: bool
ownerid:int
class ConfigDict:
orm_mode = True
class UserDomain(BaseModel):
id: int
is_default: bool
domain:DomainOut
user:UserOut
class Domain(Base): class Domain(Base):
id: int id: int
tenantid: str tenantid: str
name: str name: str
url: str url: str
kintoneuser: str kintoneuser: str
is_active: bool kintonepwd: str
updateuser: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
@@ -222,3 +163,11 @@ class ErrorCreate(BaseModel):
title:str title:str
location:str location:str
content:str content:str
class OperationCreate(BaseModel):
tenantid:str
domainurl:str
userid:int
operation:str
function:str
detail:str

View File

@@ -1,5 +1,6 @@
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker,declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.core import config from app.core import config

View File

@@ -1,6 +1,5 @@
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
@@ -15,22 +14,14 @@ 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",lifespan=lifespan title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api"
) )
origins = [ origins = [
@@ -45,8 +36,6 @@ 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()
@@ -54,7 +43,8 @@ add_pagination(app)
# request.state.db.close() # request.state.db.close()
# return response # return response
def startup_event(): @app.on_event("startup")
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)
@@ -70,7 +60,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= ApiReturnError(msg = f"{exc.detail}").model_dump(), content={"detail": f"{exc.detail}"},
) )
@app.get("/api/v1") @app.get("/api/v1")

View File

@@ -1,145 +0,0 @@
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

View File

@@ -1,6 +1,4 @@
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": "success"} assert response.json() == {"message": "Hello World"}

169
backend/conftest.py Normal file
View File

@@ -0,0 +1,169 @@
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.

File diff suppressed because one or more lines are too long

View File

@@ -1,211 +0,0 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" version="24.9.2">
<diagram name="ページ1" id="oKiyF3b1qzm0IX1SNAWJ">
<mxGraphModel dx="1395" dy="1078" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="Clf12EPbcqlM1huzJpPN-73" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d0cee2;strokeColor=#56517e;" vertex="1" parent="1">
<mxGeometry x="92" y="645" width="720" height="180" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-71" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="110" y="605" width="720" height="180" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-53" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffcccc;strokeColor=#36393d;" vertex="1" parent="1">
<mxGeometry x="140" y="570" width="720" height="180" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-52" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#cdeb8b;strokeColor=#36393d;" vertex="1" parent="1">
<mxGeometry x="170" y="530" width="720" height="180" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-51" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffcc99;strokeColor=#36393d;" vertex="1" parent="1">
<mxGeometry x="210" y="490" width="720" height="180" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-49" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="90" y="20" width="1080" height="290" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-1" target="Clf12EPbcqlM1huzJpPN-9">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-1" value="タスク開始" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="120" y="215" width="90" height="50" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-8" target="Clf12EPbcqlM1huzJpPN-9">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-47" value="DBからクロールのタスクを取得する" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-45">
<mxGeometry x="0.2449" y="53" relative="1" as="geometry">
<mxPoint x="17" y="-44" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-79" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-8" target="Clf12EPbcqlM1huzJpPN-78">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-8" value="DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#cce5ff;strokeColor=#36393d;" vertex="1" parent="1">
<mxGeometry x="570" y="-110" width="100" height="70" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-9" value="&lt;div style=&quot;background-color: rgb(255, 255, 254); font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;&quot;&gt;&lt;span style=&quot;&quot;&gt;タスクスケジューラ&lt;/span&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=1;fontColor=default;" vertex="1" parent="1">
<mxGeometry x="260" y="217.5" width="140" height="45" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;curved=1;startArrow=block;startFill=1;endArrow=none;endFill=0;dashed=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-9">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-18" value="タスクの割り当て" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-17">
<mxGeometry x="0.04" relative="1" as="geometry">
<mxPoint x="-18" y="30" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-31">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-15" value="&lt;b&gt;RabbitMQ&lt;/b&gt;" style="whiteSpace=wrap;html=1;rounded=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
<mxGeometry x="510" y="380" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-19" target="Clf12EPbcqlM1huzJpPN-21">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-19" value="MQコマンドの受信" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="250" y="560" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-19">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-21" target="Clf12EPbcqlM1huzJpPN-23">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-21" value="データ収集" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="490" y="560" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-23" target="Clf12EPbcqlM1huzJpPN-15">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-23" value="結果をMQメッセージを変換" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
<mxGeometry x="740" y="560" width="170" height="60" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-33" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-26" target="Clf12EPbcqlM1huzJpPN-31">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="980" y="390" />
<mxPoint x="980" y="350" />
<mxPoint x="745" y="350" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-26" value="データ収集結果&lt;br&gt;一時保存" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#eeeeee;strokeColor=#36393d;" vertex="1" parent="1">
<mxGeometry x="970" y="390" width="100" height="70" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-27" value="タスクコマンド" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="1">
<mxGeometry x="340" y="450" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-23" target="Clf12EPbcqlM1huzJpPN-26">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-29" value="コンテンツなど大きい情報を一時保存" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="950" y="500" width="230" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-30" value="収集結果通知MQ" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="740" y="450" width="110" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-37" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-31" target="Clf12EPbcqlM1huzJpPN-36">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-31" value="メッセージキューから&lt;div&gt;データ受信&lt;/div&gt;" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="677" y="212.5" width="150" height="50" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-34" value="MQのキーで収集結果を取得" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="760" y="350" width="170" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-36" target="Clf12EPbcqlM1huzJpPN-38">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-36" value="&lt;div style=&quot;background-color: rgb(255, 255, 254); font-family: Consolas, &amp;quot;Courier New&amp;quot;, monospace; line-height: 19px; white-space: pre;&quot;&gt;&lt;span&gt;データクリーニング&lt;br&gt;と重複排除&lt;/span&gt;&lt;/div&gt;" style="whiteSpace=wrap;html=1;rounded=1;fontColor=default;fontSize=11;" vertex="1" parent="1">
<mxGeometry x="890" y="207.5" width="148" height="60" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-43" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-38" target="Clf12EPbcqlM1huzJpPN-42">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-38" value="データベースに保存" style="whiteSpace=wrap;html=1;fontSize=11;rounded=1;" vertex="1" parent="1">
<mxGeometry x="904" y="60" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-42" target="Clf12EPbcqlM1huzJpPN-31">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-42" value="収集結果からサブ項目を抽出" style="whiteSpace=wrap;html=1;fontSize=11;rounded=1;" vertex="1" parent="1">
<mxGeometry x="692" y="60" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-46" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-38" target="Clf12EPbcqlM1huzJpPN-8">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-48" value="クロール結果をDBに登録する" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-46">
<mxGeometry x="0.1149" y="13" relative="1" as="geometry">
<mxPoint as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-50" value="&lt;b&gt;メインプログラム&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="540" y="20" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-54" value="&lt;b&gt;クローラー サブプログラム&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="485" y="490" width="185" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-55" value="&lt;b&gt;SharePointクローラー&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="220" y="640" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-60" value="MQ情報" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;align=center;fontSize=14;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" vertex="1" parent="1">
<mxGeometry x="1024" y="630" width="160" height="236" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-61" value="MQキー" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
<mxGeometry y="26" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-70" value="クローラーID" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
<mxGeometry y="56" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-62" value="分類" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
<mxGeometry y="86" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-63" value="タイトル" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
<mxGeometry y="116" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-64" value="URL" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
<mxGeometry y="146" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-65" value="サブ項目" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
<mxGeometry y="176" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-66" value="コンテンツ" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
<mxGeometry y="206" width="160" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-67" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-60" target="Clf12EPbcqlM1huzJpPN-23">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="930" y="840" as="sourcePoint" />
<mxPoint x="980" y="790" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-68" value="&lt;b&gt;OneNodeクローラー&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="180" y="680" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-69" value="&lt;b&gt;EIM クローラー&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="150" y="720" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-72" value="&lt;b&gt;Coredasuクローラー&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="120" y="755" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-74" value="&lt;b&gt;その他クローラー&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="92" y="795" width="130" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-75" value="PostGre SQL" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
<mxGeometry x="585" y="-40" width="90" height="30" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-76" value="再帰的な処理" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxGeometry x="1030" y="140" width="80" height="50" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-77" value="NEO4J" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
<mxGeometry x="570" y="-390" width="100" height="80" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-78" value="データ抽出" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="560" y="-250" width="120" height="60" as="geometry" />
</mxCell>
<mxCell id="Clf12EPbcqlM1huzJpPN-80" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-78" target="Clf12EPbcqlM1huzJpPN-77">
<mxGeometry relative="1" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

View File

@@ -1,6 +1,6 @@
{ {
"name": "k-tune", "name": "k-tune",
"version": "2.0.0 Beta", "version": "0.2.0",
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです", "description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
"productName": "k-tune | kintoneジェネレーター", "productName": "k-tune | kintoneジェネレーター",
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>", "author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",

View File

@@ -115,8 +115,7 @@ module.exports = configure(function (/* ctx */) {
// Quasar plugins // Quasar plugins
plugins: [ plugins: [
'Notify', 'Notify'
'Dialog'
] ]
}, },

View File

@@ -1,58 +1,64 @@
<template> <template>
<detail-field-table <div class="q-px-xs">
detailField="description" <div v-if="!isLoaded" class="spinner flex flex-center">
:name="name" <q-spinner color="primary" size="3em" />
:type="type" </div>
:filter="filter" <q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
:columns="columns" virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
:fetchData="fetchApps" :filter="filter" style="max-height: 65vh;">
@update:selected="(item) => { selected = item }" <template v-slot:body-cell-description="props">
/> <q-td :props="props">
<q-scroll-area class="description-cell">
<div v-html="props.row.description"></div>
</q-scroll-area>
</q-td>
</template>
</q-table>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref, PropType } from 'vue'; import { ref, onMounted, reactive, watchEffect } from 'vue'
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import DetailFieldTable from './dialog/DetailFieldTable.vue';
interface IAppDisplay {
id: string;
name: string;
description: string;
createdate: string;
}
export default { export default {
name: 'AppSelectBox', name: 'AppSelectBox',
components: {
DetailFieldTable
},
props: { props: {
name: String, name: String,
type: String, type: String,
filter: String, filter: String,
filterInitRowsFunc: { updateSelectApp: {
type: Function as PropType<(app: IAppDisplay) => boolean>, type: Function
} }
}, },
setup(props) { setup(props) {
const selected = ref<IAppDisplay[]>([]);
const columns = [ const columns = [
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true, sort: (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10) }, { name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true },
{ name: 'name', label: 'アプリ名', field: 'name', sortable: true, align: 'left' }, { name: 'name', label: 'アプリ名', field: 'name', sortable: true, align: 'left' },
{ name: 'description', label: '概要', field: 'description', align: 'left', sortable: false }, { name: 'description', label: '概要', field: 'description', align: 'left', sortable: false },
{ name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' } { name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' }
]; ]
const isLoaded = ref(false);
const rows: any[] = reactive([]);
const selected = ref([])
const fetchApps = async () => { watchEffect(()=>{
const res = await api.get('api/v1/allapps'); if (selected.value && selected.value[0] && props.updateSelectApp) {
return res.data.apps.map((item: any) => ({ props.updateSelectApp(selected.value[0])
}
});
onMounted(() => {
api.get('api/v1/allapps').then(res => {
res.data.apps.forEach((item: any) => {
rows.push({
id: item.appId, id: item.appId,
name: item.name, name: item.name,
description: item.description, description: item.description,
createdate: dateFormat(item.createdAt) createdate: dateFormat(item.createdAt)
})).filter(app => !props.filterInitRowsFunc || props.filterInitRowsFunc(app)); });
}; });
isLoaded.value = true;
});
});
const dateFormat = (dateStr: string) => { const dateFormat = (dateStr: string) => {
const date = new Date(dateStr); const date = new Date(dateStr);
@@ -64,13 +70,31 @@ export default {
const minutes = pad(date.getMinutes()); const minutes = pad(date.getMinutes());
const seconds = pad(date.getSeconds()); const seconds = pad(date.getSeconds());
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`; return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}; }
return { return {
columns, columns,
fetchApps, rows,
selected selected,
}; isLoaded,
pagination: ref({
rowsPerPage: 10
})
} }
}; },
}
</script> </script>
<style lang="scss">
.description-cell {
height: 60px;
width: 300px;
max-height: 60px;
max-width: 300px;
white-space: break-spaces;
}
.spinner {
min-height: 300px;
min-width: 400px;
}
</style>

View File

@@ -1,78 +1,37 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-md">
<q-table :loading="loading" :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"> <q-table :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
<template v-slot:body-cell-name="p">
<q-td class="content-box 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 == currentDomainId" color="primary">現在</q-badge>
</q-td>
</template>
</q-table>
</div> </div>
</template> </template>
<script> <script>
import { ref,onMounted,reactive, computed } from 'vue' import { ref,onMounted,reactive } from 'vue'
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { useAuthStore } from 'src/stores/useAuthStore';
export default { export default {
name: 'DomainSelect', name: 'DomainSelect',
props: { props: {
name: String, name: String,
type: String, type: String
filterInitRowsFunc: {
type: Function,
}, },
}, setup() {
setup(props) {
const authStore = useAuthStore();
const currentDomainId = computed(() => authStore.currentDomain.id);
const loading = ref(true);
const inactiveRowClass = (row) => row.domainActive ? '' : 'inactive-row';
const columns = [ const columns = [
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass }, { name: 'id'},
{ name: 'name', align: 'left', label: 'ドメイン', field: 'name', sortable: true, classes: inactiveRowClass }, { name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true},
{ name: 'url', label: 'URL', field: 'url',align: 'left', sortable: true, classes: inactiveRowClass }, { name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true },
{ name: 'user', label: 'アカウント', field: 'user',align: 'left', classes: inactiveRowClass }, { name: 'url', label: 'URL', field: 'url', sortable: true },
{ name: 'owner', label: '所有者', field: row => row.owner.fullName, align: 'left', classes: inactiveRowClass }, { name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' }
] ]
const rows = reactive([]); const rows = reactive([])
onMounted( () => {
onMounted(() => { api.get(`api/domains/1`).then(res =>{
loading.value = true; res.data.forEach((item) =>
api.get(`api/domains`).then(res =>{ {
res.data.data.forEach((data) => { rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});
const item = {
id: data.id,
tenantid: data.tenantid,
domainActive: data.is_active,
name: data.name,
url: data.url,
user: data.kintoneuser,
owner: {
id: data.owner.id,
firstName: data.owner.first_name,
lastName: data.owner.last_name,
fullNameSearch: (data.owner.last_name + data.owner.first_name).toLowerCase(),
fullName: data.owner.last_name + ' ' + data.owner.first_name,
email: data.owner.email,
isActive: data.owner.is_active,
isSuperuser: data.owner.is_superuser,
} }
} )
if (props.filterInitRowsFunc && !props.filterInitRowsFunc(item)) {
return;
}
rows.push(item);
})
loading.value = false;
}); });
}); });
return { return {
loading,
currentDomainId,
columns, columns,
rows, rows,
selected: ref([]), selected: ref([]),
@@ -81,11 +40,3 @@ export default {
} }
</script> </script>
<style lang="scss">
.q-table td.inactive-row {
color: #aaa;
}
.q-table .content-box {
box-sizing: content-box;
}
</style>

View File

@@ -1,21 +1,18 @@
<template> <template>
<q-btn-dropdown <q-btn-dropdown
class="customized-disabled-btn" color="primay"
push push
flat flat
no-caps no-caps
icon="share" icon="share"
size="md" size="md"
:label="userStore.currentDomain.domainName" :label="userStore.currentDomain.domainName"
:disable-dropdown="true"
dropdown-icon='none'
:disable="true"
> >
<q-list> <q-list>
<q-item :active="isCurrentDomain(domain)" active-class="active-domain-item" v-for="domain in domains" :key="domain.domainName" <q-item v-for="domain in domains" :key="domain.domainName"
clickable v-close-popup @click="onItemClick(domain)"> clickable v-close-popup @click="onItemClick(domain)">
<q-item-section side> <q-item-section side>
<q-icon name="share" size="sm" :color="isCurrentDomain(domain) ? 'orange': ''" text-color="white"></q-icon> <q-icon name="share" size="sm" color="orange" text-color="white"></q-icon>
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>{{domain.domainName}}</q-item-label> <q-item-label>{{domain.domainName}}</q-item-label>
@@ -27,46 +24,20 @@
</template> </template>
<script setup lang="ts" > <script setup lang="ts" >
import { IDomainInfo } from 'src/types/DomainTypes'; import { IDomainInfo } from 'src/types/ActionTypes';
import { useAuthStore } from 'stores/useAuthStore'; import { useAuthStore,IUserState } from 'stores/useAuthStore';
import { useDomainStore } from 'src/stores/useDomainStore'; import { ref } from 'vue';
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const userStore = useAuthStore(); const userStore = useAuthStore();
const domainStore = useDomainStore(); const domains = ref<IDomainInfo[]>([]);
const route = useRoute()
const domains = computed(() => domainStore.userDomains);
(async ()=>{ (async ()=>{
await domainStore.loadUserDomains(); domains.value = await userStore.getUserDomains();
})(); })();
const isUnclickable = computed(()=>{
return route.path.startsWith('/FlowChart/') || domains.value === undefined || domains.value.length === 0;
});
const isCurrentDomain=(domain:IDomainInfo)=>{
return domain.id === userStore.currentDomain.id;
}
const onItemClick=(domain:IDomainInfo)=>{ const onItemClick=(domain:IDomainInfo)=>{
console.log(domain); console.log(domain);
userStore.setCurrentDomain(domain); userStore.setCurrentDomain(domain);
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.q-btn.disabled.customized-disabled-btn {
opacity: 1 !important;
cursor: default !important;
}
.q-item.active-domain-item {
color: inherit;
background: #eee;
}
.q-btn.disabled.customized-disabled-btn * {
cursor: default !important;
}
</style> </style>

View File

@@ -4,7 +4,6 @@
tag="a" tag="a"
:target="target?target:'_blank'" :target="target?target:'_blank'"
:href="link" :href="link"
:disable="disable"
v-if="!isSeparator" v-if="!isSeparator"
> >
<q-item-section <q-item-section
@@ -34,7 +33,6 @@ export interface EssentialLinkProps {
icon?: string; icon?: string;
isSeparator?: boolean; isSeparator?: boolean;
target?:string; target?:string;
disable?:boolean;
} }
withDefaults(defineProps<EssentialLinkProps>(), { withDefaults(defineProps<EssentialLinkProps>(), {
caption: '', caption: '',

View File

@@ -1,191 +0,0 @@
<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 title="解除" 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>

View File

@@ -1,45 +0,0 @@
<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>

View File

@@ -4,7 +4,7 @@
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle"> <q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
<q-toolbar class="bg-grey-4"> <q-toolbar class="bg-grey-4">
<q-toolbar-title>{{ name }}</q-toolbar-title> <q-toolbar-title>{{ name }}</q-toolbar-title>
<q-space v-if="$slots.toolbar"></q-space> <q-space></q-space>
<slot name="toolbar"></slot> <slot name="toolbar"></slot>
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" /> <q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
</q-toolbar> </q-toolbar>
@@ -12,8 +12,8 @@
<slot></slot> <slot></slot>
</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="確定" v-close-popup @click="CloseDialogue('OK')" />
<q-btn flat label="キャンセル" :disable="okBtnLoading" v-close-popup @click="CloseDialogue('Cancel')" /> <q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</q-dialog> </q-dialog>
@@ -30,19 +30,13 @@ export default {
height:String, height:String,
minWidth:String, minWidth:String,
minHeight:String, minHeight:String,
okBtnLoading:Boolean,
okBtnAutoClose:{
type: Boolean,
default: true
},
disableBtn:{ disableBtn:{
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
emits: [ emits: [
'close', 'close'
'update:visible'
], ],
setup(props, context) { setup(props, context) {
const CloseDialogue = (val) => { const CloseDialogue = (val) => {

View File

@@ -1,65 +0,0 @@
<template>
<q-btn flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
<q-menu>
<q-list dense :style="'min-width:' + minWidth ">
<template v-for="(item, index) in actions" :key="index" >
<q-item v-if="isAction(item)" :class="item.class" clickable v-close-popup @click="item.action(row)">
<q-item-section side style="color: inherit;">
<q-icon size="1.2em" :name="item.icon" />
</q-item-section>
<q-item-section>{{ item.label }}</q-item-section>
</q-item>
<q-separator v-else />
</template>
</q-list>
</q-menu>
</q-btn>
</template>
<script lang="ts">
import { PropType } from 'vue';
import { IDomainOwnerDisplay } from '../types/DomainTypes';
interface Action {
label: string;
icon?: string;
action: (row: any) => void|Promise<void>;
class?: string;
}
interface Separator {
separator: boolean;
}
type MenuItem = Action | Separator;
export default {
name: 'TableActionMenu',
props: {
row: {
type: Object as PropType<IDomainOwnerDisplay>,
required: true
},
minWidth: {
type: String,
default: '100px'
},
actions: {
type: Array as PropType<MenuItem[]>,
required: true
}
},
methods: {
isAction(item: MenuItem): item is Action {
return !('separator' in item);
}
}
};
</script>
<style lang="scss" scoped>
.q-table tr > td:last-child .action-menu {
opacity: 0.25;
}
.q-table tr:hover > td:last-child .action-menu {
opacity: 1;
}
</style>

View File

@@ -1,69 +0,0 @@
<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>

View File

@@ -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: 'lastName', label: '氏名', field: 'lastName', align: 'left', sortable: true }, { name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true }, { name: 'lastName', label: '苗字', field: 'lastName', 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.data.map((item) => { rows.value = result.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;

View File

@@ -1,88 +0,0 @@
<template>
<div class="q-px-xs">
<div v-if="!isLoaded" class="spinner flex flex-center">
<q-spinner color="primary" size="3em" />
</div>
<q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
:filter="filter" style="max-height: 65vh;" @update:selected="emitSelected">
<template v-slot:[`body-cell-${detailField}`]="props">
<q-td :props="props">
<q-scroll-area class="description-cell">
<div v-html="props.row[detailField]"></div>
</q-scroll-area>
</q-td>
</template>
</q-table>
</div>
</template>
<script lang="ts">
import { ref, onMounted, reactive, PropType } from 'vue'
interface IRow {
[key: string]: any;
}
export default {
name: 'DetailFieldTable',
props: {
name: String,
type: String,
filter: String,
detailField: {
type: String,
required: true
},
columns: {
type: Array as PropType<any[]>,
required: true
},
fetchData: {
type: Function as PropType<() => Promise<IRow[]>>,
required: true
}
},
emits: ['update:selected'],
setup(props, { emit }) {
const isLoaded = ref(false);
const rows = reactive<IRow[]>([]);
const selected = ref([]);
onMounted(async () => {
const data = await props.fetchData();
rows.push(...data);
isLoaded.value = true;
});
const emitSelected = (selectedItems: any[]) => {
emit('update:selected', selectedItems);
};
return {
rows,
selected,
isLoaded,
pagination: ref({
rowsPerPage: 10
}),
emitSelected
};
}
};
</script>
<style lang="scss">
.description-cell {
height: 60px;
width: 300px;
max-height: 60px;
max-width: 300px;
white-space: break-spaces;
}
.spinner {
min-height: 300px;
min-width: 400px;
}
</style>

View File

@@ -1,82 +0,0 @@
<template>
<detail-field-table
detailField="comment"
type="single"
:columns="columns"
:fetchData="fetchVersionHistory"
@update:selected="(item) => { selected = item }"
/>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, watch } from 'vue';
import { IAppDisplay, IAppVersion } from 'src/types/AppTypes';
import { date } from 'quasar';
import { api } from 'boot/axios';
import DetailFieldTable from './DetailFieldTable.vue';
import { IUser, IUserDisplay } from 'src/types/UserTypes';
export default defineComponent({
name: 'VersionHistory',
components: {
DetailFieldTable
},
props: {
app: {
type: Object as PropType<IAppDisplay>,
required: true,
},
},
setup(props, { emit }) {
const selected = ref<IAppVersion[]>([]);
const columns = [
{ name: 'version', label: 'ID', field: 'version', align: 'left', sortable: true },
{ name: 'name', label: 'バージョン名', field: 'name', align: 'left', sortable: true },
{ name: 'comment', label: 'コメント', field: 'comment', align: 'left', sortable: true },
{ name: 'creator', label: '作成者', field: (row: IAppVersion) => row.creator.fullName, align: 'left', sortable: true },
{ name: 'createTime', label: '作成日時', field: 'createTime', align: 'left', sortable: true },
{ name: 'updater', label: '更新者', field: (row: IAppVersion) => row.updater.fullName, align: 'left', sortable: true },
{ name: 'updateTime', label: '更新日時', field: 'updateTime', align: 'left', sortable: true },
];
const formatDate = (dateStr: string) => {
return date.formatDate(dateStr, 'YYYY/MM/DD HH:mm:ss');
};
const toUserDisaplay = (user: IUser) => {
return {
id: user.id,
firstName: user.first_name,
lastName: user.last_name,
fullNameSearch: (user.last_name + user.first_name).toLowerCase(),
fullName: user.last_name + ' ' + user.first_name,
email: user.email,
isActive: user.is_active,
isSuperuser: user.is_superuser,
}
}
const fetchVersionHistory = async () => {
const { data } = await api.get(`api/apps/${props.app.id}/versions`);
return data.data.map((item: any) => ({
id: item.id,
version: item.version,
appid: item.appid,
name: item.name,
comment: item.comment,
updater: toUserDisaplay(item.updateuser),
updateTime: formatDate(item.updatetime),
creator: toUserDisaplay(item.createuser),
createTime: formatDate(item.createtime),
} as IAppVersion));
};
return {
fetchVersionHistory,
columns,
selected
};
},
});
</script>

View File

@@ -1,42 +0,0 @@
<template>
<q-input
v-model="versionInfo.name"
filled
autofocus
label="バージョン名"
:rules="[(val) => !val || val.length <= 30 || '30字以内で入力ください']"
/>
<q-input
v-model="versionInfo.desc"
filled
type="textarea"
:rules="[(val) => !val || val.length <= 80 || '80字以内で入力ください']"
label="説明"
/>
</template>
<script setup lang="ts">
import { ref, watch, defineProps, defineEmits } from 'vue';
import { QInput } from 'quasar';
import { IVersionInfo } from 'src/types/AppTypes';
const props = defineProps<{
modelValue: IVersionInfo;
}>();
const defaultTitle = `${new Date().toLocaleString()}`;
const versionInfo = ref({
...props.modelValue,
name: props.modelValue.name || defaultTitle,
});
const emit = defineEmits(['update:modelValue']);
watch(
versionInfo,
() => {
emit('update:modelValue', { ...versionInfo.value });
},
{ immediate: true, deep: true }
);
</script>

View File

@@ -7,14 +7,18 @@
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" <q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
class="q-mr-sm"> class="q-mr-sm">
</q-icon> </q-icon>
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div> <div class="no-wrap"
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''">{{prop.node.label }}
</div>
<q-space></q-space> <q-space></q-space>
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> --> <!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
</div> </div>
</template> </template>
<template v-slot:header-CHANGE="prop"> <template v-slot:header-CHANGE="prop">
<div class="row col items-start no-wrap event-node"> <div class="row col items-start no-wrap event-node">
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div> <div class="no-wrap"
:class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''"
>{{ prop.node.label }}</div>
<q-space></q-space> <q-space></q-space>
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm" <q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
@click="addChangeEvent(prop.node)"></q-icon> @click="addChangeEvent(prop.node)"></q-icon>
@@ -23,7 +27,7 @@
<template v-slot:header-DELETABLE="prop"> <template v-slot:header-DELETABLE="prop">
<div class="row col items-start event-node" @click="onSelected(prop.node)"> <div class="row col items-start event-node" @click="onSelected(prop.node)">
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" class="q-mr-sm" /> <q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" class="q-mr-sm" />
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div> <div class="no-wrap" :class="selectedEvent && prop.node.eventId === selectedEvent.eventId ? 'selected-node' : ''" >{{ prop.node.label}}</div>
<q-space></q-space> <q-space></q-space>
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon> <q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
</div> </div>
@@ -38,7 +42,7 @@
import { QTree, useQuasar } from 'quasar'; import { QTree, useQuasar } from 'quasar';
import { ActionFlow, RootAction } from 'src/types/ActionTypes'; import { ActionFlow, RootAction } from 'src/types/ActionTypes';
import { useFlowEditorStore } from 'stores/flowEditor'; import { useFlowEditorStore } from 'stores/flowEditor';
import { defineComponent, ref, watchEffect } from 'vue'; import { defineComponent, ref,watchEffect } from 'vue';
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode, kintoneEvent } from '../../types/KintoneEvents'; import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode, kintoneEvent } from '../../types/KintoneEvents';
import FieldSelect from '../FieldSelect.vue'; import FieldSelect from '../FieldSelect.vue';
import ShowDialog from '../ShowDialog.vue'; import ShowDialog from '../ShowDialog.vue';
@@ -76,11 +80,6 @@ export default defineComponent({
const isFieldChange = (node: IKintoneEventNode) => { const isFieldChange = (node: IKintoneEventNode) => {
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1; return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1;
} }
const getSelectedClass = (node: IKintoneEventNode) => {
return store.selectedEvent && node.eventId === store.selectedEvent.eventId ? 'selected-node' : '';
};
//フィールド値変更イベント追加 //フィールド値変更イベント追加
const closeDg = (val: string) => { const closeDg = (val: string) => {
if (val == 'OK') { if (val == 'OK') {
@@ -133,7 +132,7 @@ export default defineComponent({
const screen = store.eventTree.findEventById(node.parentId); const screen = store.eventTree.findEventById(node.parentId);
let flow = store.findFlowByEventId(node.eventId); let flow = store.findFlowByEventId(node.eventId);
let screenName = screen !== null ? screen.label : ''; let screenName = screen !== null ? screen.label : "";
let nodeLabel = node.label; let nodeLabel = node.label;
// if(isFieldChange(node)){ // if(isFieldChange(node)){
// screenName=nodeLabel; // screenName=nodeLabel;
@@ -160,7 +159,6 @@ export default defineComponent({
tree, tree,
showDialog, showDialog,
isFieldChange, isFieldChange,
getSelectedClass,
onSelected, onSelected,
selectedEvent, selectedEvent,
addChangeEvent, addChangeEvent,

View File

@@ -1,33 +0,0 @@
<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>

View File

@@ -22,8 +22,6 @@
<div v-if="isAdmin()"> <div v-if="isAdmin()">
<EssentialLink v-for="link in adminLinks" :key="link.title" v-bind="link" /> <EssentialLink v-for="link in adminLinks" :key="link.title" v-bind="link" />
</div> </div>
<EssentialLink v-for="link in domainLinks" :key="link.title" v-bind="link" />
</q-list> </q-list>
</q-drawer> </q-drawer>
@@ -34,39 +32,27 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, reactive } from 'vue'; import { onMounted } from 'vue';
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue'; import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
import DomainSelector from 'components/DomainSelector.vue'; import DomainSelector from 'components/DomainSelector.vue';
import { useAuthStore } from 'stores/useAuthStore'; import { useAuthStore } from 'stores/useAuthStore';
import { useRoute } from 'vue-router';
const authStore = useAuthStore(); const authStore = useAuthStore();
const route = useRoute()
const noDomain = computed(() => !authStore.hasDomain);
const essentialLinks: EssentialLinkProps[] = reactive([ const essentialLinks: EssentialLinkProps[] = [
{ {
title: 'ホーム', title: 'ホーム',
caption: '設計書から導入する', caption: '設計書から導入する',
icon: 'home', icon: 'home',
link: '/', link: '/',
target: '_self', target: '_self'
disable: noDomain
}, },
// {
// title: 'フローエディター',
// caption: 'イベントを設定する',
// icon: 'account_tree',
// link: '/#/FlowChart',
// target: '_self'
// },
{ {
title: 'アプリ管理', title: 'フローエディター',
caption: 'アプリを管理する', caption: 'イベントを設定する',
icon: 'widgets', icon: 'account_tree',
link: '/#/app', link: '/#/FlowChart',
target: '_self', target: '_self'
disable: noDomain
}, },
// { // {
// title: '条件エディター', // title: '条件エディター',
@@ -97,9 +83,68 @@ const essentialLinks: EssentialLinkProps[] = reactive([
// link:'https://cybozu.dev/ja/kintone/docs/', // link:'https://cybozu.dev/ja/kintone/docs/',
// icon:'help_outline' // icon:'help_outline'
// }, // },
]); // {
// title:'',
// isSeparator:true
// },
// {
// title: 'Docs',
// caption: 'quasar.dev',
// icon: 'school',
// link: 'https://quasar.dev'
// },
// {
// title: 'Icons',
// caption: 'Material Icons',
// icon: 'insert_emoticon',
// link: 'https://fonts.google.com/icons?selected=Material+Icons:insert_emoticon:'
// },
// {
// title: 'Github',
// caption: 'github.com/quasarframework',
// icon: 'code',
// link: 'https://github.com/quasarframework'
// },
// {
// title: 'Discord Chat Channel',
// caption: 'chat.quasar.dev',
// icon: 'chat',
// link: 'https://chat.quasar.dev'
// },
// {
// title: 'Forum',
// caption: 'forum.quasar.dev',
// icon: 'record_voice_over',
// link: 'https://forum.quasar.dev'
// },
// {
// title: 'Twitter',
// caption: '@quasarframework',
// icon: 'rss_feed',
// link: 'https://twitter.quasar.dev'
// },
// {
// title: 'Facebook',
// caption: '@QuasarFramework',
// icon: 'public',
// link: 'https://facebook.quasar.dev'
// },
// {
// title: 'Quasar Awesome',
// caption: 'Community Quasar projects',
// icon: 'favorite',
// link: 'https://awesome.quasar.dev'
// }
];
const domainLinks: EssentialLinkProps[] = reactive([ const adminLinks: EssentialLinkProps[] = [
{
title: 'ユーザー管理',
caption: 'ユーザーを管理する',
icon: 'manage_accounts',
link: '/#/user',
target: '_self'
},
{ {
title: 'ドメイン管理', title: 'ドメイン管理',
caption: 'kintoneのドメイン設定', caption: 'kintoneのドメイン設定',
@@ -114,23 +159,13 @@ const domainLinks: EssentialLinkProps[] = reactive([
link: '/#/userDomain', link: '/#/userDomain',
target: '_self' target: '_self'
}, },
]); ]
const adminLinks: EssentialLinkProps[] = reactive([
{
title: 'ユーザー管理',
caption: 'ユーザーを管理する',
icon: 'manage_accounts',
link: '/#/user',
target: '_self'
},
])
const version = process.env.version; const version = process.env.version;
const productName = process.env.productName; const productName = process.env.productName;
onMounted(() => { onMounted(() => {
authStore.setLeftMenu(!route.path.startsWith('/FlowChart/')); authStore.toggleLeftMenu();
}); });
function toggleLeftDrawer() { function toggleLeftDrawer() {

View File

@@ -1,203 +0,0 @@
<template>
<div class="q-pa-md">
<div class="q-gutter-sm row items-start">
<q-breadcrumbs>
<q-breadcrumbs-el icon="widgets" label="アプリ管理" />
</q-breadcrumbs>
</div>
<q-table title="Treats" :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="showAddAppDialog" />
<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="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">
<q-td :props="prop">
<a :href="prop.row.url" target="_blank" :title="prop.row.name" >
{{ prop.row.url }}
</a>
</q-td>
</template>
<template v-slot:body-cell-actions="p">
<q-td :props="p">
<table-action-menu :row="p.row" :actions="actionList" />
</q-td>
</template>
</q-table>
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeSelectAppDialog" min-width="50vw" min-height="50vh" :ok-btn-auto-close="false" :ok-btn-loading="isAdding">
<template v-slot:toolbar>
<q-input dense debounce="300" v-model="dgFilter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<app-select-box ref="appDialog" name="アプリ" type="single" :filter="dgFilter" :filterInitRowsFunc="filterInitRows" />
</show-dialog>
<show-dialog v-model:visible="showVersionHistory" :name="targetRow?.name + 'のバージョン履歴'" @close="closeHistoryDg" min-width="50vw" min-height="50vh" :ok-btn-auto-close="false" :ok-btn-loading="isAdding">
<version-history ref="versionDialog" :app="targetRow as IAppDisplay" />
</show-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch, reactive } from 'vue';
import { useQuasar } from 'quasar'
import { api } from 'boot/axios';
import { useAuthStore } from 'stores/useAuthStore';
import { useFlowEditorStore } from 'stores/flowEditor';
import { router } from 'src/router';
import { date } from 'quasar'
import { IManagedApp, IAppDisplay, IAppVersion } from 'src/types/AppTypes';
import ShowDialog from 'src/components/ShowDialog.vue';
import AppSelectBox from 'src/components/AppSelectBox.vue';
import TableActionMenu from 'components/TableActionMenu.vue';
import VersionHistory from 'components/dialog/VersionHistory.vue';
const authStore = useAuthStore();
const numberStringSorting = (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10);
const columns = [
{ name: 'id', label: 'アプリID', field: 'id', align: 'left', sortable: true, sort: numberStringSorting },
{ name: 'name', label: 'アプリ名', field: 'name', align: 'left', sortable: true },
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
{ name: 'updateUser', label: '最後更新者', field: (row: IAppDisplay) => row.updateUser.fullName, 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: 'actions', label: '', field: 'actions' }
];
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
const loading = ref(false);
const filter = ref('');
const dgFilter = ref('');
const rows = ref<IAppDisplay[]>([]);
const targetRow = ref<IAppDisplay>();
const rowIds = new Set<string>();
const $q = useQuasar()
const store = useFlowEditorStore();
const appDialog = ref();
const showSelectApp=ref(false);
const showVersionHistory=ref(false);
const isAdding = ref(false);
const actionList = [
{ label: '設定', icon: 'account_tree', action: toEditFlowPage },
{ label: '履歴', icon: 'history', action: showHistory },
{ separator: true },
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
];
const getApps = async () => {
loading.value = true;
rowIds.clear();
try {
const { data } = await api.get('api/apps');
rows.value = data.data.map((item: IManagedApp) => {
rowIds.add(item.appid);
return appToAppDisplay(item)
}).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 () => {
await getApps();
});
watch(() => authStore.currentDomain.id, async () => {
await getApps();
});
const filterInitRows = (row: {id: string}) => {
return !rowIds.has(row.id);
}
const showAddAppDialog = () => {
showSelectApp.value = true;
dgFilter.value = ''
}
const closeSelectAppDialog = async (val: 'OK'|'Cancel') => {
showSelectApp.value = true;
if (val == 'OK' && appDialog.value.selected[0]) {
isAdding.value = true;
toEditFlowPage(appDialog.value.selected[0]);
}
showSelectApp.value = false;
isAdding.value = false;
}
function removeRow(app:IAppDisplay) {
targetRow.value = app;
return
}
function showHistory(app:IAppDisplay) {
targetRow.value = app;
showVersionHistory.value = true;
dgFilter.value = ''
}
const closeHistoryDg = async (val: 'OK'|'Cancel') => {
showSelectApp.value = true;
if (val == 'OK' && appDialog.value.selected[0]) {
isAdding.value = true;
await getApps();
}
showSelectApp.value = false;
isAdding.value = false;
}
const appToAppDisplay = (app: IManagedApp) => {
const user = app.updateuser;
return {
id: app.appid,
sortId: parseInt(app.appid, 10),
name: app.appname,
url: `${app.domainurl}/k/${app.appid}`,
version: app.version,
updateTime:date.formatDate(app.update_time, 'YYYY/MM/DD HH:mm'),
updateUser: {
id: user.id,
firstName: user.first_name,
lastName: user.last_name,
fullNameSearch: (user.last_name + user.first_name).toLowerCase(),
fullName: user.last_name + ' ' + user.first_name,
email: user.email,
isSuperuser: user.is_superuser,
isActive: user.is_active,
}
} as IAppDisplay
}
async function toEditFlowPage(app:IAppDisplay) {
store.setApp({
appId: app.id,
name: app.name
});
store.selectFlow(undefined);
await router.push('/FlowChart/' + app.id);
};
</script>

View File

@@ -3,7 +3,11 @@
<q-layout container class="absolute-full shadow-2 rounded-borders"> <q-layout container class="absolute-full shadow-2 rounded-borders">
<div class="q-pa-sm q-gutter-sm "> <div class="q-pa-sm q-gutter-sm ">
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated> <q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
<div class="flex-center absolute-full" style="padding:15px"> <div class="flex-center fixed-top app-selector">
<AppSelector />
</div>
<div class="flex-center absolute-full" style="padding-top:65px;padding-left:15px;padding-right:15px;">
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }"> <q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
<EventTree /> <EventTree />
</q-scroll-area> </q-scroll-area>
@@ -14,15 +18,6 @@
<q-space></q-space> <q-space></q-space>
<q-btn-dropdown color="primary" label="保存" icon="save" :loading="saveLoading" > <q-btn-dropdown color="primary" label="保存" icon="save" :loading="saveLoading" >
<q-list> <q-list>
<q-item clickable v-close-popup @click="onSaveVersion">
<q-item-section avatar >
<q-icon name="bookmark_border"></q-icon>
</q-item-section>
<q-item-section>
<q-item-label>新バージョン保存</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="onSaveFlow"> <q-item clickable v-close-popup @click="onSaveFlow">
<q-item-section avatar > <q-item-section avatar >
<q-icon name="save" color="primary"></q-icon> <q-icon name="save" color="primary"></q-icon>
@@ -47,24 +42,8 @@
</div> </div>
<q-btn flat dense round <q-btn flat dense round
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'" :icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
:style="{'left': fixedLeftPosition}" :style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
@click="drawerLeft=!drawerLeft" class="expand" /> @click="drawerLeft=!drawerLeft" class="expand" />
<q-breadcrumbs v-if="store.appInfo" class="fixed q-pl-md"
:style="{'left': fixedLeftPosition}">
<q-breadcrumbs-el icon="widgets" label="アプリ管理" to="/app" />
<q-breadcrumbs-el>
<template v-slot>
<a class="full-width" :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
{{ store.appInfo?.name }}
<q-icon
class="q-ma-xs"
name="open_in_new"
color="grey-9"
/>
</a>
</template>
</q-breadcrumbs-el>
</q-breadcrumbs>
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}"> <div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]"> <div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode" <node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
@@ -84,46 +63,31 @@
</template> </template>
<action-select ref="appDg" name="model" :filter="filter" type="single" @clearFilter="onClearFilter" ></action-select> <action-select ref="appDg" name="model" :filter="filter" type="single" @clearFilter="onClearFilter" ></action-select>
</ShowDialog> </ShowDialog>
<!-- save version dialog -->
<ShowDialog v-model:visible="saveVersionAction" name="新バージョン保存" @close="closeSaveVersionDg" min-width="500px">
<version-input v-model="versionInfo" />
</ShowDialog>
<q-inner-loading
:showing="initLoading"
color="primary"
label="読み込み中..."
/>
</q-page> </q-page>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'; import { ref, reactive, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes'; import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
import { IAppDisplay, IManagedApp, IVersionInfo } from 'src/types/AppTypes';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor'; import { useFlowEditorStore } from 'stores/flowEditor';
import { useAuthStore } from 'stores/useAuthStore'; import { useAuthStore } from 'stores/useAuthStore';
import { api } from 'boot/axios';
import NodeItem from 'src/components/main/NodeItem.vue'; import NodeItem from 'src/components/main/NodeItem.vue';
import ShowDialog from 'components/ShowDialog.vue'; import ShowDialog from 'components/ShowDialog.vue';
import ActionSelect from 'components/ActionSelect.vue'; import ActionSelect from 'components/ActionSelect.vue';
import PropertyPanel from 'components/right/PropertyPanel.vue'; import PropertyPanel from 'components/right/PropertyPanel.vue';
import AppSelector from 'components/left/AppSelector.vue';
import EventTree from 'components/left/EventTree.vue'; import EventTree from 'components/left/EventTree.vue';
import VersionInput from 'components/dialog/VersionInput.vue';
import { FlowCtrl } from '../control/flowctrl'; import { FlowCtrl } from '../control/flowctrl';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
const deployLoading = ref(false); const deployLoading = ref(false);
const saveLoading = ref(false); const saveLoading = ref(false);
const initLoading = ref(true);
const drawerLeft = ref(false); const drawerLeft = ref(false);
const versionInfo = ref<IVersionInfo>();
const $q = useQuasar(); const $q = useQuasar();
const store = useFlowEditorStore(); const store = useFlowEditorStore();
const authStore = useAuthStore(); const authStore = useAuthStore();
const route = useRoute()
const appDg = ref(); const appDg = ref();
const prevNodeIfo = ref({ const prevNodeIfo = ref({
@@ -132,7 +96,6 @@ const prevNodeIfo = ref({
}); });
// const refFlow = ref<ActionFlow|null>(null); // const refFlow = ref<ActionFlow|null>(null);
const showAddAction = ref(false); const showAddAction = ref(false);
const saveVersionAction = ref(false);
const drawerRight = ref(false); const drawerRight = ref(false);
const filter=ref(""); const filter=ref("");
const model = ref(""); const model = ref("");
@@ -148,9 +111,6 @@ const minPanelWidth=computed(()=>{
return "300px"; return "300px";
} }
}); });
const fixedLeftPosition = computed(()=>{
return drawerLeft.value?"300px":"0px";
});
const addNode = (node: IActionNode, inputPoint: string) => { const addNode = (node: IActionNode, inputPoint: string) => {
if (drawerRight.value) { if (drawerRight.value) {
@@ -193,7 +153,7 @@ const onDeleteAllNextNodes = (node: IActionNode) => {
} }
const closeDg = (val: any) => { const closeDg = (val: any) => {
console.log("Dialog closed->", val); console.log("Dialog closed->", val);
if (val == 'OK' && appDg?.value?.selected?.length > 0) { if (val == 'OK') {
const data = appDg.value.selected[0]; const data = appDg.value.selected[0];
const actionProps = JSON.parse(data.property); const actionProps = JSON.parse(data.property);
const outputPoint = JSON.parse(data.outputPoints); const outputPoint = JSON.parse(data.outputPoints);
@@ -261,20 +221,6 @@ const onSaveActionProps=(props:IActionProperty[])=>{
} }
}; };
const onSaveVersion = async () => {
versionInfo.value = {
id: '1' // TODO
}
saveVersionAction.value = true;
// await onSaveAllFlow();
}
const closeSaveVersionDg = (val: 'OK'|'CANCEL') => {
if (val == 'OK') {
console.log(versionInfo.value);
}
}
const onSaveFlow = async () => { const onSaveFlow = async () => {
const targetFlow = store.selectedFlow; const targetFlow = store.selectedFlow;
if (targetFlow === undefined) { if (targetFlow === undefined) {
@@ -344,62 +290,38 @@ const onSaveAllFlow= async ()=>{
} }
const fetchData = async () => { const fetchData = async () => {
initLoading.value = true;
if (store.appInfo === undefined && route?.params?.id !== undefined) {
// only for page refreshed
const app = await fetchAppById(route.params.id as string);
store.setApp(app);
};
await store.loadFlow();
initLoading.value = false
drawerLeft.value = true; drawerLeft.value = true;
} if (store.appInfo === undefined) return;
const flowCtrl = new FlowCtrl();
const fetchAppById = async(id: string) => { const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
let result = await api.get('api/apps'); if (actionFlows && actionFlows.length > 0) {
const app = result.data?.data?.find((item: IManagedApp) => item.appid === id ) as IManagedApp; store.setFlows(actionFlows);
if (app) {
return convertManagedAppToAppInfo(app);
} }
if (actionFlows && actionFlows.length == 1) {
result = await api.get(`api/v1/app?app=${id}`); store.selectFlow(actionFlows[0]);
const kApp = result?.data as IAppDisplay | KErrorMsg;
if (isErrorMsg(kApp)) {
$q.notify({
type: 'negative',
caption: 'エラー',
message: kApp.message,
});
} }
return kApp; const root = actionFlows[0].getRoot();
} if (root) {
store.setActiveNode(root);
type KErrorMsg = {
message: string;
}
const isErrorMsg = (e: IAppDisplay | KErrorMsg): e is KErrorMsg => {
return 'message' in e;
};
const convertManagedAppToAppInfo = (app: IManagedApp): AppInfo => {
return {
appId: app.appid,
name: app.appname
} }
}; }
const onClearFilter=()=>{ const onClearFilter=()=>{
filter.value=''; filter.value='';
} }
onMounted(() => { onMounted(() => {
authStore.setLeftMenu(false); authStore.toggleLeftMenu();
fetchData(); fetchData();
}); });
</script> </script>
<style lang="scss"> <style lang="scss">
.app-selector {
padding: 15px;
z-index: 999;
}
.flowchart { .flowchart {
padding-top: 10px; padding-top: 10px;
} }

View File

@@ -5,7 +5,7 @@
<q-breadcrumbs-el icon="domain" label="ドメイン管理" /> <q-breadcrumbs-el icon="domain" label="ドメイン管理" />
</q-breadcrumbs> </q-breadcrumbs>
</div> </div>
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination"> <q-table title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
<template v-slot:top> <template v-slot:top>
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" /> <q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
@@ -17,23 +17,12 @@
</q-input> </q-input>
</template> </template>
<template v-slot:header-cell-active="p">
<q-th auto-width :props="p">
<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.id == currentDomainId" color="primary">既定</q-badge>
</q-td>
</template>
<template v-slot:body-cell-actions="p"> <template v-slot:body-cell-actions="p">
<q-td :props="p"> <q-td :props="p">
<table-action-menu :row="p.row" :actions="actionList" /> <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> </q-td>
</template> </template>
@@ -49,11 +38,15 @@
<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="url" label="Kintone url" hint="KintoneのURLを入力してください" lazy-rules
:rules="[val => val && val.length > 0 || 'KintoneのURLを入力してください']" /> :rules="[val => val && val.length > 0, isDomain || '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" />
@@ -67,15 +60,6 @@
</template> </template>
</q-input> </q-input>
<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="domainActive" />
</q-item-section>
</q-item>
<div class="q-gutter-y-md" v-if="!isCreate"> <div class="q-gutter-y-md" v-if="!isCreate">
<q-separator /> <q-separator />
@@ -102,7 +86,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 :loading="addEditLoading" label="保存" type="submit" color="primary" /> <q-btn 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>
@@ -112,141 +96,69 @@
<q-dialog v-model="confirm" persistent> <q-dialog v-model="confirm" persistent>
<q-card> <q-card>
<q-card-section v-if="deleteLoadingState == -1" class="row items-center"> <q-card-section 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 v-if="deleteLoadingState > 0" label="処理に行く" color="primary" v-close-popup @click="openShareDg(editId)" /> <q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomain()" />
<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>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, reactive } 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 ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
import TableActionMenu from 'components/TableActionMenu.vue';
import { IDomain, IDomainDisplay, IDomainOwnerDisplay, IDomainSubmit } from '../types/DomainTypes';
const authStore = useAuthStore(); const authStore = useAuthStore();
const domainStore = useDomainStore();
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 },
// { {
// name: 'tenantid', name: 'tenantid',
// required: true, required: true,
// label: 'テナントID', label: 'テナントID',
// field: 'tenantid', field: row => row.tenantid,
// align: 'left', format: val => `${val}`,
// sortable: true, align: 'left',
// classes: inactiveRowClass sortable: true
// }, },
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true, classes: inactiveRowClass }, { name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true },
{ name: 'active', label: 'x', align: 'left', field: 'domainActive', classes: inactiveRowClass }, { name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true, classes: inactiveRowClass }, { name: 'user', label: 'ログイン名', field: 'user', align: 'left', },
{ name: 'user', label: 'ログイン名', field: 'user', align: 'left', classes: inactiveRowClass }, { name: 'actions', label: '操作', field: 'actions' }
{ name: 'owner', label: '所有者', field: (row: IDomainOwnerDisplay) => 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<IDomainOwnerDisplay[]>([]); const rows = ref([]);
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 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);
const kintoneuser = ref(''); const kintoneuser = ref('');
const kintonepwd = ref(''); const kintonepwd = ref('');
const kintonepwdBK = ref(''); const kintonepwdBK = ref('');
const domainActive = ref(true);
const isCreate = ref(true); const isCreate = ref(true);
let editId = ref(0); let editId = ref(0);
const shareDg = ref(false);
const shareDomain = ref<IDomainOwnerDisplay>({} as IDomainOwnerDisplay);
const activeOptions = [ const getDomain = async () => {
{ value: 0, label: '全状態' },
{ value: 1, label: '使用' },
{ value: 2, label: '未使用'}
]
const activeFilter = ref(activeOptions[0]);
const activeFilterUpdate = (option: {value: number}) => {
switch (option.value) {
case 1:
getDomain((row) => row.domainActive)
break;
case 2:
getDomain((row) => !row.domainActive)
break;
default:
getDomain()
break;
}
}
const actionList = [
{ label: '編集', icon: 'edit_note', action: editRow },
{ label: '利用権限設定', icon: 'person_add_alt', action: openShareDg },
{ separator: true },
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
];
const getDomain = async (filter?: (row: IDomainOwnerDisplay) => boolean) => {
loading.value = true; loading.value = true;
const { data } = await api.get<{data:IDomain[]}>(`api/domains`); const result = await api.get(`api/domains/1`);
rows.value = data.data.map((item) => { rows.value = result.data.map((item) => {
return { return { id: item.id, tenantid: item.tenantid, name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd }
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,
}
}
}).filter(filter || (() => true));
loading.value = false; loading.value = false;
} }
@@ -261,36 +173,27 @@ const addRow = () => {
show.value = true; show.value = true;
} }
async function removeRow(row: IDomainOwnerDisplay) { const removeRow = (row) => {
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(({ data }) => { api.delete(`api/domain/${editId.value}`).then(() => {
if (!data.data) {
// TODO dialog
}
getDomain(); getDomain();
// authStore.setCurrentDomain();
}) })
editId.value = 0; // set in removeRow() editId.value = 0;
deleteLoadingState.value = -1;
}; };
function 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;
kintonepwd.value = row.password; kintonepwd.value = row.password;
domainActive.value = row.domainActive;
isPwd.value = true; isPwd.value = true;
show.value = true; show.value = true;
}; };
@@ -310,42 +213,35 @@ const closeDg = () => {
} }
const onSubmit = () => { const onSubmit = () => {
addEditLoading.value = true; if (editId.value !== 0) {
const method = editId.value !== 0 ? 'put' : 'post'; api.put(`api/domain`, {
const param: IDomainSubmit = {
'id': editId.value, 'id': editId.value,
'tenantid': '1', // TODO: テナントIDを取得する 'tenantid': tenantid.value,
'name': name.value, 'name': name.value,
'url': url.value, 'url': url.value,
'kintoneuser': kintoneuser.value, 'kintoneuser': kintoneuser.value,
'kintonepwd': ((isCreate.value && editId.value == 0) || resetPsw.value) ? kintonepwd.value : '', 'kintonepwd': isCreate.value || resetPsw.value ? kintonepwd.value : ''
'is_active': domainActive.value, }).then(() => {
'ownerid': authStore.userId || '' getDomain();
} closeDg();
// for search: api.put(`api/domain`)、api.post(`api/domain`) onReset();
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) { else {
await authStore.setCurrentDomain(); api.post(`api/domain`, {
} 'id': 0,
'tenantid': tenantid.value,
'name': name.value,
'url': url.value,
'kintoneuser': kintoneuser.value,
'kintonepwd': kintonepwd.value
}).then(() => {
getDomain(); getDomain();
domainStore.loadUserDomains();
closeDg(); closeDg();
onReset(); onReset();
addEditLoading.value = false;
}) })
}
function openShareDg(row: IDomainOwnerDisplay|number) {
if (typeof row === 'number') {
row = rows.value.find(item => item.id === row) as IDomainOwnerDisplay;
} }
shareDomain.value = row ;
shareDg.value = true;
};
function closeShareDg() {
shareDg.value = false;
} }
const onReset = () => { const onReset = () => {
@@ -356,24 +252,6 @@ const onReset = () => {
isPwd.value = true; isPwd.value = true;
editId.value = 0; editId.value = 0;
isCreate.value = true; isCreate.value = true;
domainActive.value = true;
resetPsw.value = false resetPsw.value = false
addEditLoading.value = false;
} }
</script> </script>
<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 {
color: #aaa;
background-color: #fafafa;
}
.q-table tr > td.inactive-row:last-child {
color: inherit;
}
</style>

View File

@@ -1,19 +1,28 @@
<template> <template>
<div class="q-pa-md"> <div class="q-pa-lg">
<div class="q-gutter-sm row items-start"> <div class="q-gutter-sm row items-start">
<q-breadcrumbs> <q-breadcrumbs>
<q-breadcrumbs-el icon="assignment_ind" label="ドメイン適用" /> <q-breadcrumbs-el icon="assignment_ind" label="ドメイン適用" />
</q-breadcrumbs> </q-breadcrumbs>
</div> </div>
<q-table 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" />
@@ -29,23 +38,67 @@
<template v-slot:item="props"> <template v-slot:item="props">
<div class="q-pa-sm"> <div class="q-pa-sm">
<domain-card :item="props.row" :active-id="activeDomainId"> <q-card>
<template v-slot:actions> <q-card-section>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-title">Domain</div>
<div class="q-table__grid-item-value">{{ props.row.name }}</div>
</div>
<div class="q-table__grid-item-row">
<div class="q-table__grid-item-title">URL</div>
<div class="q-table__grid-item-value" style="width: 22rem;">{{ props.row.url }}</div>
</div>
<div class="q-table__grid-item-row">
<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"> <q-card-actions align="right">
<q-chip class="no-border" v-if="isActive(props.row.id)" outline color="primary" text-color="white" icon="done"> <div style="width: 98%;">
既定 <div class="row items-center justify-between">
</q-chip> <div class="q-table__grid-item-value"
<q-btn flat v-else :loading="activeDomainLoadingId === props.row.id" :disable="deleteDomainLoadingId === props.row.id" @click="activeDomain(props.row)">既定にする</q-btn> :class="isActive(props.row.id) ? 'text-positive' : 'text-negative'">{{
<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> isActive(props.row.id)?'既定':'' }}</div>
<div class="col-auto">
<q-btn v-if="!isActive(props.row.id)" flat
@click="activeDomain(props.row.id)">既定にする</q-btn>
<q-btn flat @click="clickDeleteConfirm(props.row)">削除</q-btn>
</div>
</div>
</div>
</q-card-actions> </q-card-actions>
</template> </q-card>
</domain-card>
</div> </div>
</template> </template>
</q-table> </q-table>
<show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished" :ok-btn-loading="addUserDomainLoading" :ok-btn-auto-close="false"> <show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished">
<domain-select ref="addDomainRef" name="ドメイン" type="single" :filterInitRowsFunc="filterAddDgInitRows"></domain-select> <domain-select ref="addDomainRef" name="ドメイン" type="multiple"></domain-select>
</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> </show-dialog>
<q-dialog v-model="showDeleteConfirm" persistent> <q-dialog v-model="showDeleteConfirm" persistent>
@@ -70,61 +123,49 @@
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 { 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 pagination = ref({ sortBy: 'id', rowsPerPage: 0 }); const pagination = ref({ sortBy: 'id', rowsPerPage: 0 });
const rows = ref<IDomainOwnerDisplay[]>([]); const rows = ref([] as any[]);
const rowIds = new Set<string>();
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: 'user', sortable: true }, { name: 'kintoneuser', label: 'User', field: 'kintoneuser', 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);
const addDomainRef = ref(); const addDomainRef = ref();
const filterAddDgInitRows = (row: {domainActive: boolean, id: string}) => {
return row.domainActive && !rowIds.has(row.id);
}
const clickAddDomain = () => { const clickAddDomain = () => {
editId.value = 0; editId.value = 0;
showAddDomainDg.value = true; showAddDomainDg.value = true;
}; };
const addUserDomainFinished = async (val: string) => { const addUserDomainFinished = (val: string) => {
showAddDomainDg.value = true; if (val == 'OK') {
const selected = addDomainRef.value.selected; let dodmainids = [];
if (val == 'OK' && selected.length > 0) { let domains = JSON.parse(JSON.stringify(addDomainRef.value.selected));
addUserDomainLoading.value = true; for (var key in domains) {
const { data } = await api.post(`api/domain/${authStore.userId}?domainid=${selected[0].id}`) dodmainids.push(domains[key].id);
if (rows.value.length === 0 && data.data) {
const domain = data.data;
await authStore.setCurrentDomain({
id: domain.id,
kintoneUrl: domain.url,
domainName: domain.name
});
} }
await getDomain(); api.post(`api/domain/${useOtherUser.value ? otherUserId.value : authStore.userId}`, dodmainids)
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); });
} }
addUserDomainLoading.value = false;
showAddDomainDg.value = false;
}; };
const showDeleteConfirm = ref(false); const showDeleteConfirm = ref(false);
@@ -134,26 +175,16 @@ const clickDeleteConfirm = (row: any) => {
editId.value = row.id; editId.value = row.id;
}; };
const deleteDomainFinished = async () => { const deleteDomainFinished = () => {
deleteDomainLoadingId.value = editId.value; api.delete(`api/domain/${editId.value}/${useOtherUser.value ? otherUserId.value : authStore.userId}`).then(() => {
const { data } = await api.delete(`api/domain/${editId.value}/${authStore.userId}`) getDomain(useOtherUser.value ? otherUserId.value : undefined);
if (data.msg == 'OK' && authStore.currentDomain.id === editId.value) { })
authStore.setCurrentDomain();
}
editId.value = 0; editId.value = 0;
await getDomain();
deleteDomainLoadingId.value = undefined;
}; };
const activeDomain = async (domain: any) => { const activeDomain = (id: number) => {
activeDomainLoadingId.value = domain.id; api.put(`api/activedomain/${id}${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`)
await authStore.setCurrentDomain({ .then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); })
id: domain.id,
kintoneUrl: domain.url,
domainName: domain.name
});
await getDomain();
activeDomainLoadingId.value = undefined;
}; };
let activeDomainId = ref(0); let activeDomainId = ref(0);
@@ -162,58 +193,44 @@ const isActive = computed(() => (id: number) => {
return id == activeDomainId.value; return id == activeDomainId.value;
}); });
const isNotOwner = computed(() => (ownerId: string) => {
return ownerId !== authStore.userId; const showSwitchUserDd = ref(false);
}); 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) => {
rowIds.clear(); const resp = await api.get(`api/activedomain${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`);
const resp = await api.get(`api/defaultdomain`); activeDomainId.value = resp?.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.sort((a, b) => a.id - b.id).reduce((acc, item) => { rows.value = domains.map((item) => {
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({
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 () => {
initLoading.value = true; currentUserName.value = authStore.userInfo.email
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>

View File

@@ -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="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 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 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 :loading="addEditLoading" label="保存" type="submit" color="primary" /> <q-btn 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: 'lastName', label: '氏名', field: 'lastName', align: 'left', sortable: true }, { name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true }, { name: 'lastName', label: '苗字', field: 'lastName', 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,7 +168,6 @@ 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([]);
@@ -190,7 +189,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.data.map((item) => { rows.value = result.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;
@@ -261,7 +260,6 @@ 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,
@@ -304,7 +302,6 @@ 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>

View File

@@ -5,7 +5,6 @@ import {
createWebHashHistory, createWebHashHistory,
createWebHistory, createWebHistory,
} from 'vue-router'; } from 'vue-router';
import { Dialog } from 'quasar'
import routes from './routes'; import routes from './routes';
import { useAuthStore } from 'stores/useAuthStore'; import { useAuthStore } from 'stores/useAuthStore';
@@ -48,18 +47,6 @@ export default route(function (/* { store, ssrContext } */) {
authStore.returnUrl = to.fullPath; authStore.returnUrl = to.fullPath;
return '/login'; return '/login';
} }
// redirect to domain setting page if no domain exist
const domainPages = [...publicPages, '/domain', '/userDomain', '/user'];
if (!authStore.hasDomain && !domainPages.includes(to.path)) {
Dialog.create({
title: '注意',
message: '既定/利用可能なドメインはありません。<br>ドメイン管理ページに遷移して処理します。',
html: true,
persistent: true,
})
return '/domain';
}
}); });
return routerInstance; return routerInstance;
}); });

View File

@@ -6,7 +6,7 @@ const routes: RouteRecordRaw[] = [
component: () => import('pages/LoginPage.vue') component: () => import('pages/LoginPage.vue')
}, },
{ {
path:'/FlowChart/:id', path:'/FlowChart',
component:()=>import('layouts/MainLayout.vue'), component:()=>import('layouts/MainLayout.vue'),
children:[ children:[
{path:'',component:()=>import('pages/FlowChart.vue')} {path:'',component:()=>import('pages/FlowChart.vue')}
@@ -27,7 +27,6 @@ const routes: RouteRecordRaw[] = [
{ path: 'domain', component: () => import('pages/TenantDomain.vue') }, { path: 'domain', component: () => import('pages/TenantDomain.vue') },
{ path: 'userdomain', component: () => import('pages/UserDomain.vue')}, { path: 'userdomain', component: () => import('pages/UserDomain.vue')},
{ path: 'user', component: () => import('pages/UserManagement.vue')}, { path: 'user', component: () => import('pages/UserManagement.vue')},
{ path: 'app', component: () => import('pages/AppManagement.vue')},
{ path: 'condition', component: () => import('pages/conditionPage.vue') } { path: 'condition', component: () => import('pages/conditionPage.vue') }
], ],
}, },

View File

@@ -64,9 +64,7 @@ export const useFlowEditorStore = defineStore('flowEditor', {
this.selectedFlow = flow; this.selectedFlow = flow;
if(flow!==undefined){ if(flow!==undefined){
const eventId = flow.getRoot()?.name; const eventId = flow.getRoot()?.name;
this.selectedEvent = this.eventTree.findEventById(eventId) as IKintoneEvent; this.selectedEvent=this.eventTree.findEventById(eventId) as IKintoneEvent;
} else {
this.selectedEvent = undefined;
} }
}, },
setActiveNode(node: IActionNode) { setActiveNode(node: IActionNode) {
@@ -88,8 +86,8 @@ export const useFlowEditorStore = defineStore('flowEditor', {
//eventTreeにバンドする //eventTreeにバンドする
this.eventTree.bindFlows(actionFlows); this.eventTree.bindFlows(actionFlows);
if (actionFlows === undefined || actionFlows.length === 0) { if (actionFlows === undefined || actionFlows.length === 0) {
this.setFlows([]); this.flows = [];
this.selectFlow(undefined); this.selectedFlow = undefined;
this.expandedScreen =[]; this.expandedScreen =[];
return; return;
} }
@@ -97,11 +95,6 @@ export const useFlowEditorStore = defineStore('flowEditor', {
if (actionFlows && actionFlows.length > 0) { if (actionFlows && actionFlows.length > 0) {
this.selectFlow(actionFlows[0]); this.selectFlow(actionFlows[0]);
} }
const root = actionFlows[0].getRoot();
if (root) {
this.setActiveNode(root);
}
const expandEventIds = actionFlows.map((flow) => flow.getRoot()?.name); const expandEventIds = actionFlows.map((flow) => flow.getRoot()?.name);
const expandScreens:string[]=[]; const expandScreens:string[]=[];
expandEventIds.forEach((eventid)=>{ expandEventIds.forEach((eventid)=>{
@@ -128,7 +121,6 @@ export const useFlowEditorStore = defineStore('flowEditor', {
const jsonData = { const jsonData = {
flowid: isNew ? flow.createNewId() : flow.id, flowid: isNew ? flow.createNewId() : flow.id,
appid: this.appInfo?.appId, appid: this.appInfo?.appId,
appname: this.appInfo?.name,
eventid: root?.name, eventid: root?.name,
name: root?.subTitle, name: root?.subTitle,
content: JSON.stringify(flow), content: JSON.stringify(flow),

View File

@@ -1,7 +1,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { api } from 'boot/axios'; import { api } from 'boot/axios';
import { router } from 'src/router'; import { router } from 'src/router';
import { IDomainInfo } from '../types/DomainTypes'; import { IDomainInfo } from '../types/ActionTypes';
import { jwtDecode } from 'jwt-decode'; import { jwtDecode } from 'jwt-decode';
interface UserInfo { interface UserInfo {
firstName: string; firstName: string;
@@ -16,8 +16,7 @@ export interface IUserState {
LeftDrawer: boolean; LeftDrawer: boolean;
userId?: string; userId?: string;
userInfo: UserInfo; userInfo: UserInfo;
roles:string, permissions: 'admin' | 'user';
permissions: string;
} }
export const useAuthStore = defineStore('auth', { export const useAuthStore = defineStore('auth', {
@@ -28,21 +27,14 @@ export const useAuthStore = defineStore('auth', {
currentDomain: {} as IDomainInfo, currentDomain: {} as IDomainInfo,
userId: '', userId: '',
userInfo: {} as UserInfo, userInfo: {} as UserInfo,
roles:'', permissions: 'user',
permissions: '',
}), }),
getters: { getters: {
toggleLeftDrawer(): boolean { toggleLeftDrawer(): boolean {
return this.LeftDrawer; return this.LeftDrawer;
}, },
hasDomain(): boolean {
return this.currentDomain.id !== undefined;
}
}, },
actions: { actions: {
setLeftMenu(value:boolean){
this.LeftDrawer=value;
},
toggleLeftMenu() { toggleLeftMenu() {
this.LeftDrawer = !this.LeftDrawer; this.LeftDrawer = !this.LeftDrawer;
}, },
@@ -56,7 +48,7 @@ export const useAuthStore = defineStore('auth', {
this.token = result.data.access_token; this.token = result.data.access_token;
const tokenJson = jwtDecode(result.data.access_token); const tokenJson = jwtDecode(result.data.access_token);
this.userId = tokenJson.sub; this.userId = tokenJson.sub;
this.permissions = (tokenJson as any).permissions==='ALL' ? 'admin': 'user'; this.permissions = (tokenJson as any).permissions ?? 'user';
api.defaults.headers['Authorization'] = 'Bearer ' + this.token; api.defaults.headers['Authorization'] = 'Bearer ' + this.token;
this.currentDomain = await this.getCurrentDomain(); this.currentDomain = await this.getCurrentDomain();
this.userInfo = await this.getUserInfo(); this.userInfo = await this.getUserInfo();
@@ -68,16 +60,24 @@ export const useAuthStore = defineStore('auth', {
} }
}, },
async getCurrentDomain(): Promise<IDomainInfo> { async getCurrentDomain(): Promise<IDomainInfo> {
const resp = await api.get(`api/defaultdomain`); const activedomain = await api.get(`api/activedomain`);
const activedomain = resp?.data?.data;
return { return {
id: activedomain?.id, id: activedomain.data.id,
domainName: activedomain?.name, domainName: activedomain.data.name,
kintoneUrl: activedomain?.url, kintoneUrl: activedomain.data.url,
}; };
}, },
async getUserDomains(): Promise<IDomainInfo[]> {
const resp = await api.get(`api/domain`);
const domains = resp.data as any[];
return domains.map((data) => ({
id: data.id,
domainName: data.name,
kintoneUrl: data.url,
}));
},
async getUserInfo():Promise<UserInfo>{ async getUserInfo():Promise<UserInfo>{
const resp = (await api.get(`api/v1/users/me`)).data.data; const resp = (await api.get(`api/v1/users/me`)).data;
return { return {
firstName: resp.first_name, firstName: resp.first_name,
lastName: resp.last_name, lastName: resp.last_name,
@@ -89,15 +89,11 @@ export const useAuthStore = defineStore('auth', {
this.currentDomain = {} as IDomainInfo; // 清空当前域 this.currentDomain = {} as IDomainInfo; // 清空当前域
router.push('/login'); router.push('/login');
}, },
async setCurrentDomain(domain?: IDomainInfo) { async setCurrentDomain(domain: IDomainInfo) {
if (!domain) {
this.currentDomain = {} as IDomainInfo;
return;
}
if (domain.id === this.currentDomain.id) { if (domain.id === this.currentDomain.id) {
return; return;
} }
await api.put(`api/defaultdomain/${domain.id}`); await api.put(`api/activedomain/${domain.id}`);
this.currentDomain = domain; this.currentDomain = domain;
}, },
}, },

View File

@@ -1,23 +0,0 @@
import { defineStore } from 'pinia';
import { api } from 'boot/axios';
import { IDomainInfo, IDomain } from '../types/DomainTypes';
export const useDomainStore = defineStore('domain', {
state: () => ({
userDomains: [] as IDomainInfo[],
}),
actions: {
async loadUserDomains(): Promise<IDomainInfo[]> {
const resp = await api.get(`api/domain`);
const domains = resp.data as IDomain[];
this.userDomains = domains
.filter(data => data.is_active)
.map((data) => ({
id: data.id,
domainName: data.name,
kintoneUrl: data.url,
}));
return this.userDomains;
},
},
});

View File

@@ -1,4 +1,9 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
export interface IDomainInfo{
id:number;
domainName:string;
kintoneUrl:string;
}
/** /**
* アプリ情報 * アプリ情報

View File

@@ -1,51 +0,0 @@
import { IUser, IUserDisplay } from './UserTypes';
export interface IManagedApp {
appid: string;
appname: string;
domainurl: string;
version: string;
user: IUser;
updateuser: IUser;
create_time: string;
update_time: string;
}
export interface IManagedApp {
appid: string;
appname: string;
domainurl: string;
version: string;
user: IUser;
updateuser: IUser;
create_time: string;
update_time: string;
}
export interface IAppDisplay{
id:string;
sortId: number;
name:string;
url:string;
updateUser: IUserDisplay;
updateTime:string;
version:string;
}
export interface IVersionInfo {
id: string;
name?: string;
desc?: string;
}
export interface IAppVersion {
id: number;
version: number;
appid: string;
name: string
comment: string;
updater: IUserDisplay;
updateTime: string;
creator: IUserDisplay;
createTime: string;
}

View File

@@ -1,38 +0,0 @@
import { IUser, IUserDisplay } from './UserTypes';
export interface IDomainInfo {
id: number;
domainName: string;
kintoneUrl: string;
}
export interface IDomain {
id: number;
tenantid: string;
name: string;
url: string;
kintoneuser: string,
kintonepwd: string,
create_time: string;
update_time: string;
is_active: boolean;
owner: IUser
}
export interface IDomainSubmit extends Omit<IDomain, 'create_time' | 'update_time' | 'owner'> {
ownerid: string;
}
export interface IDomainDisplay {
id: number;
tenantid: string;
name: string;
url: string;
user: string;
password?: string;
domainActive: boolean;
}
export interface IDomainOwnerDisplay extends IDomainDisplay {
owner: IUserDisplay
}

View File

@@ -1,24 +0,0 @@
export interface IUser {
id: number;
first_name: string;
last_name: string;
email: string;
is_active: boolean,
is_superuser: boolean,
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[]
}