mirror of
https://github.com/StanGirard/quivr.git
synced 2024-08-16 16:40:40 +03:00
Feat/rate limiting (#719)
* feat: add max brain count * fix: prevent page cashing when invitation is invalid * feat: rename rights to role in frontend
This commit is contained in:
parent
b3455d3686
commit
d27504f735
@ -6,8 +6,10 @@ JWT_SECRET_KEY=<change-me>
|
||||
AUTHENTICATE=true
|
||||
GOOGLE_APPLICATION_CREDENTIALS=<change-me>
|
||||
GOOGLE_CLOUD_PROJECT=<change-me>
|
||||
|
||||
MAX_BRAIN_SIZE=52428800
|
||||
MAX_REQUESTS_NUMBER=200
|
||||
MAX_BRAIN_PER_USER=5
|
||||
|
||||
#Private LLM Variables
|
||||
PRIVATE=False
|
||||
|
@ -1,4 +1,3 @@
|
||||
import os
|
||||
from typing import Any, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
@ -6,7 +5,7 @@ from logger import get_logger
|
||||
from pydantic import BaseModel
|
||||
from utils.vectors import get_unique_files_from_vector_ids
|
||||
|
||||
from models.settings import CommonsDep, common_dependencies
|
||||
from models.settings import BrainRateLimiting, CommonsDep, common_dependencies
|
||||
from models.users import User
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@ -19,12 +18,16 @@ class Brain(BaseModel):
|
||||
model: Optional[str] = "gpt-3.5-turbo-0613"
|
||||
temperature: Optional[float] = 0.0
|
||||
max_tokens: Optional[int] = 256
|
||||
max_brain_size: Optional[int] = int(os.getenv("MAX_BRAIN_SIZE", 52428800))
|
||||
files: List[Any] = []
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
@property
|
||||
def max_brain_size(self) -> int:
|
||||
brain_rate_limiting = BrainRateLimiting()
|
||||
return brain_rate_limiting.max_brain_size
|
||||
|
||||
@property
|
||||
def commons(self) -> CommonsDep:
|
||||
return common_dependencies()
|
||||
|
@ -7,6 +7,11 @@ from supabase.client import Client, create_client
|
||||
from vectorstore.supabase import SupabaseVectorStore
|
||||
|
||||
|
||||
class BrainRateLimiting(BaseSettings):
|
||||
max_brain_size = 52428800
|
||||
max_brain_per_user = 5
|
||||
|
||||
|
||||
class BrainSettings(BaseSettings):
|
||||
openai_api_key: str
|
||||
anthropic_api_key: str
|
||||
|
@ -8,7 +8,7 @@ from models.brains import (
|
||||
get_default_user_brain,
|
||||
get_default_user_brain_or_create_new,
|
||||
)
|
||||
from models.settings import common_dependencies
|
||||
from models.settings import BrainRateLimiting, common_dependencies
|
||||
from models.users import User
|
||||
|
||||
from routes.authorizations.brain_authorization import has_brain_authorization
|
||||
@ -104,6 +104,15 @@ async def create_brain_endpoint(
|
||||
|
||||
brain = Brain(name=brain.name) # pyright: ignore reportPrivateUsage=none
|
||||
|
||||
user_brains = brain.get_user_brains(current_user.id)
|
||||
max_brain_per_user = BrainRateLimiting().max_brain_per_user
|
||||
|
||||
if len(user_brains) >= max_brain_per_user:
|
||||
raise HTTPException(
|
||||
status_code=429,
|
||||
detail=f"Maximum number of brains reached ({max_brain_per_user}).",
|
||||
)
|
||||
|
||||
brain.create_brain() # pyright: ignore reportPrivateUsage=none
|
||||
default_brain = get_default_user_brain(current_user)
|
||||
if default_brain:
|
||||
|
@ -4,6 +4,7 @@ import time
|
||||
from auth import AuthBearer, get_current_user
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from models.brains import Brain, get_default_user_brain
|
||||
from models.settings import BrainRateLimiting
|
||||
from models.users import User
|
||||
|
||||
user_router = APIRouter()
|
||||
@ -32,7 +33,8 @@ async def get_user_endpoint(
|
||||
information about the user's API usage.
|
||||
"""
|
||||
|
||||
max_brain_size = int(os.getenv("MAX_BRAIN_SIZE", 52428800))
|
||||
max_brain_size = BrainRateLimiting().max_brain_size
|
||||
|
||||
if request.headers.get("Openai-Api-Key"):
|
||||
max_brain_size = MAX_BRAIN_SIZE_WITH_OWN_KEY
|
||||
|
||||
|
@ -34,7 +34,7 @@ const DocumentItem = forwardRef(
|
||||
const { track } = useEventTracking();
|
||||
const { currentBrain } = useBrainContext();
|
||||
|
||||
const canDeleteFile = currentBrain?.rights === "Owner";
|
||||
const canDeleteFile = currentBrain?.role === "Owner";
|
||||
|
||||
if (!session) {
|
||||
throw new Error("User session not found");
|
||||
|
@ -17,7 +17,7 @@ export const useInvitation = () => {
|
||||
const brainId = params?.brainId as UUID | undefined;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [brainName, setBrainName] = useState<string>("");
|
||||
const [rights, setRights] = useState<BrainRoleType | undefined>();
|
||||
const [role, setRole] = useState<BrainRoleType | undefined>();
|
||||
const [isProcessingRequest, setIsProcessingRequest] = useState(false);
|
||||
|
||||
const { publish } = useToast();
|
||||
@ -37,9 +37,9 @@ export const useInvitation = () => {
|
||||
|
||||
const checkInvitationValidity = async () => {
|
||||
try {
|
||||
const { name, rights: assignedRights } = await getInvitation(brainId);
|
||||
const { name, role: assignedRole } = await getInvitation(brainId);
|
||||
setBrainName(name);
|
||||
setRights(assignedRights);
|
||||
setRole(assignedRole);
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||
publish({
|
||||
@ -130,7 +130,7 @@ export const useInvitation = () => {
|
||||
handleAccept,
|
||||
handleDecline,
|
||||
brainName,
|
||||
rights,
|
||||
role,
|
||||
isLoading,
|
||||
isProcessingRequest,
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ const InvitationPage = (): JSX.Element => {
|
||||
handleDecline,
|
||||
isLoading,
|
||||
brainName,
|
||||
rights,
|
||||
role,
|
||||
} = useInvitation();
|
||||
const { session } = useSupabase();
|
||||
|
||||
@ -27,15 +27,18 @@ const InvitationPage = (): JSX.Element => {
|
||||
redirectToLogin();
|
||||
}
|
||||
|
||||
if (rights === undefined) {
|
||||
throw new Error("Rights are undefined");
|
||||
if (role === undefined) {
|
||||
// This should never happen
|
||||
// It is a way to prevent the page from crashing when invitation is invalid instead of throwing an error
|
||||
// The user will be redirected to the home page (handled in the useInvitation hook)
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="pt-10">
|
||||
<PageHeading
|
||||
title={`Welcome to ${brainName}!`}
|
||||
subtitle={`You have been invited to join this brain as a ${rights} and start exploring. Do you accept this exciting journey?`}
|
||||
subtitle={`You have been invited to join this brain as a ${role} and start exploring. Do you accept this exciting journey?`}
|
||||
/>
|
||||
{isProcessingRequest ? (
|
||||
<div className="flex flex-col items-center justify-center mt-5">
|
||||
|
@ -35,7 +35,7 @@ const UploadPage = (): JSX.Element => {
|
||||
);
|
||||
}
|
||||
|
||||
const hasUploadRights = requiredRolesForUpload.includes(currentBrain.rights);
|
||||
const hasUploadRights = requiredRolesForUpload.includes(currentBrain.role);
|
||||
|
||||
if (!hasUploadRights) {
|
||||
return (
|
||||
@ -44,7 +44,7 @@ const UploadPage = (): JSX.Element => {
|
||||
<strong className="font-bold mr-1">Oh no!</strong>
|
||||
<span className="block sm:inline">
|
||||
{
|
||||
"You don't have the necessary rights to upload content to the selected brain. 🧠💡🥲"
|
||||
"You don't have the necessary role to upload content to the selected brain. 🧠💡🥲"
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -2,7 +2,8 @@
|
||||
import { renderHook } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { Subscription, SubscriptionUpdatableProperties } from "../brain";
|
||||
import { Subscription } from "../brain";
|
||||
import { SubscriptionUpdatableProperties } from "../types";
|
||||
import { useBrainApi } from "../useBrainApi";
|
||||
|
||||
const axiosGetMock = vi.fn(() => ({
|
||||
@ -89,6 +90,12 @@ describe("useBrainApi", () => {
|
||||
});
|
||||
|
||||
it("should call getBrains with the correct parameters", async () => {
|
||||
axiosGetMock.mockImplementationOnce(() => ({
|
||||
data: {
|
||||
//@ts-ignore we don't really need returned value here
|
||||
brains: [],
|
||||
},
|
||||
}));
|
||||
const {
|
||||
result: {
|
||||
current: { getBrains },
|
||||
@ -123,19 +130,26 @@ describe("useBrainApi", () => {
|
||||
const subscriptions: Subscription[] = [
|
||||
{
|
||||
email: "user@quivr.app",
|
||||
rights: "Viewer",
|
||||
role: "Viewer",
|
||||
},
|
||||
];
|
||||
await addBrainSubscriptions(id, subscriptions);
|
||||
|
||||
expect(axiosPostMock).toHaveBeenCalledTimes(1);
|
||||
expect(axiosPostMock).toHaveBeenCalledWith(
|
||||
`/brains/${id}/subscription`,
|
||||
subscriptions
|
||||
);
|
||||
expect(axiosPostMock).toHaveBeenCalledWith(`/brains/${id}/subscription`, [
|
||||
{
|
||||
email: "user@quivr.app",
|
||||
rights: "Viewer",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("should call getBrainUsers with the correct parameters", async () => {
|
||||
axiosGetMock.mockImplementationOnce(() => ({
|
||||
//@ts-ignore we don't really need returned value here
|
||||
data: [],
|
||||
}));
|
||||
|
||||
const {
|
||||
result: {
|
||||
current: { getBrainUsers },
|
||||
@ -156,13 +170,13 @@ describe("useBrainApi", () => {
|
||||
const brainId = "123";
|
||||
const email = "456";
|
||||
const subscription: SubscriptionUpdatableProperties = {
|
||||
rights: "Viewer",
|
||||
role: "Viewer",
|
||||
};
|
||||
await updateBrainAccess(brainId, email, subscription);
|
||||
expect(axiosPutMock).toHaveBeenCalledTimes(1);
|
||||
expect(axiosPutMock).toHaveBeenCalledWith(
|
||||
`/brains/${brainId}/subscription`,
|
||||
{ ...subscription, email }
|
||||
{ rights: "Viewer", email }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,9 +1,22 @@
|
||||
/* eslint-disable max-lines */
|
||||
import { AxiosInstance } from "axios";
|
||||
|
||||
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types";
|
||||
import { Brain, MinimalBrainForUser } from "@/lib/context/BrainProvider/types";
|
||||
import {
|
||||
BackendMinimalBrainForUser,
|
||||
Brain,
|
||||
MinimalBrainForUser,
|
||||
} from "@/lib/context/BrainProvider/types";
|
||||
import { Document } from "@/lib/types/Document";
|
||||
|
||||
import { SubscriptionUpdatableProperties } from "./types";
|
||||
import { mapBackendMinimalBrainToMinimalBrain } from "./utils/mapBackendMinimalBrainToMinimalBrain";
|
||||
import {
|
||||
BackendSubscription,
|
||||
mapSubscriptionToBackendSubscription,
|
||||
} from "./utils/mapSubscriptionToBackendSubscription";
|
||||
import { mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties } from "./utils/mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties";
|
||||
|
||||
export const getBrainDocuments = async (
|
||||
brainId: string,
|
||||
axiosInstance: AxiosInstance
|
||||
@ -19,11 +32,10 @@ export const createBrain = async (
|
||||
name: string,
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<MinimalBrainForUser> => {
|
||||
const createdBrain = (
|
||||
await axiosInstance.post<MinimalBrainForUser>(`/brains/`, { name })
|
||||
).data;
|
||||
|
||||
return createdBrain;
|
||||
return mapBackendMinimalBrainToMinimalBrain(
|
||||
(await axiosInstance.post<BackendMinimalBrainForUser>(`/brains/`, { name }))
|
||||
.data
|
||||
);
|
||||
};
|
||||
|
||||
export const getBrain = async (
|
||||
@ -47,40 +59,49 @@ export const deleteBrain = async (
|
||||
export const getDefaultBrain = async (
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<MinimalBrainForUser | undefined> => {
|
||||
return (await axiosInstance.get<MinimalBrainForUser>(`/brains/default/`))
|
||||
.data;
|
||||
return mapBackendMinimalBrainToMinimalBrain(
|
||||
(await axiosInstance.get<BackendMinimalBrainForUser>(`/brains/default/`))
|
||||
.data
|
||||
);
|
||||
};
|
||||
|
||||
export const getBrains = async (
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<MinimalBrainForUser[]> => {
|
||||
const brains = (
|
||||
await axiosInstance.get<{ brains: MinimalBrainForUser[] }>(`/brains/`)
|
||||
const { brains } = (
|
||||
await axiosInstance.get<{ brains: BackendMinimalBrainForUser[] }>(
|
||||
`/brains/`
|
||||
)
|
||||
).data;
|
||||
|
||||
return brains.brains;
|
||||
return brains.map(mapBackendMinimalBrainToMinimalBrain);
|
||||
};
|
||||
|
||||
export type Subscription = { email: string; rights: BrainRoleType };
|
||||
export type Subscription = { email: string; role: BrainRoleType };
|
||||
|
||||
export const addBrainSubscriptions = async (
|
||||
brainId: string,
|
||||
subscriptions: Subscription[],
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<void> => {
|
||||
await axiosInstance.post(`/brains/${brainId}/subscription`, subscriptions);
|
||||
await axiosInstance.post(
|
||||
`/brains/${brainId}/subscription`,
|
||||
subscriptions.map(mapSubscriptionToBackendSubscription)
|
||||
);
|
||||
};
|
||||
|
||||
export const getBrainUsers = async (
|
||||
brainId: string,
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<Subscription[]> => {
|
||||
return (await axiosInstance.get<Subscription[]>(`/brains/${brainId}/users`))
|
||||
.data;
|
||||
};
|
||||
const brainsUsers = (
|
||||
await axiosInstance.get<BackendSubscription[]>(`/brains/${brainId}/users`)
|
||||
).data;
|
||||
|
||||
export type SubscriptionUpdatableProperties = {
|
||||
rights: BrainRoleType | null;
|
||||
return brainsUsers.map((brainUser) => ({
|
||||
email: brainUser.email,
|
||||
role: brainUser.rights,
|
||||
}));
|
||||
};
|
||||
|
||||
export const updateBrainAccess = async (
|
||||
@ -90,7 +111,9 @@ export const updateBrainAccess = async (
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<void> => {
|
||||
await axiosInstance.put(`/brains/${brainId}/subscription`, {
|
||||
...subscription,
|
||||
...mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties(
|
||||
subscription
|
||||
),
|
||||
email: userEmail,
|
||||
});
|
||||
};
|
||||
|
5
frontend/lib/api/brain/types.ts
Normal file
5
frontend/lib/api/brain/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types";
|
||||
|
||||
export type SubscriptionUpdatableProperties = {
|
||||
role: BrainRoleType | null;
|
||||
};
|
@ -10,9 +10,9 @@ import {
|
||||
getBrainUsers,
|
||||
getDefaultBrain,
|
||||
Subscription,
|
||||
SubscriptionUpdatableProperties,
|
||||
updateBrainAccess,
|
||||
} from "./brain";
|
||||
import { SubscriptionUpdatableProperties } from "./types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainApi = () => {
|
||||
|
@ -0,0 +1,12 @@
|
||||
import {
|
||||
BackendMinimalBrainForUser,
|
||||
MinimalBrainForUser,
|
||||
} from "@/lib/context/BrainProvider/types";
|
||||
|
||||
export const mapBackendMinimalBrainToMinimalBrain = (
|
||||
backendMinimalBrain: BackendMinimalBrainForUser
|
||||
): MinimalBrainForUser => ({
|
||||
id: backendMinimalBrain.id,
|
||||
name: backendMinimalBrain.name,
|
||||
role: backendMinimalBrain.rights,
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types";
|
||||
|
||||
import { Subscription } from "../brain";
|
||||
|
||||
export type BackendSubscription = { email: string; rights: BrainRoleType };
|
||||
|
||||
export const mapSubscriptionToBackendSubscription = (
|
||||
subscription: Subscription
|
||||
): BackendSubscription => ({
|
||||
email: subscription.email,
|
||||
rights: subscription.role,
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
import { BrainRoleType } from "@/lib/components/NavBar/components/NavItems/components/BrainsDropDown/components/BrainActions/types";
|
||||
|
||||
import { SubscriptionUpdatableProperties } from "../types";
|
||||
|
||||
type BackendSubscriptionUpdatableProperties = {
|
||||
rights: BrainRoleType | null;
|
||||
};
|
||||
export const mapSubscriptionUpdatablePropertiesToBackendSubscriptionUpdatableProperties =
|
||||
(
|
||||
subscriptionUpdatableProperties: SubscriptionUpdatableProperties
|
||||
): BackendSubscriptionUpdatableProperties => ({
|
||||
rights: subscriptionUpdatableProperties.role,
|
||||
});
|
@ -13,8 +13,6 @@ export const acceptInvitation = async (
|
||||
)
|
||||
).data;
|
||||
|
||||
console.log("acceptedInvitation", acceptedInvitation);
|
||||
|
||||
return acceptedInvitation;
|
||||
};
|
||||
|
||||
@ -33,6 +31,11 @@ export const declineInvitation = async (
|
||||
|
||||
export type InvitationBrain = {
|
||||
name: string;
|
||||
role: BrainRoleType;
|
||||
};
|
||||
|
||||
//TODO: rename rights to role in Backend and use InvitationBrain instead of BackendInvitationBrain
|
||||
type BackendInvitationBrain = Omit<InvitationBrain, "role"> & {
|
||||
rights: BrainRoleType;
|
||||
};
|
||||
|
||||
@ -40,7 +43,14 @@ export const getInvitation = async (
|
||||
brainId: UUID,
|
||||
axiosInstance: AxiosInstance
|
||||
): Promise<InvitationBrain> => {
|
||||
return (
|
||||
await axiosInstance.get<InvitationBrain>(`/brains/${brainId}/subscription`)
|
||||
const invitation = (
|
||||
await axiosInstance.get<BackendInvitationBrain>(
|
||||
`/brains/${brainId}/subscription`
|
||||
)
|
||||
).data;
|
||||
|
||||
return {
|
||||
name: invitation.name,
|
||||
role: invitation.rights,
|
||||
};
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import axios from "axios";
|
||||
import { FormEvent, useState } from "react";
|
||||
import { MdAdd } from "react-icons/md";
|
||||
|
||||
@ -5,11 +6,12 @@ import Button from "@/lib/components/ui/Button";
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import { Modal } from "@/lib/components/ui/Modal";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
|
||||
export const AddBrainModal = (): JSX.Element => {
|
||||
const [newBrainName, setNewBrainName] = useState("");
|
||||
const [isPending, setIsPending] = useState(false);
|
||||
|
||||
const { publish } = useToast();
|
||||
const { createBrain } = useBrainContext();
|
||||
|
||||
const handleSubmit = async (e: FormEvent) => {
|
||||
@ -17,10 +19,31 @@ export const AddBrainModal = (): JSX.Element => {
|
||||
if (newBrainName.trim() === "" || isPending) {
|
||||
return;
|
||||
}
|
||||
setIsPending(true);
|
||||
await createBrain(newBrainName);
|
||||
setNewBrainName("");
|
||||
setIsPending(false);
|
||||
try {
|
||||
setIsPending(true);
|
||||
await createBrain(newBrainName);
|
||||
setNewBrainName("");
|
||||
} catch (err) {
|
||||
if (axios.isAxiosError(err) && err.response?.status === 429) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: `${JSON.stringify(
|
||||
(
|
||||
err.response as {
|
||||
data: { detail: string };
|
||||
}
|
||||
).data.detail
|
||||
)}`,
|
||||
});
|
||||
} else {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: `${JSON.stringify(err)}`,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setIsPending(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -12,7 +12,7 @@ const requiredAccessToShareBrain: BrainRoleType[] = ["Owner", "Editor"];
|
||||
export const BrainActions = ({ brain }: BrainActionsProps): JSX.Element => {
|
||||
return (
|
||||
<div className="absolute right-0 flex flex-row">
|
||||
{requiredAccessToShareBrain.includes(brain.rights) && (
|
||||
{requiredAccessToShareBrain.includes(brain.role) && (
|
||||
<ShareBrain brainId={brain.id} name={brain.name} />
|
||||
)}
|
||||
<DeleteBrain brainId={brain.id} />
|
||||
|
@ -39,7 +39,7 @@ vi.mock("@/lib/context/BrainProvider/hooks/useBrainContext", async () => {
|
||||
useBrainContext: () => ({
|
||||
...actual.useBrainContext(),
|
||||
currentBrain: {
|
||||
rights: "Editor",
|
||||
role: "Editor",
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
@ -23,7 +23,7 @@ export const BrainUsers = ({ brainId }: BrainUsersProps): JSX.Element => {
|
||||
<BrainUser
|
||||
key={subscription.email}
|
||||
email={subscription.email}
|
||||
rights={subscription.rights}
|
||||
role={subscription.role}
|
||||
brainId={brainId}
|
||||
fetchBrainUsers={fetchBrainUsers}
|
||||
/>
|
||||
|
@ -11,14 +11,14 @@ import { availableRoles } from "../../../../types";
|
||||
|
||||
type BrainUserProps = {
|
||||
email: string;
|
||||
rights: BrainRoleType;
|
||||
role: BrainRoleType;
|
||||
brainId: string;
|
||||
fetchBrainUsers: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const BrainUser = ({
|
||||
email,
|
||||
rights,
|
||||
role,
|
||||
brainId,
|
||||
fetchBrainUsers,
|
||||
}: BrainUserProps): JSX.Element => {
|
||||
@ -30,7 +30,7 @@ export const BrainUser = ({
|
||||
updateSelectedRole,
|
||||
} = useBrainUser({
|
||||
fetchBrainUsers: fetchBrainUsers,
|
||||
rights,
|
||||
role,
|
||||
brainId,
|
||||
email,
|
||||
});
|
||||
@ -62,7 +62,7 @@ export const BrainUser = ({
|
||||
onChange={(newRole) => void updateSelectedRole(newRole)}
|
||||
value={selectedRole}
|
||||
options={availableRoles}
|
||||
readOnly={currentBrain?.rights !== "Owner" && selectedRole === "Owner"}
|
||||
readOnly={currentBrain?.role !== "Owner" && selectedRole === "Owner"}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -9,7 +9,7 @@ import { BrainRoleType } from "../../../../../../../types";
|
||||
|
||||
type UseBrainUserProps = {
|
||||
fetchBrainUsers: () => Promise<void>;
|
||||
rights: BrainRoleType;
|
||||
role: BrainRoleType;
|
||||
brainId: string;
|
||||
email: string;
|
||||
};
|
||||
@ -17,19 +17,19 @@ type UseBrainUserProps = {
|
||||
export const useBrainUser = ({
|
||||
brainId,
|
||||
fetchBrainUsers,
|
||||
rights,
|
||||
role,
|
||||
email,
|
||||
}: UseBrainUserProps) => {
|
||||
const { updateBrainAccess } = useBrainApi();
|
||||
const { publish } = useToast();
|
||||
const [selectedRole, setSelectedRole] = useState<BrainRoleType>(rights);
|
||||
const [selectedRole, setSelectedRole] = useState<BrainRoleType>(role);
|
||||
const [isRemovingAccess, setIsRemovingAccess] = useState(false);
|
||||
const { currentBrain } = useBrainContext();
|
||||
const updateSelectedRole = async (newRole: BrainRoleType) => {
|
||||
setSelectedRole(newRole);
|
||||
try {
|
||||
await updateBrainAccess(brainId, email, {
|
||||
rights: newRole,
|
||||
role: newRole,
|
||||
});
|
||||
publish({ variant: "success", text: `Updated ${email} to ${newRole}` });
|
||||
void fetchBrainUsers();
|
||||
@ -58,7 +58,7 @@ export const useBrainUser = ({
|
||||
setIsRemovingAccess(true);
|
||||
try {
|
||||
await updateBrainAccess(brainId, email, {
|
||||
rights: null,
|
||||
role: null,
|
||||
});
|
||||
publish({ variant: "success", text: `Removed ${email} from brain` });
|
||||
void fetchBrainUsers();
|
||||
@ -82,7 +82,7 @@ export const useBrainUser = ({
|
||||
setIsRemovingAccess(false);
|
||||
}
|
||||
};
|
||||
const canRemoveAccess = currentBrain?.rights === "Owner";
|
||||
const canRemoveAccess = currentBrain?.role === "Owner";
|
||||
|
||||
return {
|
||||
isRemovingAccess,
|
||||
|
@ -20,7 +20,7 @@ export const UserToInvite = ({
|
||||
roleAssignation,
|
||||
}: UserToInviteProps): JSX.Element => {
|
||||
const [selectedRole, setSelectedRole] = useState<BrainRoleType>(
|
||||
roleAssignation.rights
|
||||
roleAssignation.role
|
||||
);
|
||||
const [email, setEmail] = useState(roleAssignation.email);
|
||||
const { currentBrain } = useBrainContext();
|
||||
@ -33,7 +33,7 @@ export const UserToInvite = ({
|
||||
onChange({
|
||||
...roleAssignation,
|
||||
email,
|
||||
rights: selectedRole,
|
||||
role: selectedRole,
|
||||
});
|
||||
}, [email, selectedRole]);
|
||||
|
||||
@ -60,7 +60,7 @@ export const UserToInvite = ({
|
||||
<Select
|
||||
onChange={setSelectedRole}
|
||||
value={selectedRole}
|
||||
options={userRoleToAssignableRoles[currentBrain.rights]}
|
||||
options={userRoleToAssignableRoles[currentBrain.role]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -2,6 +2,7 @@
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Subscription } from "@/lib/api/brain/brain";
|
||||
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
|
||||
@ -61,11 +62,11 @@ export const useShareBrain = (brainId: string) => {
|
||||
const inviteUsers = async (): Promise<void> => {
|
||||
setSendingInvitation(true);
|
||||
try {
|
||||
const inviteUsersPayload = roleAssignations
|
||||
const inviteUsersPayload: Subscription[] = roleAssignations
|
||||
.filter(({ email }) => email !== "")
|
||||
.map((assignation) => ({
|
||||
email: assignation.email,
|
||||
rights: assignation.rights,
|
||||
role: assignation.role,
|
||||
}));
|
||||
|
||||
await addBrainSubscriptions(brainId, inviteUsersPayload);
|
||||
|
@ -3,7 +3,7 @@ import { BrainRoleAssignation } from "../../../types";
|
||||
export const generateBrainAssignation = (): BrainRoleAssignation => {
|
||||
return {
|
||||
email: "",
|
||||
rights: "Viewer",
|
||||
role: "Viewer",
|
||||
id: Math.random().toString(),
|
||||
};
|
||||
};
|
||||
|
@ -4,6 +4,6 @@ export type BrainRoleType = (typeof roles)[number];
|
||||
|
||||
export type BrainRoleAssignation = {
|
||||
email: string;
|
||||
rights: BrainRoleType;
|
||||
role: BrainRoleType;
|
||||
id: string;
|
||||
};
|
||||
|
@ -18,6 +18,11 @@ export type Brain = {
|
||||
export type MinimalBrainForUser = {
|
||||
id: UUID;
|
||||
name: string;
|
||||
role: BrainRoleType;
|
||||
};
|
||||
|
||||
//TODO: rename rights to role in Backend and use MinimalBrainForUser instead of BackendMinimalBrainForUser
|
||||
export type BackendMinimalBrainForUser = Omit<MinimalBrainForUser, "role"> & {
|
||||
rights: BrainRoleType;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user