diff --git a/nextjs/src/actions/brains.ts b/nextjs/src/actions/brains.ts index 4b6a1ad3..82f9eda1 100644 --- a/nextjs/src/actions/brains.ts +++ b/nextjs/src/actions/brains.ts @@ -1,5 +1,5 @@ 'use server'; -import { DEFAULT_SORT, MODULE_ACTIONS, MODULES, REVALIDATE_TAG_NAME, ROLE_TYPE } from '@/utils/constant'; +import { DEFAULT_SORT, MODULE_ACTIONS, MODULES, REVALIDATE_TAG_NAME, ROLE_TYPE, BRAIN_ID_REQUIRED } from '@/utils/constant'; import { revalidateTagging, serverApi } from './serverApi'; import { getSessionUser } from '@/utils/handleAuth'; import { FormatUserType, ObjectType } from '@/types/common'; @@ -285,4 +285,36 @@ export const convertToSharedAction = async (brainId: string, data: { members?: O revalidateTagging(response, `${REVALIDATE_TAG_NAME.BRAIN}-${sessionUser.companyId}`), ]); return response; +}; + +export const leaveBrainAction = async (brainId: string) => { + const sessionUser = await getSessionUser(); + + if (!brainId) { + return { + status: 400, + code: 'ERROR', + message: BRAIN_ID_REQUIRED, + data: {} + }; + } + + if (!sessionUser?._id) { + return { + status: 401, + code: 'ERROR', + message: 'Authentication required', + data: {} + }; + } + + const response = await serverApi({ + action: MODULE_ACTIONS.UNSHARE, + parameters: [brainId], + data: { user_id: sessionUser._id } + }); + + await revalidateTagging(response, `${REVALIDATE_TAG_NAME.BRAIN}-${sessionUser.companyId}`); + + return response; }; \ No newline at end of file diff --git a/nextjs/src/components/Brains/BrainList.tsx b/nextjs/src/components/Brains/BrainList.tsx index 9ed21190..42f1a6ba 100644 --- a/nextjs/src/components/Brains/BrainList.tsx +++ b/nextjs/src/components/Brains/BrainList.tsx @@ -39,6 +39,7 @@ import Link from 'next/link'; import Image from 'next/image'; import ConvertToSharedModal from './ConvertToSharedModal'; import { ShareBrainIcon } from '@/icons/Share'; +import LeaveBrainButton from './LeaveBrainButton'; type DefaultEditOptionProps = { onEdit: () => void; @@ -53,6 +54,7 @@ type CommonListProps = { b: BrainListType; currentUser: SetUserData; closeSidebar: () => void; + onBrainLeave?: (brainId: string) => void; } type LinkItemsProps = { @@ -188,7 +190,6 @@ const DefaultEditOption = React.memo( className="edit-collapse-title" onClick={handleEditBrain} > - Manage @@ -199,7 +200,7 @@ const DefaultEditOption = React.memo( } ); -export const CommonList = ({ b, currentUser, closeSidebar }: CommonListProps) => { +export const CommonList = ({ b, currentUser, closeSidebar, onBrainLeave }: CommonListProps) => { const dispatch = useDispatch(); const router = useRouter(); @@ -209,7 +210,7 @@ export const CommonList = ({ b, currentUser, closeSidebar }: CommonListProps) => const [isEditing, setIsEditing] = useState(false); const [editedTitle, setEditedTitle] = useState(b.title); const inputRef = useRef(null); - const buttonRef=useRef(null) + const buttonRef = useRef(null); const [deleteBrain, isDeletePending] = useServerAction(deleteBrainAction); const [updateBrain, isUpdatePending] = useServerAction(updateBrainAction); const [showConvertModal, setShowConvertModal] = useState(false); @@ -227,6 +228,8 @@ export const CommonList = ({ b, currentUser, closeSidebar }: CommonListProps) => return getRandomCharacter(); }, [b?._id]); // Only changes if brain ID changes + const isOwner = b.user.id === currentUser._id; + const handleEditClick = () => { setIsEditing(true); setEditedTitle(b.title); // Reset editedTitle to the current title @@ -258,16 +261,16 @@ export const CommonList = ({ b, currentUser, closeSidebar }: CommonListProps) => const handleSaveClick = async () => { - if(b.title !==inputRef.current.value){ + if(b.title !== inputRef.current.value){ const payload = { title: editedTitle, isShare: b?.isShare, workspaceId: b?.workspaceId }; - const response:any=await updateBrain(payload, b?._id); + const response: any = await updateBrain(payload, b?._id); - if(response?.code=='ERROR'){ + if(response?.code == 'ERROR'){ setEditedTitle(b?.title) } setIsEditing(false) @@ -320,8 +323,7 @@ export const CommonList = ({ b, currentUser, closeSidebar }: CommonListProps) => return ( <> - - setShowConvertModal(false)} brain={b} @@ -347,8 +349,15 @@ export const CommonList = ({ b, currentUser, closeSidebar }: CommonListProps) => height={20} className="mr-2 flex-shrink-0 rounded collapsed-brain-logo" /> - ) : {b.title} - } + ) : ( + {b.title} + )} {isEditing ? ( ) : null} {b?.slug != `default-brain-${currentUser?._id}` && - b?.slug !== GENERAL_BRAIN_SLUG && - ((currentUser?.roleCode === ROLE_TYPE.USER && - b.user.id === currentUser?._id) || - currentUser?.roleCode !== ROLE_TYPE.USER) && ( - handleEditBrain(b)} - handleDeleteBrain={() => handleDeleteBrain(b)} - handleConvertToShared={!b.isShare ? handleConvertToShared : undefined} - isDeletePending={isDeletePending} - isPrivate={!b.isShare} - /> + b?.slug !== GENERAL_BRAIN_SLUG && ( + <> + {currentUser?.roleCode === ROLE_TYPE.USER && !isOwner && ( +
e.stopPropagation()}> + +
+ )} + {((currentUser?.roleCode === ROLE_TYPE.USER && isOwner) || + currentUser?.roleCode !== ROLE_TYPE.USER) && ( + handleEditBrain(b)} + handleDeleteBrain={() => handleDeleteBrain(b)} + handleConvertToShared={!b.isShare ? handleConvertToShared : undefined} + isDeletePending={isDeletePending} + isPrivate={!b.isShare} + /> + )} + )} diff --git a/nextjs/src/components/Brains/ConvertToSharedModal.tsx b/nextjs/src/components/Brains/ConvertToSharedModal.tsx index 11743af3..bd86ab71 100644 --- a/nextjs/src/components/Brains/ConvertToSharedModal.tsx +++ b/nextjs/src/components/Brains/ConvertToSharedModal.tsx @@ -21,6 +21,7 @@ import Toast from '@/utils/toast'; import { convertBrainToShared } from '@/lib/slices/brain/brainlist'; import { useForm } from 'react-hook-form'; import ValidationError from '@/widgets/ValidationError'; +import { CONVERT_TO_SHARED_SUCCESS, CONVERT_TO_SHARED_ERROR } from '@/utils/constant'; interface ConvertToSharedModalProps { open: boolean; @@ -116,20 +117,19 @@ const ConvertToSharedModal = ({ open, close, brain }: ConvertToSharedModalProps) })) || [], customInstruction, }; - + const response = await runAction(brain._id, payload); - + if (response?.code === 'SUCCESS') { - // Update Redux state dispatch(convertBrainToShared({ brainId: brain._id, convertedBrain: response.data })); - - Toast('Converted to Shared! Members and teams now have access.'); + + Toast(response?.message || CONVERT_TO_SHARED_SUCCESS); close(); } else { - Toast(response?.message || 'We couldn’t convert this brain. Please try again.'); + Toast(response?.message || CONVERT_TO_SHARED_ERROR); } } catch (error) { Toast('We couldn’t convert this brain. Please try again.'); @@ -150,7 +150,7 @@ const ConvertToSharedModal = ({ open, close, brain }: ConvertToSharedModalProps) Convert to Shared Brain - Convert "{brain?.title}" to a shared brain and invite team members to collaborate. + Convert "{brain?.title}" to a shared brain and invite team members to collaborate. This will make the brain accessible to selected users and teams. @@ -233,9 +233,9 @@ const ConvertToSharedModal = ({ open, close, brain }: ConvertToSharedModalProps)
-
- + {member.role == ROLE_TYPE.OWNER && ( {member.role} )} - + {(isRemoval && member.role != ROLE_TYPE.OWNER) && ( handleRemoveMember(member.user.id) @@ -387,8 +383,8 @@ const TeamItem = ({ team, handleRemoveTeam, brain }: any) => { return (
- - + +

{team.teamName} @@ -426,7 +422,7 @@ const TeamItem = ({ team, handleRemoveTeam, brain }: any) => {

{ - + handleRemoveTeam(team?.id?._id) }> @@ -437,7 +433,7 @@ const TeamItem = ({ team, handleRemoveTeam, brain }: any) => { ); }; -const EditBrainModal = ({ open, closeModal, brain }: any) => { +const EditBrainModal = ({ open, closeModal, brain }): any => { const currentUser = getCurrentUser(); const isOwner = currentUser?._id == brain?.user?.id; @@ -489,11 +485,11 @@ const EditBrainModal = ({ open, closeModal, brain }: any) => { role: ROLE_TYPE.ADMIN, }, ...brainMembers, - ].filter((m) => {return regex.test(m.user.email) && m.role!=ROLE_TYPE.ADMIN}) + ].filter((m) => { return regex.test(m.user.email) && m.role != ROLE_TYPE.ADMIN }) ); - - setTeamList(brainAddedTeam?.filter((currTeam)=>regex.test(currTeam.id.teamName))) + + setTeamList(brainAddedTeam?.filter((currTeam) => regex.test(currTeam.id.teamName))) }, [filter]); const refetchMemebrs = () => { @@ -517,7 +513,7 @@ const EditBrainModal = ({ open, closeModal, brain }: any) => { refetchMemebrs(); }; - const handleRemoveTeam = async(value) => { + const handleRemoveTeam = async (value) => { const response = await deleteShareTeamToBrain( brain?.workspaceId, brain?.companyId, @@ -528,7 +524,7 @@ const EditBrainModal = ({ open, closeModal, brain }: any) => { refetchTeams(); }; - const onLeaveBrain = () => {}; + const onLeaveBrain = () => { }; const onDeleteBrain = async () => { const data = { @@ -558,20 +554,20 @@ const EditBrainModal = ({ open, closeModal, brain }: any) => { setIsEditingInstruction(false); }; - const totalMembers = (brainAddedTeam, memberList ) => { - - if(brainAddedTeam?.length){ - - return brainAddedTeam?.reduce((acc, currTeam) => { - acc += currTeam?.id?.teamUsers?.length || 0; - return acc; - }, 0) + memberList?.length - } - else{ - return memberList.length - } - - + const totalMembers = (brainAddedTeam, memberList) => { + + if (brainAddedTeam?.length) { + + return brainAddedTeam?.reduce((acc, currTeam) => { + acc += currTeam?.id?.teamUsers?.length || 0; + return acc; + }, 0) + memberList?.length + } + else { + return memberList.length + } + + }; useEffect(() => { @@ -599,12 +595,12 @@ const EditBrainModal = ({ open, closeModal, brain }: any) => { /> {brain.title} -
- - Created By: - {`${displayName(brain?.user)} on ${dateDisplay( - brain?.createdAt - )}`} +
+ + Created By: + {`${displayName(brain?.user)} on ${dateDisplay( + brain?.createdAt + )}`}
{
)}
- + {brain.isShare && ( <>
@@ -688,107 +684,113 @@ const EditBrainModal = ({ open, closeModal, brain }: any) => {
- -
- {/* Add Member start */} - {((currentUser.roleCode === + {/* Add Member start */} + {((currentUser.roleCode === ROLE_TYPE.USER && brain?.user?.id === currentUser._id) || - currentUser.roleCode !== + currentUser.roleCode !== ROLE_TYPE.USER) && ( -
- - setAddMemberModal( - true - ) - } - > - + +
+ + setAddMemberModal( + true + ) + } + > + + + Add + Member + + + + + setAddTeamModal( + true + ) + } + > + + + Add a Team + + +
+
+ + + )} + {/* Add Member End */} +
+ +
+ Members{' '} + + {totalMembers( + teamList, + memberList + )} + +
+ +
+ + {/* Member List Start */} +
+ {memberList?.map((nm) => ( + - - Add Member - - - - - setAddTeamModal( - true - ) - } - > - ( + - - Add a Team - - + ))}
- )} - {/* Add Member End */} - -
- Members{' '} - - {totalMembers( - teamList, - memberList - )} - -
- -
- - {/* Member List Start */} -
- {memberList?.map((nm) => ( - - ))} - {teamList?.map((team) => ( - - ))} + {/* Member List End */}
- {/* Member List End */} -
)}
diff --git a/nextjs/src/components/Brains/LeaveBrainButton.tsx b/nextjs/src/components/Brains/LeaveBrainButton.tsx new file mode 100644 index 00000000..46ea1be6 --- /dev/null +++ b/nextjs/src/components/Brains/LeaveBrainButton.tsx @@ -0,0 +1,104 @@ +import React, { useState } from "react"; +import useLeaveBrain from "@/hooks/brains/useLeaveBrain"; +import ExitIcon from "@/icons/ExitIcon"; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogClose, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; + +interface LeaveBrainButtonProps { + brainId: string; + brainTitle: string; + onLeaveSuccess?: (brainId: string) => void; + buttonClassName?: string; + iconClassName?: string; + hideLabel?: boolean; +} + +const LeaveBrainButton: React.FC = ({ + brainId, + brainTitle, + onLeaveSuccess, + buttonClassName = "", + iconClassName = "", + hideLabel = false, +}) => { + const [open, setOpen] = useState(false); + const { leaveBrain, isLeaving } = useLeaveBrain({ onLeaveSuccess }); + + const handleLeave = () => { + leaveBrain(brainId); + }; + + return ( + <> +
setOpen(true)} + className={`cursor-pointer flex items-center gap-x-1 text-red hover:opacity-80 transition-opacity ${buttonClassName}`} + > + + {!hideLabel && Leave Brain} +
+ + + + + + + Leave Brain + + + +
+
+

+ Are you sure you want to leave +

+

+ "{brainTitle}"? +

+

+ You will lose access to all its content and conversations. +

+
+
+ + + + + + + +
+
+ + ); +}; + +export default LeaveBrainButton; \ No newline at end of file diff --git a/nextjs/src/components/Brains/ShareBrainList.tsx b/nextjs/src/components/Brains/ShareBrainList.tsx index afced3be..b05b9eb1 100644 --- a/nextjs/src/components/Brains/ShareBrainList.tsx +++ b/nextjs/src/components/Brains/ShareBrainList.tsx @@ -7,7 +7,7 @@ import { decryptedPersist } from '@/utils/helper'; import { WORKSPACE } from '@/utils/localstorage'; import { useDispatch, useSelector } from 'react-redux'; import { CommonList } from './BrainList'; -import { useMemo, useRef, useState, useEffect } from 'react'; +import { useMemo, useState, useEffect } from 'react'; import { AllBrainListType } from '@/types/brain'; import { WorkspaceListType } from '@/types/workspace'; import { useSidebar } from '@/context/SidebarContext'; @@ -25,65 +25,73 @@ const ShareBrainList = ({ brainList, workspaceFirst }: ShareBrainListProps) => { (store: RootState) => store.workspacelist.selected ); const currentUser = useMemo(() => getCurrentUser(), []); - const scrollRef = useRef(null); - const [isAtBottom, setIsAtBottom] = useState(false); + + const [leftBrains, setLeftBrains] = useState([]); + + useEffect(() => { + if (typeof window !== 'undefined') { + const stored = sessionStorage.getItem('leftBrains'); + if (stored) { + setLeftBrains(JSON.parse(stored)); + } + } + }, []); if (!selectedWorkSpace || !selectedWorkSpace._id) { const persistWorkspace = decryptedPersist(WORKSPACE); const setData = persistWorkspace ? persistWorkspace : workspaceFirst; - dispatch(setSelectedWorkSpaceAction(setData)); + if (setData) { + dispatch(setSelectedWorkSpaceAction(setData)); + } + return null; } const selectedWorkSpaceBrainList = brainList.find( (brain) => brain._id.toString() === selectedWorkSpace?._id?.toString() ); - const shareBrainList = selectedWorkSpaceBrainList?.brains.filter( - (brain) => brain.isShare + if (!selectedWorkSpaceBrainList) { + return null; + } + + const shareBrainList = selectedWorkSpaceBrainList.brains.filter( + (brain) => { + const isBrainOwner = brain.user.id === currentUser._id; + const userLeftThisBrain = leftBrains.includes(brain._id); + + return brain.isShare && (isBrainOwner || !userLeftThisBrain); + } ); - const dispatchPayload = shareBrainList ? shareBrainList : []; - const hasMoreBrains = shareBrainList && shareBrainList.length > 6; + const dispatchPayload = shareBrainList; + dispatch(cacheShareList(dispatchPayload)); - useEffect(() => { - const handleScroll = () => { - if (scrollRef.current) { - const { scrollTop, scrollHeight, clientHeight } = scrollRef.current; - const atBottom = scrollTop + clientHeight >= scrollHeight - 5; // 5px threshold - setIsAtBottom(atBottom); + const handleBrainLeave = (brainId: string) => { + const brainToLeave = selectedWorkSpaceBrainList.brains.find(b => b._id === brainId); + + if (brainToLeave && brainToLeave.user.id !== currentUser._id) { + const updatedLeftBrains = [...leftBrains, brainId]; + setLeftBrains(updatedLeftBrains); + + if (typeof window !== 'undefined') { + sessionStorage.setItem('leftBrains', JSON.stringify(updatedLeftBrains)); } - }; - - const scrollElement = scrollRef.current; - if (scrollElement) { - scrollElement.addEventListener('scroll', handleScroll); - // Check initial state - handleScroll(); } + }; - return () => { - if (scrollElement) { - scrollElement.removeEventListener('scroll', handleScroll); - } - }; - }, [shareBrainList]); - - dispatch(cacheShareList(dispatchPayload)); return ( <> {shareBrainList?.length > 0 && ( -
-
- {shareBrainList.map((b) => ( - - ))} -
- {hasMoreBrains && !isAtBottom &&
} +
+ {shareBrainList.map((b) => ( + + ))}
)} diff --git a/nextjs/src/hooks/brains/useLeaveBrain.ts b/nextjs/src/hooks/brains/useLeaveBrain.ts new file mode 100644 index 00000000..4851730a --- /dev/null +++ b/nextjs/src/hooks/brains/useLeaveBrain.ts @@ -0,0 +1,62 @@ +import { useState } from "react"; +import Toast from "@/utils/toast"; +import { leaveBrainAction } from "@/actions/brains"; + +export const useLeaveBrain = ({ + onLeaveSuccess, +}: { onLeaveSuccess?: (brainId: string) => void } = {}) => { + const [isLeaving, setIsLeaving] = useState(false); + const [leftBrains, setLeftBrains] = useState(() => { + if (typeof window !== "undefined") { + return JSON.parse(sessionStorage.getItem("leftBrains") || "[]"); + } + return []; + }); + + const leaveBrain = async (brainId: string) => { + setIsLeaving(true); + try { + const result = await leaveBrainAction(brainId); + + if (result?.status === 200) { + Toast(result.message); + + const updatedLeftBrains = [...leftBrains, brainId]; + setLeftBrains(updatedLeftBrains); + + if (typeof window !== "undefined") { + sessionStorage.setItem( + "leftBrains", + JSON.stringify(updatedLeftBrains) + ); + } + + onLeaveSuccess?.(brainId); + + setTimeout(() => { + window.location.href = "/"; + }, 1000); + } else { + throw new Error(result?.message); + } + } catch (error: any) { + Toast(error?.message); + } finally { + setIsLeaving(false); + } + }; + + const isBrainLeft = (brainId: string) => leftBrains.includes(brainId); + + const resetLeftBrain = (brainId: string) => { + const updatedLeftBrains = leftBrains.filter((id) => id !== brainId); + setLeftBrains(updatedLeftBrains); + if (typeof window !== "undefined") { + sessionStorage.setItem("leftBrains", JSON.stringify(updatedLeftBrains)); + } + }; + + return { leaveBrain, isLeaving, isBrainLeft, resetLeftBrain, leftBrains }; +}; + +export default useLeaveBrain; diff --git a/nextjs/src/utils/constant.ts b/nextjs/src/utils/constant.ts index 1bccfdf9..252f5940 100644 --- a/nextjs/src/utils/constant.ts +++ b/nextjs/src/utils/constant.ts @@ -84,6 +84,7 @@ export const MODULE_ACTIONS = { EXPORT: 'export', SIGNUP: 'register', SEND_MAIL: 'sendMail', + LEAVE: 'leave', FORGOT_PASSWORD: 'forgotPassword', RESET_PASSWORD: 'resetPassword', SEND_MAIL_NOTIFICATION: 'sendMailNotification', @@ -181,7 +182,12 @@ export const PASSWORD_REGEX_MESSAGE = 'Your password must contain at least one u export const EMAIL_REGEX_MESSAGE = 'Please enter your email address.'; export const ALREADY_PRESENT_EMAIL_MESSAGE = 'Email already exist'; export const STORAGE_INCREASE_REQUEST = 'Storage request received. Admin will contact you soon.'; -export const BRAIN_MEMBER_ADDED = 'Member has been successfully added to the brain'; +export const BRAIN_MEMBER_ADDED = 'Member has been successfully added to the brain'; +export const BRAIN_ID_REQUIRED = 'Brain ID is required'; +export const LEAVE_BRAIN_SUCCESS = 'You have successfully left the brain'; +export const LEAVE_BRAIN_ERROR = 'Unable to leave the brain. Please try again'; +export const CONVERT_TO_SHARED_SUCCESS = 'Brain converted to shared successfully'; +export const CONVERT_TO_SHARED_ERROR = 'Unable to convert brain to shared. Please try again'; export const FILE_SIZE_MESSAGE = 'Please upload less than 5 MB file'; export const PROFILE_IMG_SIZE_MESSAGE = 'Please upload less than 500 KB file'; export const API_KEY_MESSAGE = 'API key is required'; diff --git a/nodejs/resources/lang/en/common.json b/nodejs/resources/lang/en/common.json index 437fb6db..ee59970d 100644 --- a/nodejs/resources/lang/en/common.json +++ b/nodejs/resources/lang/en/common.json @@ -43,7 +43,10 @@ "unfavorite": "{module} removed from Favourites", "unfavoriteError": "Error occured while unfavoriting {module}", "credit_expired": "Your credit limit has been reached. Please contact your administrator to increase your credit limit.", - "trial_expired": "Your trial period has expired. Please upgrade to a paid plan to continue using our services." + "trial_expired": "Your trial period has expired. Please upgrade to a paid plan to continue using our services.", + "leave": "You have left the {module} successfully", + "leaveError": "Error occurred while leaving the {module}" + }, "import": { "not_found": "Import chat not found", diff --git a/nodejs/src/controller/web/brainController.js b/nodejs/src/controller/web/brainController.js index 40cae32b..c83d2f43 100644 --- a/nodejs/src/controller/web/brainController.js +++ b/nodejs/src/controller/web/brainController.js @@ -121,13 +121,13 @@ const workspaceWiseList = catchAsync(async (req, res) => { return util.recordNotFound(null, res); }) -const convertToShared = catchAsync(async (req, res) => { - const result = await brainService.convertToShared(req); +const leaveBrain = catchAsync(async (req, res) => { + const result = await brainService.leaveBrain(req); if (result) { - res.message = _localize('module.convertToShared', req, BRAIN); + res.message = _localize('module.leave', req, BRAIN); return util.successResponse(result, res); } - return util.failureResponse(_localize('module.convertToSharedError', req, BRAIN), res); + return util.failureResponse(_localize('module.leaveError', req, BRAIN), res); }) module.exports = { @@ -144,6 +144,6 @@ module.exports = { restoreBrain, deleteAllBrain, workspaceWiseList, - convertToShared + leaveBrain } diff --git a/nodejs/src/routes/web/brains.js b/nodejs/src/routes/web/brains.js index cc95a8ca..f2ac9b30 100644 --- a/nodejs/src/routes/web/brains.js +++ b/nodejs/src/routes/web/brains.js @@ -18,6 +18,6 @@ router.post('/share-doc', validate(shareDocKeys), authentication, brainControlle router.post('/share/list', authentication, checkPromptLimit, brainController.shareList); router.post('/restore/:id', authentication, brainController.restoreBrain); router.post('/list-all', authentication, checkPromptLimit, brainController.workspaceWiseList); -router.put('/convert-to-shared/:id', validate(convertToSharedKeys), authentication, checkPromptLimit, brainController.convertToShared); +router.post('/:id/leave', authentication, brainController.leaveBrain); module.exports = router; \ No newline at end of file diff --git a/nodejs/src/services/brain.js b/nodejs/src/services/brain.js index 9d2bf841..76d44af2 100644 --- a/nodejs/src/services/brain.js +++ b/nodejs/src/services/brain.js @@ -554,75 +554,45 @@ async function getBrainStatus(brains) { } } -const convertToShared = async (req) => { +const leaveBrain = async (req) => { try { - const { isPrivateBrainVisible } = req.user; - const brainId = req.params.id; - const { shareWith = [], teams = [], customInstruction } = req.body; + const { id: brainId } = req.params; + const userId = req.user.id; - if (!isPrivateBrainVisible) { - throw new Error(_localize('module.unAuthorized', req, 'Brain')); - } - - // Find the private brain and ensure ownership - const existingBrain = await Brain.findOne({ - _id: brainId, - 'user.id': req.userId, - isShare: false - }); - - if (!existingBrain) { + const brain = await Brain.findById(brainId); + if (!brain) { throw new Error(_localize('module.notFound', req, 'Brain')); } - // Check workspace access - const accessOfWorkspace = await accessOfWorkspaceToUser({ - workspaceId: existingBrain.workspaceId, - userId: req.user.id - }); - - if (!accessOfWorkspace) { - throw new Error(_localize('module.unAuthorized', req, 'Brain')); + if (brain.user.id.toString() === userId) { + throw new Error(_localize('brain.creatorCannotLeave', req)); } - // Convert to shared brain - const updateData = { - isShare: true, - ...(customInstruction !== undefined && { customInstruction }) - }; - - const updatedBrain = await Brain.findOneAndUpdate( - { _id: brainId }, - updateData, - { new: true } - ); - - // Handle owner's share record - ensure owner has access - await shareBrainFormat(req, updatedBrain); + const shareBrain = await ShareBrain.findOne({ + 'brain.id': brainId, + 'user.id': userId + }); - // Add members if provided - if (shareWith.length > 0) { - await Promise.all([ - shareBrainWithUser(shareWith, updatedBrain, req), - addWorkSpaceUsers(shareWith, { _id: updatedBrain.workspaceId }, req.user) - ]); + if (!shareBrain) { + throw new Error(_localize('brain.notMember', req)); } - // Add teams if provided - if (teams.length > 0) { - const workspace = await Workspace.findById(updatedBrain.workspaceId); - await Promise.all([ - addWorkSpaceTeam(teams, workspace, req.user), - addShareBrainTeam(teams, updatedBrain, req.user), - addBrainChatMember(updatedBrain, teams, req.user.id, true) - ]); - } + await ShareBrain.deleteOne({ + 'brain.id': brainId, + 'user.id': userId + }); - return updatedBrain; + await removeBrainChatMember(brainId, userId); + + return { + status: 200, + message: _localize('module.leave', req, 'Brain'), + data: {} + }; } catch (error) { - handleError(error, 'Error - convertToShared'); + handleError(error, 'Error - leaveBrain'); } -}; +} module.exports = { createBrain, @@ -646,6 +616,6 @@ module.exports = { getBrainStatus, getGeneralBrain, defaultGeneralBrainMember, - convertToShared, + leaveBrain } \ No newline at end of file