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_PATH=./local_models/ggml-gpt4all-j-v1.3-groovy.bin
|
||||||
MODEL_N_CTX=1000
|
MODEL_N_CTX=1000
|
||||||
MODEL_N_BATCH=8
|
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.crawl_routes import crawl_router
|
||||||
from routes.explore_routes import explore_router
|
from routes.explore_routes import explore_router
|
||||||
from routes.misc_routes import misc_router
|
from routes.misc_routes import misc_router
|
||||||
|
from routes.subscription_routes import subscription_router
|
||||||
from routes.upload_routes import upload_router
|
from routes.upload_routes import upload_router
|
||||||
from routes.user_routes import user_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(upload_router)
|
||||||
app.include_router(user_router)
|
app.include_router(user_router)
|
||||||
app.include_router(api_key_router)
|
app.include_router(api_key_router)
|
||||||
|
app.include_router(subscription_router)
|
||||||
|
|
||||||
@app.exception_handler(HTTPException)
|
@app.exception_handler(HTTPException)
|
||||||
async def http_exception_handler(_, exc):
|
async def http_exception_handler(_, exc):
|
||||||
|
@ -20,16 +20,13 @@ class Brain(BaseModel):
|
|||||||
max_tokens: Optional[int] = 256
|
max_tokens: Optional[int] = 256
|
||||||
max_brain_size: Optional[int] = int(os.getenv("MAX_BRAIN_SIZE", 0))
|
max_brain_size: Optional[int] = int(os.getenv("MAX_BRAIN_SIZE", 0))
|
||||||
files: List[Any] = []
|
files: List[Any] = []
|
||||||
_commons: Optional[CommonsDep] = None
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def commons(self) -> CommonsDep:
|
def commons(self) -> CommonsDep:
|
||||||
if not self._commons:
|
return common_dependencies()
|
||||||
self.__class__._commons = common_dependencies()
|
|
||||||
return self._commons # pyright: ignore reportPrivateUsage=none
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def brain_size(self):
|
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
|
sentence_transformers>=2.0.0
|
||||||
sentry-sdk==1.26.0
|
sentry-sdk==1.26.0
|
||||||
pyright==1.1.316
|
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)
|
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 (
|
CREATE TABLE IF NOT EXISTS migrations (
|
||||||
name VARCHAR(255) PRIMARY KEY,
|
name VARCHAR(255) PRIMARY KEY,
|
||||||
executed_at TIMESTAMPTZ DEFAULT current_timestamp
|
executed_at TIMESTAMPTZ DEFAULT current_timestamp
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT INTO migrations (name)
|
INSERT INTO migrations (name)
|
||||||
SELECT '20230629143400_add_file_sha1_brains_vectors'
|
SELECT '202307111517030_add_subscription_invitations_table'
|
||||||
WHERE NOT EXISTS (
|
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