diff --git a/backend/src/API/coffeeChatAPI.ts b/backend/src/API/coffeeChatAPI.ts index d651328f3..46d32ec0a 100644 --- a/backend/src/API/coffeeChatAPI.ts +++ b/backend/src/API/coffeeChatAPI.ts @@ -138,6 +138,22 @@ export const clearAllCoffeeChats = async (user: IdolMember): Promise => { await CoffeeChatDao.clearAllCoffeeChats(); }; +/** + * Archives all coffee chats by setting the isArchived field to true. + * @param user - The user making the request. + * @throws PermissionError if the user does not have permissions. + */ +export const archiveCoffeeChats = async (user: IdolMember): Promise => { + const canArchive = await PermissionsManager.isLeadOrAdmin(user); + if (!canArchive) { + throw new PermissionError( + `User with email ${user.email} does not have permission to archive coffee chats.` + ); + } + + await CoffeeChatDao.archiveCoffeeChats(); +}; + /** * Gets the coffee chat bingo board */ diff --git a/backend/src/api.ts b/backend/src/api.ts index b23bd3ce2..45930ef00 100644 --- a/backend/src/api.ts +++ b/backend/src/api.ts @@ -43,7 +43,8 @@ import { checkMemberMeetsCategory, runAutoChecker, notifyMemberCoffeeChat, - getCoffeeChatSuggestions + getCoffeeChatSuggestions, + archiveCoffeeChats } from './API/coffeeChatAPI'; import { allSignInForms, @@ -204,6 +205,11 @@ const loginCheckedPut = ( handler: (req: Request, user: IdolMember) => Promise> ) => router.put(path, loginCheckedHandler(handler)); +const loginCheckedPatch = ( + path: string, + handler: (req: Request, user: IdolMember) => Promise> +) => router.patch(path, loginCheckedHandler(handler)); + // Members router.get('/member', async (req, res) => { const type = req.query.type as string | undefined; @@ -341,6 +347,11 @@ loginCheckedPut('/coffee-chat', async (req, user) => ({ coffeeChat: await updateCoffeeChat(req.body, user) })); +loginCheckedPatch('/coffee-chat/archive', async (_, user) => { + await archiveCoffeeChats(user); + return { message: 'All coffee chats archived successfully.' }; +}); + loginCheckedGet('/coffee-chat-bingo-board', async () => { const board = await getCoffeeChatBingoBoard(); return { board }; diff --git a/backend/src/dao/CoffeeChatDao.ts b/backend/src/dao/CoffeeChatDao.ts index cf6de09a7..6f770a08d 100644 --- a/backend/src/dao/CoffeeChatDao.ts +++ b/backend/src/dao/CoffeeChatDao.ts @@ -137,6 +137,23 @@ export default class CoffeeChatDao extends BaseDao { await deleteCollection(db, 'coffee-chats', 500); } + /** + * Archives all coffee chats by setting the isArchived field to true. + */ + static async archiveCoffeeChats(): Promise { + const coffeeChatsToDelete = await coffeeChatsCollection.where('isArchived', '==', true).get(); + const deletePromises = coffeeChatsToDelete.docs.map((doc) => doc.ref.delete()); + + const coffeeChatsToArchive = await coffeeChatsCollection.where('isArchived', '==', false).get(); + const batch = db.batch(); + + coffeeChatsToArchive.docs.forEach((doc) => { + batch.update(doc.ref, { isArchived: true }); + }); + + await Promise.all([Promise.all(deletePromises), batch.commit()]); + } + /** * Gets the coffee chat bingo board */ diff --git a/frontend/src/API/APIWrapper.ts b/frontend/src/API/APIWrapper.ts index 00b3d35ba..26aeaaa1d 100644 --- a/frontend/src/API/APIWrapper.ts +++ b/frontend/src/API/APIWrapper.ts @@ -44,6 +44,18 @@ export default class APIWrapper { .then((resOrErr) => this.responseMiddleware(resOrErr)); } + public static async patch( + url: string, + body: unknown = {}, + config = {} + ): Promise { + const idToken = await getUserIDTokenNonNull(); + return axios + .patch(url, body, { headers: { ...config, 'auth-token': idToken } }) + .catch((err: AxiosError) => err) + .then((resOrErr) => this.responseMiddleware(resOrErr)); + } + private static responseMiddleware( resOrErr: AxiosResponse | AxiosError ): APIProcessedResponse { diff --git a/frontend/src/API/CoffeeChatAPI.ts b/frontend/src/API/CoffeeChatAPI.ts index 0c219216f..ee279be42 100644 --- a/frontend/src/API/CoffeeChatAPI.ts +++ b/frontend/src/API/CoffeeChatAPI.ts @@ -38,6 +38,17 @@ export default class CoffeeChatAPI { await APIWrapper.delete(`${backendURL}/coffee-chat/${uuid}`); } + public static async archiveCoffeeChats(): Promise { + return APIWrapper.patch(`${backendURL}/coffee-chat/archive`).then((res) => { + if (res.data.error) { + Emitters.generalError.emit({ + headerMsg: "Couldn't archive coffee chats", + contentMsg: `Error: ${res.data.error}` + }); + } + }); + } + public static async getCoffeeChatBingoBoard(): Promise { const res = await APIWrapper.get(`${backendURL}/coffee-chat-bingo-board`).then( (res) => res.data diff --git a/frontend/src/components/Admin/CoffeeChats/CoffeeChats.tsx b/frontend/src/components/Admin/CoffeeChats/CoffeeChats.tsx index 0e460d125..99822e6d4 100644 --- a/frontend/src/components/Admin/CoffeeChats/CoffeeChats.tsx +++ b/frontend/src/components/Admin/CoffeeChats/CoffeeChats.tsx @@ -6,6 +6,7 @@ import styles from './CoffeeChats.module.css'; import CoffeeChatAPI from '../../../API/CoffeeChatAPI'; import { useMembers } from '../../Common/FirestoreDataProvider'; import CoffeeChatsBingoBoard from '../../Forms/CoffeeChatsForm/CoffeeChatsBingoBoard'; +import { Emitters } from '../../../utils'; const CoffeeChats: React.FC = () => { const [bingoBoard, setBingoBoard] = useState([[]]); @@ -62,6 +63,30 @@ const CoffeeChats: React.FC = () => { value: member.netid })); + const archiveAllCoffeeChats = async () => { + if ( + !window.confirm( + 'Are you sure you want to archive all coffee chats? This action cannot be undone.' + ) + ) { + return; + } + + try { + await CoffeeChatAPI.archiveCoffeeChats(); + Emitters.generalSuccess.emit({ + headerMsg: 'Success', + contentMsg: 'All coffee chats have been archived successfully!! :)' + }); + setIsLoading(true); + } catch (error) { + Emitters.generalError.emit({ + headerMsg: 'Error', + contentMsg: 'Failed to archive the coffee chats' + }); + } + }; + return (
@@ -124,6 +149,9 @@ const CoffeeChats: React.FC = () => { +