Feat/shareable brains send link be (#599)

* 🗃️ new table for invitations to subscribe to brain

*  new BrainSubscription class

*  new subscription router

* 👽️ add RESEND_API_KEY to .env in BE

* 📦 add 'resend' lib to requirements

* ♻️ fix some stanGPT
This commit is contained in:
Zineb El Bachiri 2023-07-11 18:20:31 +02:00 committed by GitHub
parent 8749ffd0bd
commit f837a6e9b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 149 additions and 9 deletions

View File

@ -13,4 +13,7 @@ MAX_REQUESTS_NUMBER=200
PRIVATE=False
MODEL_PATH=./local_models/ggml-gpt4all-j-v1.3-groovy.bin
MODEL_N_CTX=1000
MODEL_N_BATCH=8
MODEL_N_BATCH=8
#RESEND
RESEND_API_KEY=

View File

@ -12,6 +12,7 @@ from routes.chat_routes import chat_router
from routes.crawl_routes import crawl_router
from routes.explore_routes import explore_router
from routes.misc_routes import misc_router
from routes.subscription_routes import subscription_router
from routes.upload_routes import upload_router
from routes.user_routes import user_router
@ -46,7 +47,7 @@ app.include_router(misc_router)
app.include_router(upload_router)
app.include_router(user_router)
app.include_router(api_key_router)
app.include_router(subscription_router)
@app.exception_handler(HTTPException)
async def http_exception_handler(_, exc):

View File

@ -20,16 +20,13 @@ class Brain(BaseModel):
max_tokens: Optional[int] = 256
max_brain_size: Optional[int] = int(os.getenv("MAX_BRAIN_SIZE", 0))
files: List[Any] = []
_commons: Optional[CommonsDep] = None
class Config:
arbitrary_types_allowed = True
@property
def commons(self) -> CommonsDep:
if not self._commons:
self.__class__._commons = common_dependencies()
return self._commons # pyright: ignore reportPrivateUsage=none
return common_dependencies()
@property
def brain_size(self):

View File

@ -0,0 +1,85 @@
import os
from typing import Optional
from uuid import UUID
import resend
from logger import get_logger
from models.settings import CommonsDep, common_dependencies
from pydantic import BaseModel
logger = get_logger(__name__)
class BrainSubscription(BaseModel):
brain_id: Optional[UUID] = None
inviter_email: Optional[str]
email: Optional[str]
rights: Optional[str]
class Config:
arbitrary_types_allowed = True
@property
def commons(self) -> CommonsDep:
return common_dependencies()
def create_subscription_invitation(self):
logger.info("Creating subscription invitation")
response = (
self.commons["supabase"]
.table("brain_subscription_invitations")
.insert({"brain_id": str(self.brain_id), "email": self.email, "rights": self.rights})
.execute()
)
return response.data
def update_subscription_invitation(self):
logger.info('Updating subscription invitation')
response = (
self.commons["supabase"]
.table("brain_subscription_invitations")
.update({"rights": self.rights})
.eq("brain_id", str(self.brain_id))
.eq("email", self.email)
.execute()
)
return response.data
def create_or_update_subscription_invitation(self):
response = self.commons["supabase"].table("brain_subscription_invitations").select("*").eq("brain_id", str(self.brain_id)).eq("email", self.email).execute()
if response.data:
response = self.update_subscription_invitation()
else:
response = self.create_subscription_invitation()
return response
def get_brain_url(self) -> str:
"""Generates the brain URL based on the brain_id."""
base_url = "https://www.quivr.app/chat"
return f"{base_url}?brain_subscription_invitation={self.brain_id}"
def resend_invitation_email(self):
resend.api_key = os.getenv("RESEND_API_KEY")
brain_url = self.get_brain_url()
html_body = f"""
<p>This brain has been shared with you by {self.inviter_email}.</p>
<p><a href='{brain_url}'>Click here</a> to access your brain.</p>
"""
try:
r = resend.Emails.send({
"from": "onboarding@resend.dev",
"to": self.email,
"subject": "Quivr - Brain Shared With You",
"html": html_body
})
print('Resend response', r)
except Exception as e:
logger.error(f"Error sending email: {e}")
return
return r

View File

@ -23,4 +23,5 @@ flake8==6.0.0
flake8-black==0.3.6
sentence_transformers>=2.0.0
sentry-sdk==1.26.0
pyright==1.1.316
pyright==1.1.316
resend==0.5.1

View File

@ -0,0 +1,25 @@
from typing import List
from uuid import UUID
from auth.auth_bearer import get_current_user
from fastapi import APIRouter, Depends, HTTPException
from models.brains_subscription_invitations import BrainSubscription
from models.users import User
subscription_router = APIRouter()
@subscription_router.post("/brain/{brain_id}/subscription")
async def invite_user_to_brain(brain_id: UUID, users: List[dict], current_user: User = Depends(get_current_user)):
# TODO: Ensure the current user has permissions to invite users to this brain
for user in users:
subscription = BrainSubscription(brain_id=brain_id, email=user['email'], rights=user['rights'], inviter_email=current_user.email or "Quivr")
try:
subscription.create_or_update_subscription_invitation()
subscription.resend_invitation_email()
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error inviting user: {e}")
return {"message": "Invitations sent successfully"}

View File

@ -0,0 +1,18 @@
BEGIN;
-- Create brain_subscription_invitations table if it doesn't exist
CREATE TABLE IF NOT EXISTS brain_subscription_invitations (
brain_id UUID,
email VARCHAR(255),
rights VARCHAR(255),
PRIMARY KEY (brain_id, email),
FOREIGN KEY (brain_id) REFERENCES Brains (brain_id)
);
INSERT INTO migrations (name)
SELECT '202307111517030_add_subscription_invitations_table'
WHERE NOT EXISTS (
SELECT 1 FROM migrations WHERE name = '202307111517030_add_subscription_invitations_table'
);
COMMIT;

View File

@ -158,13 +158,23 @@ CREATE TABLE IF NOT EXISTS brains_vectors (
FOREIGN KEY (brain_id) REFERENCES brains (brain_id)
);
-- Create brains X vectors table
CREATE TABLE IF NOT EXISTS brain_subscription_invitations (
brain_id UUID,
email VARCHAR(255),
rights VARCHAR(255),
PRIMARY KEY (brain_id, email),
FOREIGN KEY (brain_id) REFERENCES Brains (brain_id)
);
CREATE TABLE IF NOT EXISTS migrations (
name VARCHAR(255) PRIMARY KEY,
executed_at TIMESTAMPTZ DEFAULT current_timestamp
);
INSERT INTO migrations (name)
SELECT '20230629143400_add_file_sha1_brains_vectors'
SELECT '202307111517030_add_subscription_invitations_table'
WHERE NOT EXISTS (
SELECT 1 FROM migrations WHERE name = '20230629143400_add_file_sha1_brains_vectors'
SELECT 1 FROM migrations WHERE name = '202307111517030_add_subscription_invitations_table'
);