feat(chalice): router level permissions for sessions
feat(chalice): router level permissions for errors
This commit is contained in:
parent
2a760fe876
commit
28b6c58a49
12 changed files with 495 additions and 259 deletions
|
|
@ -6,14 +6,14 @@ from starlette import status
|
|||
from starlette.exceptions import HTTPException
|
||||
|
||||
from chalicelib.core import authorizers, users
|
||||
from schemas import CurrentContext
|
||||
import schemas
|
||||
|
||||
|
||||
class JWTAuth(HTTPBearer):
|
||||
def __init__(self, auto_error: bool = True):
|
||||
super(JWTAuth, self).__init__(auto_error=auto_error)
|
||||
|
||||
async def __call__(self, request: Request) -> Optional[CurrentContext]:
|
||||
async def __call__(self, request: Request) -> Optional[schemas.CurrentContext]:
|
||||
credentials: HTTPAuthorizationCredentials = await super(JWTAuth, self).__call__(request)
|
||||
if credentials:
|
||||
if not credentials.scheme == "Bearer":
|
||||
|
|
@ -49,9 +49,9 @@ class JWTAuth(HTTPBearer):
|
|||
jwt_payload["authorizer_identity"] = "jwt"
|
||||
print(jwt_payload)
|
||||
request.state.authorizer_identity = "jwt"
|
||||
request.state.currentContext = CurrentContext(tenant_id=jwt_payload.get("tenantId", -1),
|
||||
user_id=jwt_payload.get("userId", -1),
|
||||
email=user["email"])
|
||||
request.state.currentContext = schemas.CurrentContext(tenant_id=jwt_payload.get("tenantId", -1),
|
||||
user_id=jwt_payload.get("userId", -1),
|
||||
email=user["email"])
|
||||
return request.state.currentContext
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -709,36 +709,6 @@ def __status_rank(status):
|
|||
}.get(status)
|
||||
|
||||
|
||||
def merge(error_ids):
|
||||
error_ids = list(set(error_ids))
|
||||
errors = get_batch(error_ids)
|
||||
if len(error_ids) <= 1 or len(error_ids) > len(errors):
|
||||
return {"errors": ["invalid list of ids"]}
|
||||
error_ids = [e["errorId"] for e in errors]
|
||||
parent_error_id = error_ids[0]
|
||||
status = "unresolved"
|
||||
for e in errors:
|
||||
if __status_rank(status) < __status_rank(e["status"]):
|
||||
status = e["status"]
|
||||
if __status_rank(status) == MAX_RANK:
|
||||
break
|
||||
params = {
|
||||
"error_ids": tuple(error_ids),
|
||||
"parent_error_id": parent_error_id,
|
||||
"status": status
|
||||
}
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
"""UPDATE public.errors
|
||||
SET parent_error_id = %(parent_error_id)s, status = %(status)s
|
||||
WHERE error_id IN %(error_ids)s OR parent_error_id IN %(error_ids)s;""",
|
||||
params)
|
||||
cur.execute(query=query)
|
||||
# row = cur.fetchone()
|
||||
|
||||
return {"data": "success"}
|
||||
|
||||
|
||||
def format_first_stack_frame(error):
|
||||
error["stack"] = sourcemaps.format_payload(error.pop("payload"), truncate_to_first=True)
|
||||
for s in error["stack"]:
|
||||
|
|
|
|||
|
|
@ -59,87 +59,6 @@ def sessions_search(projectId: int, data: schemas.FlatSessionsSearchPayloadSchem
|
|||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}', tags=["sessions"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}', tags=["sessions"])
|
||||
def get_session2(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
return {"errors": ["session not found"]}
|
||||
data = sessions.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True, user_id=context.user_id,
|
||||
include_fav_viewed=True, group_metadata=True)
|
||||
if data is None:
|
||||
return {"errors": ["session not found"]}
|
||||
if data.get("inDB"):
|
||||
background_tasks.add_task(sessions_viewed.view_session, project_id=projectId, user_id=context.user_id,
|
||||
session_id=sessionId)
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/favorite', tags=["sessions"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/favorite', tags=["sessions"])
|
||||
def add_remove_favorite_session2(projectId: int, sessionId: int,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {
|
||||
"data": sessions_favorite.favorite_session(project_id=projectId, user_id=context.user_id,
|
||||
session_id=sessionId)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/assign', tags=["sessions"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/assign', tags=["sessions"])
|
||||
def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.get_by_session(project_id=projectId, session_id=sessionId,
|
||||
tenant_id=context.tenant_id,
|
||||
user_id=context.user_id)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/errors/{errorId}/sourcemaps', tags=["sessions", "sourcemaps"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/errors/{errorId}/sourcemaps', tags=["sessions", "sourcemaps"])
|
||||
def get_error_trace(projectId: int, sessionId: int, errorId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_trace(project_id=projectId, error_id=errorId)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/assign/{issueId}', tags=["sessions", "issueTracking"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/assign/{issueId}', tags=["sessions", "issueTracking"])
|
||||
def assign_session(projectId: int, sessionId: int, issueId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.get(project_id=projectId, session_id=sessionId, assignment_id=issueId,
|
||||
tenant_id=context.tenant_id, user_id=context.user_id)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"])
|
||||
@app.put('/{projectId}/sessions/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"])
|
||||
@app.post('/{projectId}/sessions2/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"])
|
||||
@app.put('/{projectId}/sessions2/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"])
|
||||
def comment_assignment(projectId: int, sessionId: int, issueId: str, data: schemas.CommentAssignmentSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.comment(tenant_id=context.tenant_id, project_id=projectId,
|
||||
session_id=sessionId, assignment_id=issueId,
|
||||
user_id=context.user_id, message=data.message)
|
||||
if "errors" in data.keys():
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/events/search', tags=["events"])
|
||||
def events_search(projectId: int, q: str,
|
||||
type: Union[schemas.FilterType, schemas.EventType,
|
||||
|
|
@ -664,13 +583,6 @@ def get_all_announcements(context: schemas.CurrentContext = Depends(OR_context))
|
|||
return {"data": announcements.view(user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.post('/{projectId}/errors/merge', tags=["errors"])
|
||||
def errors_merge(projectId: int, data: schemas.ErrorIdsPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.merge(error_ids=data.errors)
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/show_banner', tags=["banner"])
|
||||
def errors_merge(context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": False}
|
||||
|
|
@ -894,45 +806,6 @@ def sessions_live(projectId: int, data: schemas.LiveSessionsSearchPayloadSchema
|
|||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"])
|
||||
def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = assist.get_live_session_by_id(project_id=projectId, session_id=sessionId)
|
||||
if data is None:
|
||||
data = sessions.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True,
|
||||
user_id=context.user_id, include_fav_viewed=True, group_metadata=True, live=False)
|
||||
if data is None:
|
||||
return {"errors": ["session not found"]}
|
||||
if data.get("inDB"):
|
||||
background_tasks.add_task(sessions_viewed.view_session, project_id=projectId,
|
||||
user_id=context.user_id, session_id=sessionId)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/unprocessed/{sessionId}', tags=["assist"])
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}/replay', tags=["assist"])
|
||||
def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str) or not sessions.session_exists(project_id=projectId, session_id=sessionId):
|
||||
if isinstance(sessionId, str):
|
||||
print(f"{sessionId} not a valid number.")
|
||||
else:
|
||||
print(f"{projectId}/{sessionId} not found in DB.")
|
||||
|
||||
return {"errors": ["Replay file not found"]}
|
||||
path = assist.get_raw_mob_by_id(project_id=projectId, session_id=sessionId)
|
||||
if path is None:
|
||||
return {"errors": ["Replay file not found"]}
|
||||
|
||||
return FileResponse(path=path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@app.post('/{projectId}/heatmaps/url', tags=["heatmaps"])
|
||||
def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": heatmaps.get_by_url(project_id=projectId, data=data.dict())}
|
||||
|
||||
|
||||
@app.post('/{projectId}/mobile/{sessionId}/urls', tags=['mobile'])
|
||||
def mobile_signe(projectId: int, sessionId: int, data: schemas.MobileSignPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
|
|
@ -978,67 +851,6 @@ def edit_client(data: schemas.UpdateTenantSchema = Body(...),
|
|||
return tenants.update(tenant_id=context.tenant_id, user_id=context.user_id, data=data)
|
||||
|
||||
|
||||
@app.post('/{projectId}/errors/search', tags=['errors'])
|
||||
def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": errors.search(data, projectId, user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/stats', tags=['errors'])
|
||||
def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return errors.stats(projectId, user_id=context.user_id, startTimestamp=startTimestamp, endTimestamp=endTimestamp)
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}', tags=['errors'])
|
||||
def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24,
|
||||
density30: int = 30,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_details(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
**{"density24": density24, "density30": density30})
|
||||
if data.get("data") is not None:
|
||||
background_tasks.add_task(errors_viewed.viewed_error, project_id=projectId, user_id=context.user_id,
|
||||
error_id=errorId)
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/stats', tags=['errors'])
|
||||
def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7),
|
||||
endDate: int = TimeUTC.now(), density: int = 7,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_details_chart(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
**{"startDate": startDate, "endDate": endDate, "density": density})
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/sourcemaps', tags=['errors'])
|
||||
def errors_get_details_sourcemaps(projectId: int, errorId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_trace(project_id=projectId, error_id=errorId)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/{action}', tags=["errors"])
|
||||
def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDate: int = TimeUTC.now(-7),
|
||||
endDate: int = TimeUTC.now(), context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if action == "favorite":
|
||||
return errors_favorite.favorite_error(project_id=projectId, user_id=context.user_id, error_id=errorId)
|
||||
elif action == "sessions":
|
||||
start_date = startDate
|
||||
end_date = endDate
|
||||
return {
|
||||
"data": errors.get_sessions(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
start_date=start_date, end_date=end_date)}
|
||||
elif action in list(errors.ACTION_STATE.keys()):
|
||||
return errors.change_state(project_id=projectId, user_id=context.user_id, error_id=errorId, action=action)
|
||||
else:
|
||||
return {"errors": ["undefined action"]}
|
||||
|
||||
|
||||
@app.get('/notifications', tags=['notifications'])
|
||||
def get_notifications(context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": notifications.get_all(tenant_id=context.tenant_id, user_id=context.user_id)}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from chalicelib.core import tenants, users, projects, license
|
|||
from chalicelib.core import webhook
|
||||
from chalicelib.core.collaboration_slack import Slack
|
||||
from chalicelib.utils import helper
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from or_dependencies import OR_context
|
||||
from routers.base import get_routers
|
||||
|
||||
|
|
@ -165,3 +166,184 @@ def get_general_stats():
|
|||
def get_projects(context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": projects.get_projects(tenant_id=context.tenant_id, recording_state=True, gdpr=True, recorded=True,
|
||||
stack_integrations=True)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}', tags=["sessions"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}', tags=["sessions"])
|
||||
def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
return {"errors": ["session not found"]}
|
||||
data = sessions.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True, user_id=context.user_id,
|
||||
include_fav_viewed=True, group_metadata=True)
|
||||
if data is None:
|
||||
return {"errors": ["session not found"]}
|
||||
if data.get("inDB"):
|
||||
background_tasks.add_task(sessions_viewed.view_session, project_id=projectId, user_id=context.user_id,
|
||||
session_id=sessionId)
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/errors/{errorId}/sourcemaps', tags=["sessions", "sourcemaps"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/errors/{errorId}/sourcemaps', tags=["sessions", "sourcemaps"])
|
||||
def get_error_trace(projectId: int, sessionId: int, errorId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_trace(project_id=projectId, error_id=errorId)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/errors/search', tags=['errors'])
|
||||
def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": errors.search(data, projectId, user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/stats', tags=['errors'])
|
||||
def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return errors.stats(projectId, user_id=context.user_id, startTimestamp=startTimestamp, endTimestamp=endTimestamp)
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}', tags=['errors'])
|
||||
def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24,
|
||||
density30: int = 30,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_details(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
**{"density24": density24, "density30": density30})
|
||||
if data.get("data") is not None:
|
||||
background_tasks.add_task(errors_viewed.viewed_error, project_id=projectId, user_id=context.user_id,
|
||||
error_id=errorId)
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/stats', tags=['errors'])
|
||||
def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7),
|
||||
endDate: int = TimeUTC.now(), density: int = 7,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_details_chart(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
**{"startDate": startDate, "endDate": endDate, "density": density})
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/sourcemaps', tags=['errors'])
|
||||
def errors_get_details_sourcemaps(projectId: int, errorId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_trace(project_id=projectId, error_id=errorId)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/{action}', tags=["errors"])
|
||||
def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDate: int = TimeUTC.now(-7),
|
||||
endDate: int = TimeUTC.now(), context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if action == "favorite":
|
||||
return errors_favorite.favorite_error(project_id=projectId, user_id=context.user_id, error_id=errorId)
|
||||
elif action == "sessions":
|
||||
start_date = startDate
|
||||
end_date = endDate
|
||||
return {
|
||||
"data": errors.get_sessions(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
start_date=start_date, end_date=end_date)}
|
||||
elif action in list(errors.ACTION_STATE.keys()):
|
||||
return errors.change_state(project_id=projectId, user_id=context.user_id, error_id=errorId, action=action)
|
||||
else:
|
||||
return {"errors": ["undefined action"]}
|
||||
|
||||
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"])
|
||||
def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = assist.get_live_session_by_id(project_id=projectId, session_id=sessionId)
|
||||
if data is None:
|
||||
data = sessions.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True,
|
||||
user_id=context.user_id, include_fav_viewed=True, group_metadata=True, live=False)
|
||||
if data is None:
|
||||
return {"errors": ["session not found"]}
|
||||
if data.get("inDB"):
|
||||
background_tasks.add_task(sessions_viewed.view_session, project_id=projectId,
|
||||
user_id=context.user_id, session_id=sessionId)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/unprocessed/{sessionId}', tags=["assist"])
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}/replay', tags=["assist"])
|
||||
def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str) or not sessions.session_exists(project_id=projectId, session_id=sessionId):
|
||||
if isinstance(sessionId, str):
|
||||
print(f"{sessionId} not a valid number.")
|
||||
else:
|
||||
print(f"{projectId}/{sessionId} not found in DB.")
|
||||
|
||||
return {"errors": ["Replay file not found"]}
|
||||
path = assist.get_raw_mob_by_id(project_id=projectId, session_id=sessionId)
|
||||
if path is None:
|
||||
return {"errors": ["Replay file not found"]}
|
||||
|
||||
return FileResponse(path=path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@app.post('/{projectId}/heatmaps/url', tags=["heatmaps"])
|
||||
def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": heatmaps.get_by_url(project_id=projectId, data=data.dict())}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/favorite', tags=["sessions"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/favorite', tags=["sessions"])
|
||||
def add_remove_favorite_session2(projectId: int, sessionId: int,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {
|
||||
"data": sessions_favorite.favorite_session(project_id=projectId, user_id=context.user_id,
|
||||
session_id=sessionId)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/assign', tags=["sessions"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/assign', tags=["sessions"])
|
||||
def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.get_by_session(project_id=projectId, session_id=sessionId,
|
||||
tenant_id=context.tenant_id,
|
||||
user_id=context.user_id)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/assign/{issueId}', tags=["sessions", "issueTracking"])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/assign/{issueId}', tags=["sessions", "issueTracking"])
|
||||
def assign_session(projectId: int, sessionId: int, issueId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.get(project_id=projectId, session_id=sessionId, assignment_id=issueId,
|
||||
tenant_id=context.tenant_id, user_id=context.user_id)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"])
|
||||
@app.put('/{projectId}/sessions/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"])
|
||||
@app.post('/{projectId}/sessions2/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"])
|
||||
@app.put('/{projectId}/sessions2/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"])
|
||||
def comment_assignment(projectId: int, sessionId: int, issueId: str, data: schemas.CommentAssignmentSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.comment(tenant_id=context.tenant_id, project_id=projectId,
|
||||
session_id=sessionId, assignment_id=issueId,
|
||||
user_id=context.user_id, message=data.message)
|
||||
if "errors" in data.keys():
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
|
|
|||
1
ee/api/.gitignore
vendored
1
ee/api/.gitignore
vendored
|
|
@ -243,7 +243,6 @@ Pipfile
|
|||
/routers/__init__.py
|
||||
/chalicelib/core/assist.py
|
||||
/auth/auth_apikey.py
|
||||
/auth/auth_jwt.py
|
||||
/build.sh
|
||||
/routers/base.py
|
||||
/routers/core.py
|
||||
|
|
|
|||
60
ee/api/auth/auth_jwt.py
Normal file
60
ee/api/auth/auth_jwt.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
from typing import Optional
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from starlette import status
|
||||
from starlette.exceptions import HTTPException
|
||||
|
||||
from chalicelib.core import authorizers, users
|
||||
import schemas_ee
|
||||
|
||||
|
||||
class JWTAuth(HTTPBearer):
|
||||
def __init__(self, auto_error: bool = True):
|
||||
super(JWTAuth, self).__init__(auto_error=auto_error)
|
||||
|
||||
async def __call__(self, request: Request) -> Optional[schemas_ee.CurrentContext]:
|
||||
credentials: HTTPAuthorizationCredentials = await super(JWTAuth, self).__call__(request)
|
||||
if credentials:
|
||||
if not credentials.scheme == "Bearer":
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid authentication scheme.")
|
||||
jwt_payload = authorizers.jwt_authorizer(credentials.scheme + " " + credentials.credentials)
|
||||
auth_exists = jwt_payload is not None \
|
||||
and users.auth_exists(user_id=jwt_payload.get("userId", -1),
|
||||
tenant_id=jwt_payload.get("tenantId", -1),
|
||||
jwt_iat=jwt_payload.get("iat", 100),
|
||||
jwt_aud=jwt_payload.get("aud", ""))
|
||||
if jwt_payload is None \
|
||||
or jwt_payload.get("iat") is None or jwt_payload.get("aud") is None \
|
||||
or not auth_exists:
|
||||
print("JWTAuth: Token issue")
|
||||
if jwt_payload is not None:
|
||||
print(jwt_payload)
|
||||
print(f"JWTAuth: user_id={jwt_payload.get('userId')} tenant_id={jwt_payload.get('tenantId')}")
|
||||
if jwt_payload is None:
|
||||
print("JWTAuth: jwt_payload is None")
|
||||
print(credentials.scheme + " " + credentials.credentials)
|
||||
if jwt_payload is not None and jwt_payload.get("iat") is None:
|
||||
print("JWTAuth: iat is None")
|
||||
if jwt_payload is not None and jwt_payload.get("aud") is None:
|
||||
print("JWTAuth: aud is None")
|
||||
if jwt_payload is not None and not auth_exists:
|
||||
print("JWTAuth: not users.auth_exists")
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
|
||||
user = users.get(user_id=jwt_payload.get("userId", -1), tenant_id=jwt_payload.get("tenantId", -1))
|
||||
if user is None:
|
||||
print("JWTAuth: User not found.")
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not found.")
|
||||
jwt_payload["authorizer_identity"] = "jwt"
|
||||
print(jwt_payload)
|
||||
request.state.authorizer_identity = "jwt"
|
||||
request.state.currentContext = schemas_ee.CurrentContext(tenant_id=jwt_payload.get("tenantId", -1),
|
||||
user_id=jwt_payload.get("userId", -1),
|
||||
email=user["email"],
|
||||
permissions=user["permissions"])
|
||||
return request.state.currentContext
|
||||
|
||||
else:
|
||||
print("JWTAuth: Invalid authorization code.")
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid authorization code.")
|
||||
15
ee/api/auth/router_security.py
Normal file
15
ee/api/auth/router_security.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
from fastapi import HTTPException, Depends
|
||||
from fastapi.security import SecurityScopes
|
||||
|
||||
import schemas_ee
|
||||
from or_dependencies import OR_context
|
||||
|
||||
|
||||
def check(security_scopes: SecurityScopes, context: schemas_ee.CurrentContext = Depends(OR_context)):
|
||||
for scope in security_scopes.scopes:
|
||||
if scope not in context.permissions:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not enough permissions",
|
||||
)
|
||||
|
||||
|
|
@ -716,36 +716,6 @@ def __status_rank(status):
|
|||
}.get(status)
|
||||
|
||||
|
||||
def merge(error_ids):
|
||||
error_ids = list(set(error_ids))
|
||||
errors = get_batch(error_ids)
|
||||
if len(error_ids) <= 1 or len(error_ids) > len(errors):
|
||||
return {"errors": ["invalid list of ids"]}
|
||||
error_ids = [e["errorId"] for e in errors]
|
||||
parent_error_id = error_ids[0]
|
||||
status = "unresolved"
|
||||
for e in errors:
|
||||
if __status_rank(status) < __status_rank(e["status"]):
|
||||
status = e["status"]
|
||||
if __status_rank(status) == MAX_RANK:
|
||||
break
|
||||
params = {
|
||||
"error_ids": tuple(error_ids),
|
||||
"parent_error_id": parent_error_id,
|
||||
"status": status
|
||||
}
|
||||
with pg_client.PostgresClient() as cur:
|
||||
query = cur.mogrify(
|
||||
"""UPDATE public.errors
|
||||
SET parent_error_id = %(parent_error_id)s, status = %(status)s
|
||||
WHERE error_id IN %(error_ids)s OR parent_error_id IN %(error_ids)s;""",
|
||||
params)
|
||||
cur.execute(query=query)
|
||||
# row = cur.fetchone()
|
||||
|
||||
return {"data": "success"}
|
||||
|
||||
|
||||
def format_first_stack_frame(error):
|
||||
error["stack"] = sourcemaps.format_payload(error.pop("payload"), truncate_to_first=True)
|
||||
for s in error["stack"]:
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ rm -rf ./routers/subs/__init__.py
|
|||
rm -rf ./routers/__init__.py
|
||||
rm -rf ./chalicelib/core/assist.py
|
||||
rm -rf ./auth/auth_apikey.py
|
||||
rm -rf ./auth/auth_jwt.py
|
||||
rm -rf ./build.sh
|
||||
rm -rf ./routers/core.py
|
||||
rm -rf ./routers/crons/core_crons.py
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ from starlette.requests import Request
|
|||
from starlette.responses import Response, JSONResponse
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
from chalicelib.core import traces
|
||||
|
||||
|
||||
async def OR_context(request: Request) -> schemas.CurrentContext:
|
||||
async def OR_context(request: Request) -> schemas_ee.CurrentContext:
|
||||
if hasattr(request.state, "currentContext"):
|
||||
return request.state.currentContext
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,17 +1,23 @@
|
|||
from typing import Optional
|
||||
from typing import Optional, Union
|
||||
|
||||
from decouple import config
|
||||
from fastapi import Body, Depends, BackgroundTasks
|
||||
from fastapi import Body, Depends, BackgroundTasks, Security, HTTPException
|
||||
from fastapi.security import SecurityScopes
|
||||
from starlette import status
|
||||
from starlette.responses import RedirectResponse
|
||||
|
||||
import schemas
|
||||
import schemas_ee
|
||||
from schemas_ee import Permissions
|
||||
from auth import router_security
|
||||
from chalicelib.core import sessions
|
||||
from chalicelib.core import tenants, users, projects, license
|
||||
from chalicelib.core import webhook
|
||||
from chalicelib.core import sessions_viewed
|
||||
from chalicelib.core.collaboration_slack import Slack
|
||||
from chalicelib.utils import SAML2_helper
|
||||
from chalicelib.utils import helper
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from or_dependencies import OR_context
|
||||
from routers.base import get_routers
|
||||
|
||||
|
|
@ -171,3 +177,211 @@ def get_general_stats():
|
|||
def get_projects(context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": projects.get_projects(tenant_id=context.tenant_id, recording_state=True, gdpr=True, recorded=True,
|
||||
stack_integrations=True, user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}', tags=["sessions"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}', tags=["sessions"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
def get_session(projectId: int, sessionId: Union[int, str], background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str):
|
||||
return {"errors": ["session not found"]}
|
||||
data = sessions.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True, user_id=context.user_id,
|
||||
include_fav_viewed=True, group_metadata=True)
|
||||
if data is None:
|
||||
return {"errors": ["session not found"]}
|
||||
if data.get("inDB"):
|
||||
background_tasks.add_task(sessions_viewed.view_session, project_id=projectId, user_id=context.user_id,
|
||||
session_id=sessionId)
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/errors/{errorId}/sourcemaps', tags=["sessions", "sourcemaps"],
|
||||
dependencies=[Security(router_security.check,
|
||||
scopes=[Permissions.session_replay, Permissions.errors])])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/errors/{errorId}/sourcemaps', tags=["sessions", "sourcemaps"],
|
||||
dependencies=[Security(router_security.check,
|
||||
scopes=[Permissions.session_replay, Permissions.errors])])
|
||||
def get_error_trace(projectId: int, sessionId: int, errorId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_trace(project_id=projectId, error_id=errorId)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/errors/search', tags=['errors'],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.errors])])
|
||||
def errors_search(projectId: int, data: schemas.SearchErrorsSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": errors.search(data, projectId, user_id=context.user_id)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/stats', tags=['errors'],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.errors])])
|
||||
def errors_stats(projectId: int, startTimestamp: int, endTimestamp: int,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return errors.stats(projectId, user_id=context.user_id, startTimestamp=startTimestamp, endTimestamp=endTimestamp)
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}', tags=['errors'],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.errors])])
|
||||
def errors_get_details(projectId: int, errorId: str, background_tasks: BackgroundTasks, density24: int = 24,
|
||||
density30: int = 30,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_details(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
**{"density24": density24, "density30": density30})
|
||||
if data.get("data") is not None:
|
||||
background_tasks.add_task(errors_viewed.viewed_error, project_id=projectId, user_id=context.user_id,
|
||||
error_id=errorId)
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/stats', tags=['errors'],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.errors])])
|
||||
def errors_get_details_right_column(projectId: int, errorId: str, startDate: int = TimeUTC.now(-7),
|
||||
endDate: int = TimeUTC.now(), density: int = 7,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_details_chart(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
**{"startDate": startDate, "endDate": endDate, "density": density})
|
||||
return data
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/sourcemaps', tags=['errors'],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.errors])])
|
||||
def errors_get_details_sourcemaps(projectId: int, errorId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = errors.get_trace(project_id=projectId, error_id=errorId)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/errors/{errorId}/{action}', tags=["errors"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.errors])])
|
||||
def add_remove_favorite_error(projectId: int, errorId: str, action: str, startDate: int = TimeUTC.now(-7),
|
||||
endDate: int = TimeUTC.now(), context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if action == "favorite":
|
||||
return errors_favorite.favorite_error(project_id=projectId, user_id=context.user_id, error_id=errorId)
|
||||
elif action == "sessions":
|
||||
start_date = startDate
|
||||
end_date = endDate
|
||||
return {
|
||||
"data": errors.get_sessions(project_id=projectId, user_id=context.user_id, error_id=errorId,
|
||||
start_date=start_date, end_date=end_date)}
|
||||
elif action in list(errors.ACTION_STATE.keys()):
|
||||
return errors.change_state(project_id=projectId, user_id=context.user_id, error_id=errorId, action=action)
|
||||
else:
|
||||
return {"errors": ["undefined action"]}
|
||||
|
||||
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}', tags=["assist"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.assist_live])])
|
||||
def get_live_session(projectId: int, sessionId: str, background_tasks: BackgroundTasks,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = assist.get_live_session_by_id(project_id=projectId, session_id=sessionId)
|
||||
if data is None:
|
||||
data = sessions.get_by_id2_pg(project_id=projectId, session_id=sessionId, full_data=True,
|
||||
user_id=context.user_id, include_fav_viewed=True, group_metadata=True, live=False)
|
||||
if data is None:
|
||||
return {"errors": ["session not found"]}
|
||||
if data.get("inDB"):
|
||||
background_tasks.add_task(sessions_viewed.view_session, project_id=projectId,
|
||||
user_id=context.user_id, session_id=sessionId)
|
||||
return {'data': data}
|
||||
|
||||
|
||||
@app.get('/{projectId}/unprocessed/{sessionId}', tags=["assist"], dependencies=[Security(router_security.check, scopes=[
|
||||
Permissions.assist_live, Permissions.session_replay])])
|
||||
@app.get('/{projectId}/assist/sessions/{sessionId}/replay', tags=["assist"], dependencies=[
|
||||
Security(router_security.check,
|
||||
scopes=[Permissions.assist_live, Permissions.session_replay])])
|
||||
def get_live_session_replay_file(projectId: int, sessionId: Union[int, str],
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
if isinstance(sessionId, str) or not sessions.session_exists(project_id=projectId, session_id=sessionId):
|
||||
if isinstance(sessionId, str):
|
||||
print(f"{sessionId} not a valid number.")
|
||||
else:
|
||||
print(f"{projectId}/{sessionId} not found in DB.")
|
||||
|
||||
return {"errors": ["Replay file not found"]}
|
||||
path = assist.get_raw_mob_by_id(project_id=projectId, session_id=sessionId)
|
||||
if path is None:
|
||||
return {"errors": ["Replay file not found"]}
|
||||
|
||||
return FileResponse(path=path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@app.post('/{projectId}/heatmaps/url', tags=["heatmaps"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
def get_heatmaps_by_url(projectId: int, data: schemas.GetHeatmapPayloadSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {"data": heatmaps.get_by_url(project_id=projectId, data=data.dict())}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/favorite', tags=["sessions"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/favorite', tags=["sessions"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
def add_remove_favorite_session2(projectId: int, sessionId: int,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
return {
|
||||
"data": sessions_favorite.favorite_session(project_id=projectId, user_id=context.user_id,
|
||||
session_id=sessionId)}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/assign', tags=["sessions"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/assign', tags=["sessions"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
def assign_session(projectId: int, sessionId, context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.get_by_session(project_id=projectId, session_id=sessionId,
|
||||
tenant_id=context.tenant_id,
|
||||
user_id=context.user_id)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.get('/{projectId}/sessions/{sessionId}/assign/{issueId}', tags=["sessions", "issueTracking"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
@app.get('/{projectId}/sessions2/{sessionId}/assign/{issueId}', tags=["sessions", "issueTracking"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
def assign_session(projectId: int, sessionId: int, issueId: str,
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.get(project_id=projectId, session_id=sessionId, assignment_id=issueId,
|
||||
tenant_id=context.tenant_id, user_id=context.user_id)
|
||||
if "errors" in data:
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
||||
|
||||
@app.post('/{projectId}/sessions/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
@app.put('/{projectId}/sessions/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
@app.post('/{projectId}/sessions2/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
@app.put('/{projectId}/sessions2/{sessionId}/assign/{issueId}/comment', tags=["sessions", "issueTracking"],
|
||||
dependencies=[Security(router_security.check, scopes=[Permissions.session_replay])])
|
||||
def comment_assignment(projectId: int, sessionId: int, issueId: str, data: schemas.CommentAssignmentSchema = Body(...),
|
||||
context: schemas.CurrentContext = Depends(OR_context)):
|
||||
data = sessions_assignments.comment(tenant_id=context.tenant_id, project_id=projectId,
|
||||
session_id=sessionId, assignment_id=issueId,
|
||||
user_id=context.user_id, message=data.message)
|
||||
if "errors" in data.keys():
|
||||
return data
|
||||
return {
|
||||
'data': data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,26 @@ from pydantic import BaseModel, Field, EmailStr
|
|||
|
||||
import schemas
|
||||
from chalicelib.utils.TimeUTC import TimeUTC
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class Permissions(str, Enum):
|
||||
session_replay = "SESSION_REPLAY"
|
||||
dev_tools = "DEV_TOOLS"
|
||||
errors = "ERRORS"
|
||||
metrics = "METRICS"
|
||||
assist_live = "ASSIST_LIVE"
|
||||
assist_call = "ASSIST_CALL"
|
||||
|
||||
|
||||
class CurrentContext(schemas.CurrentContext):
|
||||
permissions: List[Optional[Permissions]] = Field(...)
|
||||
|
||||
|
||||
class RolePayloadSchema(BaseModel):
|
||||
name: str = Field(...)
|
||||
description: Optional[str] = Field(None)
|
||||
permissions: List[str] = Field(...)
|
||||
permissions: List[Permissions] = Field(...)
|
||||
all_projects: bool = Field(True)
|
||||
projects: List[int] = Field([])
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue