* feat(api): dynamic-api 1/2
* feat(api): dynamic-api 2/2
feat(api): core-api 1/2
* feat(api): changed schemas
feat(api): aipkey authorizer
feat(api): jwt authorizer payload
feat(api): core-api 2/3
* feat(api): apikey authorizer
feat(api): shared context
feat(api): response editor
feat(api): middleware
feat(api): custom router
feat(api): fix auth double call
* feat(api): dashboard
feat(api): insights
feat(api): public api v1
* feat(api): allow full CORS
* feat(api): use decouple-config instead of env
feat(api): fixed conflict slack endpoint
feat(api): fixed favorite errors param
* feat(api): migration fixes
* feat(api): changes
* feat(api): crons
* feat(api): changes and fixes
* feat(api): added new endpoints
feat(api): applied new changes
feat(api): Docker image
* feat(api): EE 1/4
* feat(api): EE core_dynamic
* feat(api): global routers generator
* feat(api): project authorizer
feat(api): docker image
feat(api): crons
* feat(api): EE trace activity
* feat(api): changed ORRouter
* feat(api): EE trace activity parameters&payload
* feat(api): EE trace activity action name & path_format
* feat(db): user trace
* feat(api): EE trace activity ignore routes and hide attribute
feat(api): fix funnel payload schema
* feat(api): mobile support
* feat(api): changed build script
* feat(api): changed mobile sign endpoint
feat(api): changed requirements.txt
* feat(api): changed dockerfile
* feat(api): changed mobile-env-var
* feat(api): removed insights
* feat(api): changed EE Dockerfile
* feat(api): cast session_id to str for signing
* feat(api): fixed error_id type
* feat(api): fixed /errors priority conflict
* feat(api): fixed /errors/{errorId} default params
* feat(api): fixed change password after invitation
* feat(api): use background task for emails instead of low-timeout-api
feat(api): EE fixed missing required params
* feat(api): funnel-insights payload change
* feat(api): funnel-insights payload change
* feat(api): changed edit user payload schema
* feat(api): changed metrics payload schema
* feat(api): changed metrics payload schema
* feat(api): changed edit user default values
feat(api): fixed change error status route
* feat(api): changed edit user
* feat(api): stop user from changing his own role
* feat(api): changed add slack
* feat(api): changed get funnel
* feat(api): changed get funnel on the fly payload
feat(api): changed update payload
* feat(api): changed get funnel on the fly payload
* feat(api): changed update funnel payload
* feat(api): changed get funnel-sessions/issues on the fly payload
* feat(api): fixed funnel missing rangeValue
* feat(api): fixes
* feat(api): iceServers configuration
* feat(api): fix issueId casting
* feat(api): changed issues-sessions endpoint payload-schema
* feat(api): EE changed traces-ignored-routes
* feat(api): EE include core sessions.py
* feat(api): EE check licence on every request if expired
* feat(api): move general stats to dynamic
* feat(api): code cleanup
feat(api): removed sentry
* feat(api): changed traces-ignore-routes
* feat(api): changed dependencies
* feat(api): changed jwt-auth-response code
* feat(api): changed traces-ignore-routes
* feat(api): changed traces-ignore-routes
* feat(api): removed PyTZ
feat(api): migrated time-helper to zoneinfo
* feat(api): EE added missing dependency
feat(api): changed base docker image
* feat(api): merge after roles
* feat(api): EE roles fastapi
* feat(db): handel HTTPExceptions
* feat(db): changed payload schema
* feat(db): changed payload schema
* feat(api): included insights
* feat(api): removed unused helper
* feat(api): merge from dev to fatsapi
* feat(api): merge fixes
feat(api): SAML migration
* feat(api): changed GET /signup response
feat(api): changed EE Dockerfile
* feat(api): changed edition detection
* feat(api): include ee endpoints
* feat(api): add/edit member changes
* feat(api): saml changed redirect
* feat(api): track session's replay
feat(api): track error's details
* feat(api): ignore tracking for read roles
* feat(api): define global queue
feat(api): define global scheduler
feat(api): traces use queue
feat(api): traces batch insert
feat(DB): changed traces schema
* feat(api): fix signup captcha
* feat(api): fix signup captcha
* feat(api): optional roleId
feat(api): set roleId to member if None
* feat(api): fixed edit role
* feat(api): return role details when creating a new member
* feat(api): trace: use BackgroundTasks instead of BackgroundTask to not override previous tasks
* feat(api): trace: use BackgroundTask if no other background task is defined
* feat(api): optimised delete metadata
* feat(api): Notification optional message
* feat(api): fix background-task reference
* feat(api): fix trace-background-task
* feat(api): fixed g-captcha for reset password
* feat(api): fix edit self-user
* feat(api): fixed create github-issue
* feat(api): set misfire_grace_time for crons
* feat(api): removed chalice
feat(api): freeze dependencies
* feat(api): refactored blueprints
* feat(api): /metadata/session_search allow projectId=None
* feat(api): public API, changed userId type
* feat(api): fix upload sourcemaps
* feat(api): user-trace support ApiKey endpoints
* feat(api): fixed user-trace foreign key type
* feat(api): fixed trace schema
* feat(api): trace save auth-method
* feat(api): trace fixed auth-method
* feat(api): trace changed schema
218 lines
9 KiB
Python
218 lines
9 KiB
Python
from typing import Optional
|
||
|
||
from decouple import config
|
||
from fastapi import Body, Depends, HTTPException, status, BackgroundTasks
|
||
from starlette.responses import RedirectResponse
|
||
|
||
import schemas
|
||
from chalicelib.core import assist
|
||
from chalicelib.core import integrations_manager
|
||
from chalicelib.core import sessions
|
||
from chalicelib.core import tenants, users, metadata, projects, license, alerts
|
||
from chalicelib.core import webhook
|
||
from chalicelib.core.collaboration_slack import Slack
|
||
from chalicelib.utils import captcha
|
||
from chalicelib.utils import helper
|
||
from or_dependencies import OR_context
|
||
from routers.base import get_routers
|
||
|
||
public_app, app, app_apikey = get_routers()
|
||
|
||
|
||
@public_app.get('/signup', tags=['signup'])
|
||
def get_all_signup():
|
||
return {"data": {"tenants": tenants.tenants_exists(),
|
||
"sso": None,
|
||
"ssoProvider": None,
|
||
"edition": helper.get_edition()}}
|
||
|
||
|
||
@public_app.post('/login', tags=["authentication"])
|
||
def login(data: schemas.UserLoginSchema = Body(...)):
|
||
if helper.allow_captcha() and not captcha.is_valid(data.g_recaptcha_response):
|
||
raise HTTPException(
|
||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
detail="Invalid captcha."
|
||
)
|
||
|
||
r = users.authenticate(data.email, data.password, for_plugin=False)
|
||
if r is None:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||
detail="You’ve entered invalid Email or Password."
|
||
)
|
||
|
||
tenant_id = r.pop("tenantId")
|
||
|
||
r["limits"] = {
|
||
"teamMember": -1,
|
||
"projects": -1,
|
||
"metadata": metadata.get_remaining_metadata_with_count(tenant_id)}
|
||
|
||
c = tenants.get_by_tenant_id(tenant_id)
|
||
c.pop("createdAt")
|
||
c["projects"] = projects.get_projects(tenant_id=tenant_id, recording_state=True, recorded=True,
|
||
stack_integrations=True, version=True)
|
||
c["smtp"] = helper.has_smtp()
|
||
c["iceServers"]: assist.get_ice_servers()
|
||
return {
|
||
'jwt': r.pop('jwt'),
|
||
'data': {
|
||
"user": r,
|
||
"client": c
|
||
}
|
||
}
|
||
|
||
|
||
@app.get('/account', tags=['accounts'])
|
||
def get_account(context: schemas.CurrentContext = Depends(OR_context)):
|
||
r = users.get(tenant_id=context.tenant_id, user_id=context.user_id)
|
||
return {
|
||
'data': {
|
||
**r,
|
||
"limits": {
|
||
"teamMember": -1,
|
||
"projects": -1,
|
||
"metadata": metadata.get_remaining_metadata_with_count(context.tenant_id)
|
||
},
|
||
**license.get_status(context.tenant_id),
|
||
"smtp": helper.has_smtp(),
|
||
"iceServers": assist.get_ice_servers()
|
||
}
|
||
}
|
||
|
||
|
||
@app.get('/projects/limit', tags=['projects'])
|
||
def get_projects_limit(context: schemas.CurrentContext = Depends(OR_context)):
|
||
return {"data": {
|
||
"current": projects.count_by_tenant(tenant_id=context.tenant_id),
|
||
"remaining": -1
|
||
}}
|
||
|
||
|
||
@app.get('/projects/{projectId}', tags=['projects'])
|
||
def get_project(projectId: int, context: schemas.CurrentContext = Depends(OR_context)):
|
||
data = projects.get_project(tenant_id=context.tenant_id, project_id=projectId, include_last_session=True,
|
||
include_gdpr=True)
|
||
if data is None:
|
||
return {"errors": ["project not found"]}
|
||
return {"data": data}
|
||
|
||
|
||
@app.put('/integrations/slack', tags=['integrations'])
|
||
@app.post('/integrations/slack', tags=['integrations'])
|
||
def add_slack_client(data: schemas.AddSlackSchema, context: schemas.CurrentContext = Depends(OR_context)):
|
||
n = Slack.add_channel(tenant_id=context.tenant_id, url=data.url, name=data.name)
|
||
if n is None:
|
||
return {
|
||
"errors": ["We couldn't send you a test message on your Slack channel. Please verify your webhook url."]
|
||
}
|
||
return {"data": n}
|
||
|
||
|
||
@app.put('/integrations/slack/{integrationId}', tags=['integrations'])
|
||
@app.post('/integrations/slack/{integrationId}', tags=['integrations'])
|
||
def edit_slack_integration(integrationId: int, data: schemas.EditSlackSchema = Body(...),
|
||
context: schemas.CurrentContext = Depends(OR_context)):
|
||
if len(data.url) > 0:
|
||
old = webhook.get(tenant_id=context.tenant_id, webhook_id=integrationId)
|
||
if old["endpoint"] != data.url:
|
||
if not Slack.say_hello(data.url):
|
||
return {
|
||
"errors": [
|
||
"We couldn't send you a test message on your Slack channel. Please verify your webhook url."]
|
||
}
|
||
return {"data": webhook.update(tenant_id=context.tenant_id, webhook_id=integrationId,
|
||
changes={"name": data.name, "endpoint": data.url})}
|
||
|
||
|
||
# this endpoint supports both jira & github based on `provider` attribute
|
||
@app.post('/integrations/issues', tags=["integrations"])
|
||
def add_edit_jira_cloud_github(data: schemas.JiraGithubSchema,
|
||
context: schemas.CurrentContext = Depends(OR_context)):
|
||
provider = data.provider.upper()
|
||
error, integration = integrations_manager.get_integration(tool=provider, tenant_id=context.tenant_id,
|
||
user_id=context.user_id)
|
||
if error is not None:
|
||
return error
|
||
return {"data": integration.add_edit(data=data.dict())}
|
||
|
||
|
||
@app.post('/client/members', tags=["client"])
|
||
@app.put('/client/members', tags=["client"])
|
||
def add_member(background_tasks: BackgroundTasks, data: schemas.CreateMemberSchema = Body(...),
|
||
context: schemas.CurrentContext = Depends(OR_context)):
|
||
return users.create_member(tenant_id=context.tenant_id, user_id=context.user_id, data=data.dict(),
|
||
background_tasks=background_tasks)
|
||
|
||
|
||
@public_app.get('/users/invitation', tags=['users'])
|
||
def process_invitation_link(token: str):
|
||
if token is None or len(token) < 64:
|
||
return {"errors": ["please provide a valid invitation"]}
|
||
user = users.get_by_invitation_token(token)
|
||
if user is None:
|
||
return {"errors": ["invitation not found"]}
|
||
if user["expiredInvitation"]:
|
||
return {"errors": ["expired invitation, please ask your admin to send a new one"]}
|
||
if user["expiredChange"] is not None and not user["expiredChange"] \
|
||
and user["changePwdToken"] is not None and user["changePwdAge"] < -5 * 60:
|
||
pass_token = user["changePwdToken"]
|
||
else:
|
||
pass_token = users.allow_password_change(user_id=user["userId"])
|
||
return RedirectResponse(url=config("SITE_URL") + config("change_password_link") % (token, pass_token))
|
||
|
||
|
||
@public_app.post('/password/reset', tags=["users"])
|
||
@public_app.put('/password/reset', tags=["users"])
|
||
def change_password_by_invitation(data: schemas.EditPasswordByInvitationSchema = Body(...)):
|
||
if data is None or len(data.invitation) < 64 or len(data.passphrase) < 8:
|
||
return {"errors": ["please provide a valid invitation & pass"]}
|
||
user = users.get_by_invitation_token(token=data.invitation, pass_token=data.passphrase)
|
||
if user is None:
|
||
return {"errors": ["invitation not found"]}
|
||
if user["expiredChange"]:
|
||
return {"errors": ["expired change, please re-use the invitation link"]}
|
||
|
||
return users.set_password_invitation(new_password=data.password, user_id=user["userId"])
|
||
|
||
|
||
@app.put('/client/members/{memberId}', tags=["client"])
|
||
@app.post('/client/members/{memberId}', tags=["client"])
|
||
def edit_member(memberId: int, data: schemas.EditMemberSchema,
|
||
context: schemas.CurrentContext = Depends(OR_context)):
|
||
return users.edit(tenant_id=context.tenant_id, editor_id=context.user_id, changes=data.dict(),
|
||
user_id_to_update=memberId)
|
||
|
||
|
||
@app.get('/metadata/session_search', tags=["metadata"])
|
||
def search_sessions_by_metadata(key: str, value: str, projectId: Optional[int] = None,
|
||
context: schemas.CurrentContext = Depends(OR_context)):
|
||
if key is None or value is None or len(value) == 0 and len(key) == 0:
|
||
return {"errors": ["please provide a key&value for search"]}
|
||
if len(value) == 0:
|
||
return {"errors": ["please provide a value for search"]}
|
||
if len(key) == 0:
|
||
return {"errors": ["please provide a key for search"]}
|
||
return {
|
||
"data": sessions.search_by_metadata(tenant_id=context.tenant_id, user_id=context.user_id, m_value=value,
|
||
m_key=key, project_id=projectId)}
|
||
|
||
|
||
@app.get('/plans', tags=["plan"])
|
||
def get_current_plan(context: schemas.CurrentContext = Depends(OR_context)):
|
||
return {
|
||
"data": license.get_status(context.tenant_id)
|
||
}
|
||
|
||
|
||
@public_app.post('/alerts/notifications', tags=["alerts"])
|
||
@public_app.put('/alerts/notifications', tags=["alerts"])
|
||
def send_alerts_notifications(background_tasks: BackgroundTasks, data: schemas.AlertNotificationSchema = Body(...)):
|
||
# TODO: validate token
|
||
return {"data": alerts.process_notifications(data.notifications, background_tasks=background_tasks)}
|
||
|
||
|
||
@public_app.get('/general_stats', tags=["private"], include_in_schema=False)
|
||
def get_general_stats():
|
||
return {"data": {"sessions:": sessions.count_all()}}
|