mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-15 09:32:22 +03:00
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:
parent
8749ffd0bd
commit
f837a6e9b9
@ -14,3 +14,6 @@ PRIVATE=False
|
||||
MODEL_PATH=./local_models/ggml-gpt4all-j-v1.3-groovy.bin
|
||||
MODEL_N_CTX=1000
|
||||
MODEL_N_BATCH=8
|
||||
|
||||
#RESEND
|
||||
RESEND_API_KEY=
|
@ -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):
|
||||
|
@ -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):
|
||||
|
85
backend/models/brains_subscription_invitations.py
Normal file
85
backend/models/brains_subscription_invitations.py
Normal 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
|
@ -24,3 +24,4 @@ flake8-black==0.3.6
|
||||
sentence_transformers>=2.0.0
|
||||
sentry-sdk==1.26.0
|
||||
pyright==1.1.316
|
||||
resend==0.5.1
|
25
backend/routes/subscription_routes.py
Normal file
25
backend/routes/subscription_routes.py
Normal 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"}
|
@ -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;
|
@ -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'
|
||||
);
|
Loading…
Reference in New Issue
Block a user