From 6d5bc8d0cee76d802c63e82f013210f865166eb6 Mon Sep 17 00:00:00 2001 From: mamadoudicko Date: Thu, 21 Sep 2023 15:10:16 +0200 Subject: [PATCH 1/7] feat: add public brain details modal --- .../library/components/PublicBrainItem.tsx | 26 ---------- .../PublicBrainItem/PublicBrainItem.tsx | 48 +++++++++++++++++++ .../components/PublicBrainItem/index.ts | 1 + .../library/components/index.ts | 1 + .../app/brains-management/library/page.tsx | 2 +- 5 files changed, 51 insertions(+), 27 deletions(-) delete mode 100644 frontend/app/brains-management/library/components/PublicBrainItem.tsx create mode 100644 frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx create mode 100644 frontend/app/brains-management/library/components/PublicBrainItem/index.ts create mode 100644 frontend/app/brains-management/library/components/index.ts diff --git a/frontend/app/brains-management/library/components/PublicBrainItem.tsx b/frontend/app/brains-management/library/components/PublicBrainItem.tsx deleted file mode 100644 index c0e708364726..000000000000 --- a/frontend/app/brains-management/library/components/PublicBrainItem.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { useTranslation } from "react-i18next"; -import { MdAdd } from "react-icons/md"; - -import Button from "@/lib/components/ui/Button"; -import { Brain } from "@/lib/context/BrainProvider/types"; - -type PublicBrainItemProps = { - brain: Brain; -}; - -export const PublicBrainItem = ({ - brain, -}: PublicBrainItemProps): JSX.Element => { - const { t } = useTranslation("brain"); - - return ( -
-

{brain.name}

-

{brain.description ?? ""}

- -
- ); -}; diff --git a/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx b/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx new file mode 100644 index 000000000000..4c9fd21d345e --- /dev/null +++ b/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx @@ -0,0 +1,48 @@ +import { useTranslation } from "react-i18next"; +import { MdAdd } from "react-icons/md"; + +import Button from "@/lib/components/ui/Button"; +import { Modal } from "@/lib/components/ui/Modal"; +import { Brain } from "@/lib/context/BrainProvider/types"; + +type PublicBrainItemProps = { + brain: Brain; +}; + +export const PublicBrainItem = ({ + brain, +}: PublicBrainItemProps): JSX.Element => { + const { t } = useTranslation("brain"); + + const subscribeButton = ( + + ); + + return ( + } + Trigger={ +
+

{brain.name}

+

+ {brain.description ?? ""} +

+ {subscribeButton} +
+ } + > +
+

{brain.name}

+

{brain.description ?? ""}

+ +

+ Last update: +

+
{subscribeButton}
+
+
+ ); +}; diff --git a/frontend/app/brains-management/library/components/PublicBrainItem/index.ts b/frontend/app/brains-management/library/components/PublicBrainItem/index.ts new file mode 100644 index 000000000000..6a535358d007 --- /dev/null +++ b/frontend/app/brains-management/library/components/PublicBrainItem/index.ts @@ -0,0 +1 @@ +export * from "./PublicBrainItem"; diff --git a/frontend/app/brains-management/library/components/index.ts b/frontend/app/brains-management/library/components/index.ts new file mode 100644 index 000000000000..6a535358d007 --- /dev/null +++ b/frontend/app/brains-management/library/components/index.ts @@ -0,0 +1 @@ +export * from "./PublicBrainItem"; diff --git a/frontend/app/brains-management/library/page.tsx b/frontend/app/brains-management/library/page.tsx index f4d72de6dc48..f3d801af669f 100644 --- a/frontend/app/brains-management/library/page.tsx +++ b/frontend/app/brains-management/library/page.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next"; import Field from "@/lib/components/ui/Field"; -import { PublicBrainItem } from "./components/PublicBrainItem"; +import { PublicBrainItem } from "./components/PublicBrainItem/PublicBrainItem"; import { useBrainsLibrary } from "./hooks/useBrainsLibrary"; const BrainsLibrary = (): JSX.Element => { From e801e5bd1551b0b0d3c764dba6a58f5f1d81591c Mon Sep 17 00:00:00 2001 From: mamadoudicko Date: Thu, 21 Sep 2023 15:25:30 +0200 Subject: [PATCH 2/7] feat(brain): add subscription route --- backend/routes/subscription_routes.py | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/backend/routes/subscription_routes.py b/backend/routes/subscription_routes.py index d43b5d3865da..93e6056a7ab5 100644 --- a/backend/routes/subscription_routes.py +++ b/backend/routes/subscription_routes.py @@ -361,3 +361,45 @@ def update_brain_subscription( update_brain_user_rights(brain_id, user_id, subscription.rights) return {"message": "Brain subscription updated successfully"} + + +@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"} From f5a8b2fe62431424d91d883e14eb4d96b188d4a9 Mon Sep 17 00:00:00 2001 From: mamadoudicko Date: Thu, 21 Sep 2023 16:27:03 +0200 Subject: [PATCH 3/7] feat: activate subscription button --- .../PublicBrainItem/PublicBrainItem.tsx | 24 +++++++-- .../hooks/usePublicBrainItem.ts | 52 +++++++++++++++++++ .../library/hooks/useBrainsLibrary.tsx | 8 +-- frontend/lib/api/subscription/subscription.ts | 13 +++++ .../api/subscription/useSubscriptionApi.ts | 3 ++ frontend/public/locales/en/brain.json | 4 +- frontend/public/locales/es/brain.json | 4 +- frontend/public/locales/fr/brain.json | 4 +- frontend/public/locales/pt-br/brain.json | 4 +- frontend/public/locales/ru/brain.json | 4 +- frontend/public/locales/zh-cn/brain.json | 4 +- 11 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 frontend/app/brains-management/library/components/PublicBrainItem/hooks/usePublicBrainItem.ts diff --git a/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx b/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx index 4c9fd21d345e..70266a8c1690 100644 --- a/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx +++ b/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx @@ -3,19 +3,35 @@ import { MdAdd } from "react-icons/md"; import Button from "@/lib/components/ui/Button"; import { Modal } from "@/lib/components/ui/Modal"; -import { Brain } from "@/lib/context/BrainProvider/types"; +import { PublicBrain } from "@/lib/context/BrainProvider/types"; + +import { usePublicBrainItem } from "./hooks/usePublicBrainItem"; type PublicBrainItemProps = { - brain: Brain; + brain: PublicBrain; }; export const PublicBrainItem = ({ brain, }: PublicBrainItemProps): JSX.Element => { + const { handleSubscribeToBrain, subscriptionRequestPending } = + usePublicBrainItem({ + brainId: brain.id, + }); + const { t } = useTranslation("brain"); const subscribeButton = ( - @@ -39,7 +55,7 @@ export const PublicBrainItem = ({

{brain.description ?? ""}

- Last update: + {t("public_brain_last_update_label")}:

{subscribeButton}
diff --git a/frontend/app/brains-management/library/components/PublicBrainItem/hooks/usePublicBrainItem.ts b/frontend/app/brains-management/library/components/PublicBrainItem/hooks/usePublicBrainItem.ts new file mode 100644 index 000000000000..b38e198207fb --- /dev/null +++ b/frontend/app/brains-management/library/components/PublicBrainItem/hooks/usePublicBrainItem.ts @@ -0,0 +1,52 @@ +import { UUID } from "crypto"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { useSubscriptionApi } from "@/lib/api/subscription/useSubscriptionApi"; +import { getAxiosErrorParams } from "@/lib/helpers/getAxiosErrorParams"; +import { useToast } from "@/lib/hooks"; + +type UseSubscribeToBrainProps = { + brainId: UUID; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const usePublicBrainItem = ({ brainId }: UseSubscribeToBrainProps) => { + const { subscribeToBrain } = useSubscriptionApi(); + const [subscriptionRequestPending, setSubscriptionRequestPending] = + useState(false); + const { publish } = useToast(); + + const { t } = useTranslation("brain"); + const handleSubscribeToBrain = async () => { + try { + setSubscriptionRequestPending(true); + await subscribeToBrain(brainId); + publish({ + text: t("public_brain_subscription_success_message"), + variant: "success", + }); + } catch (e) { + const error = getAxiosErrorParams(e); + if (error !== undefined) { + publish({ + text: error.message, + variant: "danger", + }); + + return; + } + publish({ + text: JSON.stringify(error), + variant: "danger", + }); + } finally { + setSubscriptionRequestPending(false); + } + }; + + return { + handleSubscribeToBrain, + subscriptionRequestPending, + }; +}; diff --git a/frontend/app/brains-management/library/hooks/useBrainsLibrary.tsx b/frontend/app/brains-management/library/hooks/useBrainsLibrary.tsx index 4ffddf43420d..9ad5d6350b47 100644 --- a/frontend/app/brains-management/library/hooks/useBrainsLibrary.tsx +++ b/frontend/app/brains-management/library/hooks/useBrainsLibrary.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from "react"; import { PUBLIC_BRAINS_KEY } from "@/lib/api/brain/config"; import { useBrainApi } from "@/lib/api/brain/useBrainApi"; -import { Brain } from "@/lib/context/BrainProvider/types"; +import { PublicBrain } from "@/lib/context/BrainProvider/types"; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const useBrainsLibrary = () => { @@ -14,9 +14,9 @@ export const useBrainsLibrary = () => { queryFn: getPublicBrains, }); - const [displayingPublicBrains, setDisplayingPublicBrains] = useState( - [] - ); + const [displayingPublicBrains, setDisplayingPublicBrains] = useState< + PublicBrain[] + >([]); useEffect(() => { setDisplayingPublicBrains(publicBrains); diff --git a/frontend/lib/api/subscription/subscription.ts b/frontend/lib/api/subscription/subscription.ts index 36a898883753..8c4baad03c74 100644 --- a/frontend/lib/api/subscription/subscription.ts +++ b/frontend/lib/api/subscription/subscription.ts @@ -54,3 +54,16 @@ export const getInvitation = async ( role: invitation.rights, }; }; + +export const subscribeToBrain = async ( + brainId: UUID, + axiosInstance: AxiosInstance +): Promise<{ message: string }> => { + const subscribedToBrain = ( + await axiosInstance.post<{ message: string }>( + `/brains/${brainId}/subscribe` + ) + ).data; + + return subscribedToBrain; +}; diff --git a/frontend/lib/api/subscription/useSubscriptionApi.ts b/frontend/lib/api/subscription/useSubscriptionApi.ts index f5069698997f..f58937b048fe 100644 --- a/frontend/lib/api/subscription/useSubscriptionApi.ts +++ b/frontend/lib/api/subscription/useSubscriptionApi.ts @@ -6,6 +6,7 @@ import { acceptInvitation, declineInvitation, getInvitation, + subscribeToBrain, } from "./subscription"; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -19,5 +20,7 @@ export const useSubscriptionApi = () => { declineInvitation(brainId, axiosInstance), getInvitation: async (brainId: UUID) => getInvitation(brainId, axiosInstance), + subscribeToBrain: async (brainId: UUID) => + subscribeToBrain(brainId, axiosInstance), }; }; diff --git a/frontend/public/locales/en/brain.json b/frontend/public/locales/en/brain.json index 83e190fe90d7..eb5ae18b48fc 100644 --- a/frontend/public/locales/en/brain.json +++ b/frontend/public/locales/en/brain.json @@ -36,5 +36,7 @@ "cancel_set_brain_status_to_public": "No, keep it private", "brain_library_button_label":"Brains library", "public_brains_search_bar_placeholder":"Search public brains", - "public_brain_subscribe_button_label":"Subscribe" + "public_brain_subscribe_button_label":"Subscribe", + "public_brain_subscription_success_message":"You have successfully subscribed to the brain", + "public_brain_last_update_label":"Last update" } \ No newline at end of file diff --git a/frontend/public/locales/es/brain.json b/frontend/public/locales/es/brain.json index 984d7ca8439c..24120f8615f0 100644 --- a/frontend/public/locales/es/brain.json +++ b/frontend/public/locales/es/brain.json @@ -36,5 +36,7 @@ "cancel_set_brain_status_to_public": "No, mantenerlo privado", "brain_library_button_label": "Biblioteca de cerebros", "public_brains_search_bar_placeholder": "Buscar cerebros públicos", - "public_brain_subscribe_button_label": "Suscribirse" + "public_brain_subscribe_button_label": "Suscribirse", + "public_brain_subscription_success_message": "Te has suscrito con éxito al cerebro", + "public_brain_last_update_label": "Última actualización" } \ No newline at end of file diff --git a/frontend/public/locales/fr/brain.json b/frontend/public/locales/fr/brain.json index 2b0c9e756c64..e87c449d811b 100644 --- a/frontend/public/locales/fr/brain.json +++ b/frontend/public/locales/fr/brain.json @@ -36,5 +36,7 @@ "cancel_set_brain_status_to_public": "Non, le garder privé", "brain_library_button_label": "Bibliothèque des cerveaux", "public_brains_search_bar_placeholder": "Rechercher des cerveaux publics", - "public_brain_subscribe_button_label": "S'abonner" + "public_brain_subscribe_button_label": "S'abonner", + "public_brain_subscription_success_message": "Vous vous êtes abonné avec succès au cerveau", + "public_brain_last_update_label": "Dernière mise à jour" } \ No newline at end of file diff --git a/frontend/public/locales/pt-br/brain.json b/frontend/public/locales/pt-br/brain.json index 2f2d2082ead7..534a98ac0a1f 100644 --- a/frontend/public/locales/pt-br/brain.json +++ b/frontend/public/locales/pt-br/brain.json @@ -36,5 +36,7 @@ "cancel_set_brain_status_to_public": "Não, mantê-lo privado", "brain_library_button_label": "Biblioteca de cérebros", "public_brains_search_bar_placeholder": "Pesquisar cérebros públicos", - "public_brain_subscribe_button_label": "Inscrever-se" + "public_brain_subscribe_button_label": "Inscrever-se", + "public_brain_subscription_success_message": "Você se inscreveu com sucesso no cérebro", + "public_brain_last_update_label": "Última atualização" } \ No newline at end of file diff --git a/frontend/public/locales/ru/brain.json b/frontend/public/locales/ru/brain.json index 95eb98c303f8..8871f5ad740e 100644 --- a/frontend/public/locales/ru/brain.json +++ b/frontend/public/locales/ru/brain.json @@ -36,5 +36,7 @@ "cancel_set_brain_status_to_public": "Нет, оставить приватным", "brain_library_button_label": "Библиотека мозгов", "public_brains_search_bar_placeholder": "Поиск общественных мозгов", - "public_brain_subscribe_button_label": "Подписаться" + "public_brain_subscribe_button_label": "Подписаться", + "public_brain_subscription_success_message": "Вы успешно подписались на мозг", + "public_brain_last_update_label": "Последнее обновление" } \ No newline at end of file diff --git a/frontend/public/locales/zh-cn/brain.json b/frontend/public/locales/zh-cn/brain.json index 42515d26b772..29a917a2ffa0 100644 --- a/frontend/public/locales/zh-cn/brain.json +++ b/frontend/public/locales/zh-cn/brain.json @@ -36,5 +36,7 @@ "cancel_set_brain_status_to_public": "不,保持私密", "brain_library_button_label": "大脑库", "public_brains_search_bar_placeholder": "搜索公共大脑", - "public_brain_subscribe_button_label": "订阅" + "public_brain_subscribe_button_label": "订阅", + "public_brain_subscription_success_message": "Вы успешно подписались на мозг", + "public_brain_last_update_label": "Последнее обновление" } \ No newline at end of file From 7999e6f1fc5542061dcbbc4b9bf845614b8a7563 Mon Sep 17 00:00:00 2001 From: mamadoudicko Date: Thu, 21 Sep 2023 17:05:33 +0200 Subject: [PATCH 4/7] feat: add last_update column to brain table --- backend/models/brain_entity.py | 1 + ...1160000_add_last_update_field_to_brain.sql | 23 +++++++++++++++++++ scripts/tables.sql | 4 ++-- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 scripts/20230921160000_add_last_update_field_to_brain.sql diff --git a/backend/models/brain_entity.py b/backend/models/brain_entity.py index a101af601076..dd36dc53bef3 100644 --- a/backend/models/brain_entity.py +++ b/backend/models/brain_entity.py @@ -15,6 +15,7 @@ class BrainEntity(BaseModel): openai_api_key: Optional[str] status: Optional[str] prompt_id: Optional[UUID] + last_update: str @property def id(self) -> UUID: diff --git a/scripts/20230921160000_add_last_update_field_to_brain.sql b/scripts/20230921160000_add_last_update_field_to_brain.sql new file mode 100644 index 000000000000..35888ee9fe7b --- /dev/null +++ b/scripts/20230921160000_add_last_update_field_to_brain.sql @@ -0,0 +1,23 @@ +-- Add last_update column to 'brains' table if it doesn't exist +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_name = 'brains' + AND column_name = 'last_update' + ) THEN + ALTER TABLE brains ADD COLUMN last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP; + END IF; +END +$$; + +-- Insert migration record if it doesn't exist +INSERT INTO migrations (name) +SELECT '20230921160000_add_last_update_field_to_brain' +WHERE NOT EXISTS ( + SELECT 1 FROM migrations WHERE name = '20230921160000_add_last_update_field_to_brain' +); + +-- Commit the changes +COMMIT; diff --git a/scripts/tables.sql b/scripts/tables.sql index 17bbcb2d45bb..fbc4f8a88eba 100644 --- a/scripts/tables.sql +++ b/scripts/tables.sql @@ -269,9 +269,9 @@ CREATE POLICY "Access Quivr Storage 1jccrwz_2" ON storage.objects FOR UPDATE TO CREATE POLICY "Access Quivr Storage 1jccrwz_3" ON storage.objects FOR DELETE TO anon USING (bucket_id = 'quivr'); INSERT INTO migrations (name) -SELECT '202309151054032_add_knowledge_tables' +SELECT '20230921160000_add_last_update_field_to_brain' WHERE NOT EXISTS ( - SELECT 1 FROM migrations WHERE name = '202309151054032_add_knowledge_tables' + SELECT 1 FROM migrations WHERE name = '20230921160000_add_last_update_field_to_brain' ); From f169ecbfbfcae6ed69b38cd06635c4ab3dbea24f Mon Sep 17 00:00:00 2001 From: mamadoudicko Date: Thu, 21 Sep 2023 17:11:40 +0200 Subject: [PATCH 5/7] feat: display last update on public brain details page --- backend/models/brain_entity.py | 1 + backend/models/databases/supabase/brains.py | 3 ++- .../library/components/PublicBrainItem/PublicBrainItem.tsx | 6 +++++- .../library/components/PublicBrainItem/utils/formatDate.ts | 2 ++ frontend/lib/context/BrainProvider/types.ts | 1 + 5 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 frontend/app/brains-management/library/components/PublicBrainItem/utils/formatDate.ts diff --git a/backend/models/brain_entity.py b/backend/models/brain_entity.py index dd36dc53bef3..528fe0ac836c 100644 --- a/backend/models/brain_entity.py +++ b/backend/models/brain_entity.py @@ -41,3 +41,4 @@ class PublicBrain(BaseModel): name: str description: Optional[str] number_of_subscribers: int = 0 + last_update: str diff --git a/backend/models/databases/supabase/brains.py b/backend/models/databases/supabase/brains.py index 5389d0848dff..59eadffe33e4 100644 --- a/backend/models/databases/supabase/brains.py +++ b/backend/models/databases/supabase/brains.py @@ -78,7 +78,7 @@ def get_user_brains(self, user_id) -> list[MinimalBrainEntity]: def get_public_brains(self) -> list[PublicBrain]: response = ( self.db.from_("brains") - .select("id:brain_id, name, description") + .select("id:brain_id, name, description, last_update") .filter("status", "eq", "public") .execute() ) @@ -88,6 +88,7 @@ def get_public_brains(self) -> list[PublicBrain]: id=item["id"], name=item["name"], description=item["description"], + last_update=item["last_update"], ) brain.number_of_subscribers = self.get_brain_subscribers_count(brain.id) public_brains.append(brain) diff --git a/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx b/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx index 70266a8c1690..a4cf06fa6196 100644 --- a/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx +++ b/frontend/app/brains-management/library/components/PublicBrainItem/PublicBrainItem.tsx @@ -6,6 +6,7 @@ import { Modal } from "@/lib/components/ui/Modal"; import { PublicBrain } from "@/lib/context/BrainProvider/types"; import { usePublicBrainItem } from "./hooks/usePublicBrainItem"; +import { formatDate } from "./utils/formatDate"; type PublicBrainItemProps = { brain: PublicBrain; @@ -55,7 +56,10 @@ export const PublicBrainItem = ({

{brain.description ?? ""}

- {t("public_brain_last_update_label")}: + + {t("public_brain_last_update_label")}: + {formatDate(brain.last_update)} +

{subscribeButton}
diff --git a/frontend/app/brains-management/library/components/PublicBrainItem/utils/formatDate.ts b/frontend/app/brains-management/library/components/PublicBrainItem/utils/formatDate.ts new file mode 100644 index 000000000000..afb6a01b06fd --- /dev/null +++ b/frontend/app/brains-management/library/components/PublicBrainItem/utils/formatDate.ts @@ -0,0 +1,2 @@ +export const formatDate = (date: string): string => + new Date(date).toLocaleDateString(); diff --git a/frontend/lib/context/BrainProvider/types.ts b/frontend/lib/context/BrainProvider/types.ts index ffc425e356f7..5a36ed73698f 100644 --- a/frontend/lib/context/BrainProvider/types.ts +++ b/frontend/lib/context/BrainProvider/types.ts @@ -38,6 +38,7 @@ export type PublicBrain = { name: string; description?: string; number_of_subscribers: number; + last_update: string; }; export type BrainContextType = ReturnType; From 8d45b4d8b6d8f132c08982dbc2795b1939282564 Mon Sep 17 00:00:00 2001 From: mamadoudicko Date: Thu, 21 Sep 2023 17:47:22 +0200 Subject: [PATCH 6/7] feat: change RBAC rule for public brains --- backend/routes/authorizations/brain_authorization.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/routes/authorizations/brain_authorization.py b/backend/routes/authorizations/brain_authorization.py index 45ccff45f6fc..e150cb55c5b1 100644 --- a/backend/routes/authorizations/brain_authorization.py +++ b/backend/routes/authorizations/brain_authorization.py @@ -5,6 +5,8 @@ from fastapi import Depends, HTTPException, status from models import UserIdentity from repository.brain import get_brain_for_user +from repository.brain.get_brain_details import get_brain_details + from routes.authorizations.types import RoleEnum @@ -43,6 +45,11 @@ def validate_brain_authorization( return: None """ + brain = get_brain_details(brain_id) + + if brain and brain.status == "public": + return + if required_roles is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, From da0caa64db8a72a343995b6690cd989864e9ce1c Mon Sep 17 00:00:00 2001 From: mamadoudicko Date: Thu, 21 Sep 2023 17:50:13 +0200 Subject: [PATCH 7/7] feat: maintain brain last_update time --- backend/celery_worker.py | 6 +++ backend/models/databases/supabase/brains.py | 5 ++ backend/repository/brain/update_brain.py | 9 +++- .../brain/update_brain_last_update_time.py | 8 ++++ .../BrainManagementTabs.tsx | 21 ++++++--- .../components/SettingsTab/SettingsTab.tsx | 47 ++++++++++--------- .../utils/isUserBrainOwner.ts | 19 ++++++++ 7 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 backend/repository/brain/update_brain_last_update_time.py create mode 100644 frontend/app/brains-management/[brainId]/components/BrainManagementTabs/utils/isUserBrainOwner.ts diff --git a/backend/celery_worker.py b/backend/celery_worker.py index 4769d96ab45f..c6ef9eba2502 100644 --- a/backend/celery_worker.py +++ b/backend/celery_worker.py @@ -10,6 +10,9 @@ from models.notifications import NotificationsStatusEnum from models.settings import get_supabase_client from parsers.github import process_github +from repository.brain.update_brain_last_update_time import ( + update_brain_last_update_time, +) from repository.notification.update_notification import update_notification_by_id from utils.processors import filter_file @@ -98,6 +101,8 @@ def process_file_and_notify( message=str(notification_message), ), ) + update_brain_last_update_time(brain_id) + return True @@ -158,4 +163,5 @@ def process_crawl_and_notify( message=str(notification_message), ), ) + update_brain_last_update_time(brain_id) return True diff --git a/backend/models/databases/supabase/brains.py b/backend/models/databases/supabase/brains.py index 59eadffe33e4..de9ea8f80393 100644 --- a/backend/models/databases/supabase/brains.py +++ b/backend/models/databases/supabase/brains.py @@ -94,6 +94,11 @@ def get_public_brains(self) -> list[PublicBrain]: public_brains.append(brain) return public_brains + def update_brain_last_update_time(self, brain_id: UUID) -> None: + self.db.table("brains").update({"last_update": "now()"}).match( + {"brain_id": brain_id} + ).execute() + def get_brain_for_user(self, user_id, brain_id) -> MinimalBrainEntity | None: response = ( self.db.from_("brains_users") diff --git a/backend/repository/brain/update_brain.py b/backend/repository/brain/update_brain.py index 89722c8002c0..467d59ad803f 100644 --- a/backend/repository/brain/update_brain.py +++ b/backend/repository/brain/update_brain.py @@ -3,9 +3,16 @@ from models import BrainEntity, get_supabase_db from models.databases.supabase.brains import BrainUpdatableProperties +from repository.brain.update_brain_last_update_time import update_brain_last_update_time + def update_brain_by_id(brain_id: UUID, brain: BrainUpdatableProperties) -> BrainEntity: """Update a prompt by id""" supabase_db = get_supabase_db() - return supabase_db.update_brain_by_id(brain_id, brain) # type: ignore + brain_update_answer = supabase_db.update_brain_by_id(brain_id, brain) + if brain_update_answer is None: + raise Exception("Brain not found") + + update_brain_last_update_time(brain_id) + return brain_update_answer diff --git a/backend/repository/brain/update_brain_last_update_time.py b/backend/repository/brain/update_brain_last_update_time.py new file mode 100644 index 000000000000..98945e3126fd --- /dev/null +++ b/backend/repository/brain/update_brain_last_update_time.py @@ -0,0 +1,8 @@ +from uuid import UUID + +from models.settings import get_supabase_db + + +def update_brain_last_update_time(brain_id: UUID): + supabase_db = get_supabase_db() + supabase_db.update_brain_last_update_time(brain_id) diff --git a/frontend/app/brains-management/[brainId]/components/BrainManagementTabs/BrainManagementTabs.tsx b/frontend/app/brains-management/[brainId]/components/BrainManagementTabs/BrainManagementTabs.tsx index 9f3bc287410d..87b361271488 100644 --- a/frontend/app/brains-management/[brainId]/components/BrainManagementTabs/BrainManagementTabs.tsx +++ b/frontend/app/brains-management/[brainId]/components/BrainManagementTabs/BrainManagementTabs.tsx @@ -1,12 +1,15 @@ +/* eslint-disable max-lines */ import { Content, List, Root } from "@radix-ui/react-tabs"; import { useTranslation } from "react-i18next"; import Button from "@/lib/components/ui/Button"; +import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { BrainTabTrigger, KnowledgeTab, PeopleTab } from "./components"; import ConfirmationDeleteModal from "./components/Modals/ConfirmationDeleteModal"; import { SettingsTab } from "./components/SettingsTab/SettingsTab"; import { useBrainManagementTabs } from "./hooks/useBrainManagementTabs"; +import { isUserBrainOwner } from "./utils/isUserBrainOwner"; export const BrainManagementTabs = (): JSX.Element => { const { t } = useTranslation(["translation", "config", "delete_brain"]); @@ -19,13 +22,21 @@ export const BrainManagementTabs = (): JSX.Element => { setIsDeleteModalOpen, brain, } = useBrainManagementTabs(); - - const isPubliclyAccessible = brain?.status === "public"; + const { allBrains } = useBrainContext(); if (brainId === undefined) { return
; } + const isCurrentUserBrainOwner = isUserBrainOwner({ + brainId, + userAccessibleBrains: allBrains, + }); + + const isPublicBrain = brain?.status === "public"; + + const hasEditRights = !isPublicBrain || isCurrentUserBrainOwner; + return ( { value="settings" onChange={setSelectedTab} /> - {!isPubliclyAccessible && ( + {hasEditRights && ( <> {
- {isPubliclyAccessible && ( + {isPublicBrain && ( {t("brain:public_brain_label")} @@ -80,7 +91,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => { isLoading={isSettingAsDefault} onClick={() => void setAsDefaultBrainHandler()} type="button" - disabled={isPubliclyAccessible} + disabled={!hasEditRights} > {t("setDefaultBrain", { ns: "brain" })} @@ -93,7 +104,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => { placeholder={t("brainDescriptionPlaceholder", { ns: "brain" })} autoComplete="off" className="flex-1 m-3" - disabled={isPubliclyAccessible} + disabled={!hasEditRights} {...register("description")} /> @@ -102,7 +113,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => { placeholder={t("openAiKeyPlaceholder", { ns: "config" })} autoComplete="off" className="flex-1" - disabled={isPubliclyAccessible} + disabled={!hasEditRights} {...register("openAiKey")} />
@@ -111,7 +122,7 @@ export const SettingsTab = ({ brainId }: SettingsTabProps): JSX.Element => {