2023-07-11 19:20:31 +03:00
|
|
|
from typing import List
|
|
|
|
from uuid import UUID
|
|
|
|
|
2023-07-17 16:45:18 +03:00
|
|
|
from auth.auth_bearer import AuthBearer, get_current_user
|
2023-07-11 19:20:31 +03:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
2023-11-13 21:08:47 +03:00
|
|
|
from models import BrainSubscription, PromptStatusEnum, UserIdentity
|
2023-07-18 15:30:19 +03:00
|
|
|
from pydantic import BaseModel
|
2023-08-21 13:25:16 +03:00
|
|
|
from repository.brain import (
|
|
|
|
create_brain_user,
|
2023-08-21 15:05:13 +03:00
|
|
|
get_brain_by_id,
|
2023-08-21 13:25:16 +03:00
|
|
|
get_brain_details,
|
|
|
|
get_brain_for_user,
|
|
|
|
update_brain_user_rights,
|
2023-07-18 15:30:19 +03:00
|
|
|
)
|
2023-11-13 21:08:47 +03:00
|
|
|
from repository.brain.delete_brain import delete_brain
|
2023-09-25 15:22:59 +03:00
|
|
|
from repository.brain.delete_brain_user import delete_brain_user
|
2023-11-13 21:08:47 +03:00
|
|
|
from repository.brain.get_brain_users import get_brain_users
|
2023-08-21 13:25:16 +03:00
|
|
|
from repository.brain_subscription import (
|
2023-07-18 15:30:19 +03:00
|
|
|
SubscriptionInvitationService,
|
2023-08-21 13:25:16 +03:00
|
|
|
resend_invitation_email,
|
2023-07-18 15:30:19 +03:00
|
|
|
)
|
2023-08-21 13:25:16 +03:00
|
|
|
from repository.prompt import delete_prompt_by_id, get_prompt_by_id
|
2023-11-13 21:08:47 +03:00
|
|
|
from repository.user import get_user_id_by_user_email
|
2023-08-30 16:55:06 +03:00
|
|
|
|
2023-07-18 15:30:19 +03:00
|
|
|
from routes.authorizations.brain_authorization import (
|
2023-07-19 14:36:23 +03:00
|
|
|
RoleEnum,
|
2023-07-18 15:30:19 +03:00
|
|
|
has_brain_authorization,
|
|
|
|
validate_brain_authorization,
|
|
|
|
)
|
2023-07-18 10:47:59 +03:00
|
|
|
from routes.headers.get_origin_header import get_origin_header
|
2023-07-11 19:20:31 +03:00
|
|
|
|
|
|
|
subscription_router = APIRouter()
|
2023-07-18 10:47:59 +03:00
|
|
|
subscription_service = SubscriptionInvitationService()
|
|
|
|
|
|
|
|
|
|
|
|
@subscription_router.post(
|
|
|
|
"/brains/{brain_id}/subscription",
|
|
|
|
dependencies=[
|
|
|
|
Depends(
|
2023-07-18 15:30:19 +03:00
|
|
|
AuthBearer(),
|
2023-07-18 10:47:59 +03:00
|
|
|
),
|
2023-07-19 14:36:23 +03:00
|
|
|
Depends(has_brain_authorization([RoleEnum.Owner, RoleEnum.Editor])),
|
2023-07-18 10:47:59 +03:00
|
|
|
Depends(get_origin_header),
|
|
|
|
],
|
|
|
|
tags=["BrainSubscription"],
|
|
|
|
)
|
2023-07-18 15:30:19 +03:00
|
|
|
def invite_users_to_brain(
|
|
|
|
brain_id: UUID,
|
|
|
|
users: List[dict],
|
|
|
|
origin: str = Depends(get_origin_header),
|
2023-08-21 15:05:13 +03:00
|
|
|
current_user: UserIdentity = Depends(get_current_user),
|
2023-07-18 15:30:19 +03:00
|
|
|
):
|
2023-07-18 10:47:59 +03:00
|
|
|
"""
|
|
|
|
Invite multiple users to a brain by their emails. This function creates
|
|
|
|
or updates a brain subscription invitation for each user and sends an
|
|
|
|
invitation email to each user.
|
|
|
|
"""
|
2023-07-11 19:20:31 +03:00
|
|
|
for user in users:
|
2023-07-18 15:30:19 +03:00
|
|
|
subscription = BrainSubscription(
|
|
|
|
brain_id=brain_id, email=user["email"], rights=user["rights"]
|
|
|
|
)
|
2023-07-20 16:15:32 +03:00
|
|
|
# check if user is an editor but trying to give high level permissions
|
|
|
|
if subscription.rights == "Owner":
|
|
|
|
try:
|
|
|
|
validate_brain_authorization(
|
|
|
|
brain_id,
|
|
|
|
current_user.id,
|
|
|
|
RoleEnum.Owner,
|
|
|
|
)
|
|
|
|
except HTTPException:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You don't have the rights to give owner permissions",
|
|
|
|
)
|
2023-07-18 15:30:19 +03:00
|
|
|
|
2023-07-11 19:20:31 +03:00
|
|
|
try:
|
2023-08-30 16:55:06 +03:00
|
|
|
should_send_invitation_email = (
|
|
|
|
subscription_service.create_or_update_subscription_invitation(
|
|
|
|
subscription
|
|
|
|
)
|
2023-07-18 15:30:19 +03:00
|
|
|
)
|
2023-08-30 16:55:06 +03:00
|
|
|
if should_send_invitation_email:
|
|
|
|
resend_invitation_email(
|
|
|
|
subscription,
|
|
|
|
inviter_email=current_user.email or "Quivr",
|
|
|
|
origin=origin,
|
|
|
|
)
|
2023-07-11 19:20:31 +03:00
|
|
|
except Exception as e:
|
|
|
|
raise HTTPException(status_code=400, detail=f"Error inviting user: {e}")
|
|
|
|
|
|
|
|
return {"message": "Invitations sent successfully"}
|
2023-07-13 19:05:36 +03:00
|
|
|
|
|
|
|
|
2023-07-17 16:45:18 +03:00
|
|
|
@subscription_router.get(
|
2023-07-18 10:47:59 +03:00
|
|
|
"/brains/{brain_id}/users",
|
2023-07-19 14:36:23 +03:00
|
|
|
dependencies=[
|
|
|
|
Depends(AuthBearer()),
|
|
|
|
Depends(has_brain_authorization([RoleEnum.Owner, RoleEnum.Editor])),
|
|
|
|
],
|
2023-07-17 16:45:18 +03:00
|
|
|
)
|
2023-07-13 19:05:36 +03:00
|
|
|
@subscription_router.delete(
|
2023-07-18 10:47:59 +03:00
|
|
|
"/brains/{brain_id}/subscription",
|
2023-07-13 19:05:36 +03:00
|
|
|
)
|
|
|
|
async def remove_user_subscription(
|
2023-08-21 15:05:13 +03:00
|
|
|
brain_id: UUID, current_user: UserIdentity = Depends(get_current_user)
|
2023-07-13 19:05:36 +03:00
|
|
|
):
|
|
|
|
"""
|
|
|
|
Remove a user's subscription to a brain
|
|
|
|
"""
|
2023-11-06 18:58:03 +03:00
|
|
|
targeted_brain = get_brain_by_id(brain_id)
|
|
|
|
|
|
|
|
if targeted_brain is None:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=404,
|
|
|
|
detail="Brain not found while trying to delete",
|
|
|
|
)
|
|
|
|
|
2023-08-03 11:37:13 +03:00
|
|
|
user_brain = get_brain_for_user(current_user.id, brain_id)
|
2023-07-13 19:05:36 +03:00
|
|
|
if user_brain is None:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You don't have permission for this brain",
|
|
|
|
)
|
|
|
|
|
2023-08-03 11:37:13 +03:00
|
|
|
if user_brain.rights != "Owner":
|
2023-11-13 21:08:47 +03:00
|
|
|
delete_brain_user(current_user.id, brain_id)
|
2023-07-13 19:05:36 +03:00
|
|
|
else:
|
2023-11-13 21:08:47 +03:00
|
|
|
brain_users = get_brain_users(
|
|
|
|
brain_id=brain_id,
|
|
|
|
)
|
2023-07-13 19:05:36 +03:00
|
|
|
brain_other_owners = [
|
|
|
|
brain
|
2023-07-17 16:45:18 +03:00
|
|
|
for brain in brain_users
|
2023-11-13 21:08:47 +03:00
|
|
|
if brain.rights == "Owner" and str(brain.user_id) != str(current_user.id)
|
2023-07-13 19:05:36 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
if len(brain_other_owners) == 0:
|
2023-11-13 21:08:47 +03:00
|
|
|
delete_brain(
|
|
|
|
brain_id=brain_id,
|
|
|
|
)
|
2023-11-06 18:58:03 +03:00
|
|
|
if targeted_brain.prompt_id:
|
|
|
|
brain_to_delete_prompt = get_prompt_by_id(targeted_brain.prompt_id)
|
|
|
|
if brain_to_delete_prompt is not None and (
|
|
|
|
brain_to_delete_prompt.status == PromptStatusEnum.private
|
|
|
|
):
|
|
|
|
delete_prompt_by_id(targeted_brain.prompt_id)
|
2023-08-16 14:59:19 +03:00
|
|
|
|
2023-07-13 19:05:36 +03:00
|
|
|
else:
|
2023-11-13 21:08:47 +03:00
|
|
|
delete_brain_user(
|
|
|
|
current_user.id,
|
|
|
|
brain_id,
|
|
|
|
)
|
2023-07-13 19:05:36 +03:00
|
|
|
|
|
|
|
return {"message": f"Subscription removed successfully from brain {brain_id}"}
|
2023-07-18 10:47:59 +03:00
|
|
|
|
|
|
|
|
|
|
|
@subscription_router.get(
|
|
|
|
"/brains/{brain_id}/subscription",
|
|
|
|
dependencies=[Depends(AuthBearer())],
|
|
|
|
tags=["BrainSubscription"],
|
|
|
|
)
|
2023-08-21 15:05:13 +03:00
|
|
|
def get_user_invitation(
|
|
|
|
brain_id: UUID, current_user: UserIdentity = Depends(get_current_user)
|
|
|
|
):
|
2023-07-18 10:47:59 +03:00
|
|
|
"""
|
|
|
|
Get an invitation to a brain for a user. This function checks if the user
|
|
|
|
has been invited to the brain and returns the invitation status.
|
|
|
|
"""
|
|
|
|
if not current_user.email:
|
2023-08-21 15:05:13 +03:00
|
|
|
raise HTTPException(status_code=400, detail="UserIdentity email is not defined")
|
2023-07-18 10:47:59 +03:00
|
|
|
|
|
|
|
subscription = BrainSubscription(brain_id=brain_id, email=current_user.email)
|
|
|
|
|
2023-07-18 19:28:44 +03:00
|
|
|
invitation = subscription_service.fetch_invitation(subscription)
|
|
|
|
|
|
|
|
if invitation is None:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=404,
|
|
|
|
detail="You have not been invited to this brain",
|
|
|
|
)
|
|
|
|
|
2023-08-03 11:37:13 +03:00
|
|
|
brain_details = get_brain_details(brain_id)
|
2023-07-18 19:28:44 +03:00
|
|
|
|
2023-08-01 17:25:02 +03:00
|
|
|
if brain_details is None:
|
2023-07-18 19:28:44 +03:00
|
|
|
raise HTTPException(
|
|
|
|
status_code=404,
|
|
|
|
detail="Brain not found while trying to get invitation",
|
|
|
|
)
|
|
|
|
|
2023-08-03 11:37:13 +03:00
|
|
|
return {"name": brain_details.name, "rights": invitation["rights"]}
|
2023-07-18 10:47:59 +03:00
|
|
|
|
|
|
|
|
|
|
|
@subscription_router.post(
|
|
|
|
"/brains/{brain_id}/subscription/accept",
|
|
|
|
tags=["Brain"],
|
|
|
|
)
|
2023-07-18 15:30:19 +03:00
|
|
|
async def accept_invitation(
|
2023-08-21 15:05:13 +03:00
|
|
|
brain_id: UUID, current_user: UserIdentity = Depends(get_current_user)
|
2023-07-18 15:30:19 +03:00
|
|
|
):
|
2023-07-18 10:47:59 +03:00
|
|
|
"""
|
|
|
|
Accept an invitation to a brain for a user. This function removes the
|
|
|
|
invitation from the subscription invitations and adds the user to the
|
|
|
|
brain users.
|
|
|
|
"""
|
|
|
|
if not current_user.email:
|
2023-08-21 15:05:13 +03:00
|
|
|
raise HTTPException(status_code=400, detail="UserIdentity email is not defined")
|
2023-07-18 10:47:59 +03:00
|
|
|
|
|
|
|
subscription = BrainSubscription(brain_id=brain_id, email=current_user.email)
|
|
|
|
|
|
|
|
try:
|
|
|
|
invitation = subscription_service.fetch_invitation(subscription)
|
|
|
|
except Exception as e:
|
|
|
|
raise HTTPException(status_code=400, detail=f"Error fetching invitation: {e}")
|
|
|
|
|
|
|
|
if not invitation:
|
|
|
|
raise HTTPException(status_code=404, detail="Invitation not found")
|
|
|
|
|
|
|
|
try:
|
2023-08-03 11:37:13 +03:00
|
|
|
create_brain_user(
|
|
|
|
user_id=current_user.id,
|
|
|
|
brain_id=brain_id,
|
|
|
|
rights=invitation["rights"],
|
|
|
|
is_default_brain=False,
|
2023-07-18 10:47:59 +03:00
|
|
|
)
|
|
|
|
except Exception as e:
|
|
|
|
raise HTTPException(status_code=400, detail=f"Error adding user to brain: {e}")
|
|
|
|
|
|
|
|
try:
|
|
|
|
subscription_service.remove_invitation(subscription)
|
|
|
|
except Exception as e:
|
|
|
|
raise HTTPException(status_code=400, detail=f"Error removing invitation: {e}")
|
|
|
|
|
|
|
|
return {"message": "Invitation accepted successfully"}
|
|
|
|
|
|
|
|
|
|
|
|
@subscription_router.post(
|
|
|
|
"/brains/{brain_id}/subscription/decline",
|
|
|
|
tags=["Brain"],
|
|
|
|
)
|
2023-07-18 15:30:19 +03:00
|
|
|
async def decline_invitation(
|
2023-08-21 15:05:13 +03:00
|
|
|
brain_id: UUID, current_user: UserIdentity = Depends(get_current_user)
|
2023-07-18 15:30:19 +03:00
|
|
|
):
|
2023-07-18 10:47:59 +03:00
|
|
|
"""
|
|
|
|
Decline an invitation to a brain for a user. This function removes the
|
|
|
|
invitation from the subscription invitations.
|
|
|
|
"""
|
|
|
|
if not current_user.email:
|
2023-08-21 15:05:13 +03:00
|
|
|
raise HTTPException(status_code=400, detail="UserIdentity email is not defined")
|
2023-07-18 10:47:59 +03:00
|
|
|
|
|
|
|
subscription = BrainSubscription(brain_id=brain_id, email=current_user.email)
|
|
|
|
|
|
|
|
try:
|
|
|
|
invitation = subscription_service.fetch_invitation(subscription)
|
|
|
|
except Exception as e:
|
|
|
|
raise HTTPException(status_code=400, detail=f"Error fetching invitation: {e}")
|
2023-07-18 15:30:19 +03:00
|
|
|
|
2023-07-18 10:47:59 +03:00
|
|
|
if not invitation:
|
|
|
|
raise HTTPException(status_code=404, detail="Invitation not found")
|
|
|
|
|
|
|
|
try:
|
|
|
|
subscription_service.remove_invitation(subscription)
|
|
|
|
except Exception as e:
|
|
|
|
raise HTTPException(status_code=400, detail=f"Error removing invitation: {e}")
|
|
|
|
|
|
|
|
return {"message": "Invitation declined successfully"}
|
2023-07-18 15:30:19 +03:00
|
|
|
|
|
|
|
|
|
|
|
class BrainSubscriptionUpdatableProperties(BaseModel):
|
|
|
|
rights: str | None
|
|
|
|
email: str
|
|
|
|
|
|
|
|
|
2023-07-19 14:36:23 +03:00
|
|
|
@subscription_router.put(
|
|
|
|
"/brains/{brain_id}/subscription",
|
|
|
|
dependencies=[
|
|
|
|
Depends(AuthBearer()),
|
|
|
|
Depends(has_brain_authorization([RoleEnum.Owner, RoleEnum.Editor])),
|
|
|
|
],
|
|
|
|
)
|
2023-07-18 15:30:19 +03:00
|
|
|
def update_brain_subscription(
|
|
|
|
brain_id: UUID,
|
|
|
|
subscription: BrainSubscriptionUpdatableProperties,
|
2023-08-21 15:05:13 +03:00
|
|
|
current_user: UserIdentity = Depends(get_current_user),
|
2023-07-18 15:30:19 +03:00
|
|
|
):
|
|
|
|
user_email = subscription.email
|
|
|
|
if user_email == current_user.email:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You can't change your own permissions",
|
|
|
|
)
|
|
|
|
|
|
|
|
user_id = get_user_id_by_user_email(user_email)
|
2023-08-31 11:41:02 +03:00
|
|
|
|
|
|
|
if user_id is None:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=404,
|
|
|
|
detail="User not found",
|
|
|
|
)
|
|
|
|
|
2023-07-19 14:36:23 +03:00
|
|
|
# check if user is an editor but trying to give high level permissions
|
|
|
|
if subscription.rights == "Owner":
|
|
|
|
try:
|
|
|
|
validate_brain_authorization(
|
|
|
|
brain_id,
|
|
|
|
current_user.id,
|
|
|
|
RoleEnum.Owner,
|
|
|
|
)
|
|
|
|
except HTTPException:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You don't have the rights to give owner permissions",
|
|
|
|
)
|
|
|
|
|
|
|
|
# check if user is not an editor trying to update an owner right which is not allowed
|
2023-08-03 11:37:13 +03:00
|
|
|
current_invitation = get_brain_for_user(user_id, brain_id)
|
|
|
|
if current_invitation is not None and current_invitation.rights == "Owner":
|
2023-07-19 14:36:23 +03:00
|
|
|
try:
|
|
|
|
validate_brain_authorization(
|
|
|
|
brain_id,
|
|
|
|
current_user.id,
|
|
|
|
RoleEnum.Owner,
|
|
|
|
)
|
|
|
|
except HTTPException:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You can't change the permissions of an owner",
|
|
|
|
)
|
2023-07-18 15:30:19 +03:00
|
|
|
|
2023-07-19 18:13:02 +03:00
|
|
|
# removing user access from brain
|
2023-07-18 15:30:19 +03:00
|
|
|
if subscription.rights is None:
|
2023-07-19 18:13:02 +03:00
|
|
|
try:
|
|
|
|
# only owners can remove user access to a brain
|
|
|
|
validate_brain_authorization(
|
|
|
|
brain_id,
|
|
|
|
current_user.id,
|
|
|
|
RoleEnum.Owner,
|
|
|
|
)
|
2023-11-13 21:08:47 +03:00
|
|
|
delete_brain_user(user_id, brain_id)
|
2023-07-19 18:13:02 +03:00
|
|
|
except HTTPException:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You don't have the rights to remove user access",
|
|
|
|
)
|
2023-07-18 15:30:19 +03:00
|
|
|
else:
|
|
|
|
update_brain_user_rights(brain_id, user_id, subscription.rights)
|
|
|
|
|
|
|
|
return {"message": "Brain subscription updated successfully"}
|
2023-09-22 12:44:09 +03:00
|
|
|
|
|
|
|
|
|
|
|
@subscription_router.post(
|
|
|
|
"/brains/{brain_id}/subscribe",
|
|
|
|
tags=["Subscription"],
|
|
|
|
)
|
|
|
|
async def subscribe_to_brain_handler(
|
|
|
|
brain_id: UUID, current_user: UserIdentity = Depends(get_current_user)
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Subscribe to a public brain
|
|
|
|
"""
|
|
|
|
if not current_user.email:
|
|
|
|
raise HTTPException(status_code=400, detail="UserIdentity email is not defined")
|
|
|
|
|
|
|
|
brain = get_brain_by_id(brain_id)
|
|
|
|
|
|
|
|
if brain is None:
|
|
|
|
raise HTTPException(status_code=404, detail="Brain not found")
|
|
|
|
if brain.status != "public":
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You cannot subscribe to this brain without invitation",
|
|
|
|
)
|
|
|
|
# check if user is already subscribed to brain
|
|
|
|
user_brain = get_brain_for_user(current_user.id, brain_id)
|
|
|
|
if user_brain is not None:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You are already subscribed to this brain",
|
|
|
|
)
|
|
|
|
try:
|
|
|
|
create_brain_user(
|
|
|
|
user_id=current_user.id,
|
|
|
|
brain_id=brain_id,
|
|
|
|
rights=RoleEnum.Viewer,
|
|
|
|
is_default_brain=False,
|
|
|
|
)
|
|
|
|
except Exception as e:
|
|
|
|
raise HTTPException(status_code=400, detail=f"Error adding user to brain: {e}")
|
|
|
|
|
|
|
|
return {"message": "You have successfully subscribed to the brain"}
|
2023-09-25 15:22:59 +03:00
|
|
|
|
|
|
|
|
|
|
|
@subscription_router.post(
|
|
|
|
"/brains/{brain_id}/unsubscribe",
|
|
|
|
tags=["Subscription"],
|
|
|
|
)
|
|
|
|
async def unsubscribe_from_brain_handler(
|
|
|
|
brain_id: UUID, current_user: UserIdentity = Depends(get_current_user)
|
|
|
|
):
|
|
|
|
"""
|
|
|
|
Unsubscribe from a brain
|
|
|
|
"""
|
|
|
|
if not current_user.email:
|
|
|
|
raise HTTPException(status_code=400, detail="UserIdentity email is not defined")
|
|
|
|
|
|
|
|
brain = get_brain_by_id(brain_id)
|
|
|
|
|
|
|
|
if brain is None:
|
|
|
|
raise HTTPException(status_code=404, detail="Brain not found")
|
|
|
|
if brain.status != "public":
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You cannot subscribe to this brain without invitation",
|
|
|
|
)
|
|
|
|
# check if user is already subscribed to brain
|
|
|
|
user_brain = get_brain_for_user(current_user.id, brain_id)
|
|
|
|
if user_brain is None:
|
|
|
|
raise HTTPException(
|
|
|
|
status_code=403,
|
|
|
|
detail="You are not subscribed to this brain",
|
|
|
|
)
|
|
|
|
delete_brain_user(user_id=current_user.id, brain_id=brain_id)
|
|
|
|
|
|
|
|
return {"message": "You have successfully unsubscribed from the brain"}
|