From 18537bbf46fbc665a46deb12877667f7c7d8fbfb Mon Sep 17 00:00:00 2001 From: Vin Bui <75594943+vinnie4k@users.noreply.github.com> Date: Sat, 25 Nov 2023 00:43:50 -0500 Subject: [PATCH] Merge `main` to `release` (#127) * Add Jest testing (#63) * Initial boilerplate setup * Add ArticleFactory, TestingDBConnection, FactoryUtils, placeholder article test file * Add test cases on getAllArticles * Create PublicationFactory, add tests for getAllArticles, getArticlesByPublicationID(s), getArticlesByPublicationSlug(s) * Add hooks * Update README * Add pr changes * Add pr changes * Add tests for incrementShoutouts, searchArticle, getArticlesAfterDate * Add publication tests and refactor factory files * Remove test db open message * Update src/tests/article.test.ts Co-authored-by: Archit Mehta <4architmehta@gmail.com> * Update src/tests/data/ArticleFactory.ts Co-authored-by: Archit Mehta <4architmehta@gmail.com> * Refactor code * Refactor byDate Co-authored-by: Kidus Zegeye Co-authored-by: Archit Mehta <4architmehta@gmail.com> * Add Docstrings to Test Factory Functions (#64) * Initial boilerplate setup * Add ArticleFactory, TestingDBConnection, FactoryUtils, placeholder article test file * Add test cases on getAllArticles * Create PublicationFactory, add tests for getAllArticles, getArticlesByPublicationID(s), getArticlesByPublicationSlug(s) * Add hooks * Update README * Add pr changes * Add pr changes * Add tests for incrementShoutouts, searchArticle, getArticlesAfterDate * Add publication tests and refactor factory files * Remove test db open message * Update src/tests/article.test.ts Co-authored-by: Archit Mehta <4architmehta@gmail.com> * Update src/tests/data/ArticleFactory.ts Co-authored-by: Archit Mehta <4architmehta@gmail.com> * Refactor code * Refactor byDate * Add docstrings Co-authored-by: Kidus Zegeye Co-authored-by: Archit Mehta <4architmehta@gmail.com> * Update README.md * Implement community board models (#65) * Implemented community board models - implemented Flyer and Organization models for community board feature - modified User model - updated typescript version to 4.0.5 to resolve es lint issue * Revert package.json changes - reverted package.json changes that are addressed in a different pr * Add magazines to weekly debrief (#66) * Implemented community board models - implemented Flyer and Organization models for community board feature - modified User model - updated typescript version to 4.0.5 to resolve es lint issue * Implemented weekly debrief magazines - added magazines to weekly debrief feature - updated user and weekly debrief models to allow tracking of read magazines - updated logic in user and weekly debrief controllers to enable reading of magazines * Revert package.json changes - reverted package.json changes back to the original file - added additional rules to eslintrc to suppress es lint warnings * Create jest testing for magazines - added jest test cases for magazines * Implement jest testing for User - implemented unit testing for UserRepo - indirectly test Weekly Debrief with User unit tests * Update gitignore - updated gitignore file to include secrets folder * Implement reshuffle articles (#68) * Add Docstrings to Test Factory Functions (#64) * Initial boilerplate setup * Add ArticleFactory, TestingDBConnection, FactoryUtils, placeholder article test file * Add test cases on getAllArticles * Create PublicationFactory, add tests for getAllArticles, getArticlesByPublicationID(s), getArticlesByPublicationSlug(s) * Add hooks * Update README * Add pr changes * Add pr changes * Add tests for incrementShoutouts, searchArticle, getArticlesAfterDate * Add publication tests and refactor factory files * Remove test db open message * Update src/tests/article.test.ts Co-authored-by: Archit Mehta <4architmehta@gmail.com> * Update src/tests/data/ArticleFactory.ts Co-authored-by: Archit Mehta <4architmehta@gmail.com> * Refactor code * Refactor byDate * Add docstrings Co-authored-by: Kidus Zegeye Co-authored-by: Archit Mehta <4architmehta@gmail.com> Implement shuffle article function try to implement reshuffling with aggregate * Implement reshuffling of articles * Revert package.json, update eslint and update .gitignore to match main branch --------- Co-authored-by: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> * Implement magazine search (#69) * Implement magazine search - implemented searching for magazines * update magazinerepo * Add unit test for magazine search - created unit test for MagazineRepo.searchMagazine * Convert result of shuffling resolver to ArticleModel (#71) * Add 3 new publications (Cornell Daily Sun, Collective X, and Cornell Healthcare Review) to publications.js (#70) * Resolve merge conflicts in publications.json (keep release version) (#76) * Kidus/filter articles (#48) * Add covid filter to all article query functions * Add filtering capability * Fix getArticleById filter * Create filter migration script * Finish migration script * Add toggle for filtering enforcement * Implement PR changes Co-authored-by: Kidus Zegeye * Fix BRSN's url * Migrate bookmark resolver and update User Repo & Entity * Resolve remaining merge conflicts * Implement chronological sorting * Update prod deployment script * Remove unused MagazineURL index from Magazine DB * Add no rules to publications.json * Remove trailing comma --------- Co-authored-by: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Co-authored-by: Kidus Zegeye Co-authored-by: Archit404Error <4architmehta@gmail.com> * Revert "Resolve merge conflicts in publications.json (keep release version) (#76)" (#77) This reverts commit eccf00c0fe5ebbbe81bf9b1f643640d991cb1c29. * fix merge conflicts (#78) * Kidus/filter articles (#48) * Add covid filter to all article query functions * Add filtering capability * Fix getArticleById filter * Create filter migration script * Finish migration script * Add toggle for filtering enforcement * Implement PR changes Co-authored-by: Kidus Zegeye * Fix BRSN's url * Migrate bookmark resolver and update User Repo & Entity * Resolve remaining merge conflicts * Implement chronological sorting * Update prod deployment script * Remove unused MagazineURL index from Magazine DB * Add no rules to publications.json * Remove trailing comma --------- Co-authored-by: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Co-authored-by: Kidus Zegeye Co-authored-by: Archit404Error <4architmehta@gmail.com> * Implement community board [1/7] (#81) * Begin implementing FlyerRepo - implemented basic FlyerRepo functions - updated Flyer model to support uploading in app instead of through a google form * Implement flyers for community board - alphabetized imports and fields - finished implementing flyers for community board * Address pr review comments - addressed pr review comments - removed redundant filtering in FlyerRepo - added checks for null return values in FlyerRepo - alphabetized imports and exports * Add imageURL field to Magazine entity. (#73) * Add no rules to volume-backend's publications.json (#82) * Implement community board (flyer tests) [2/7] (#84) * Begin implementing FlyerRepo - implemented basic FlyerRepo functions - updated Flyer model to support uploading in app instead of through a google form * Implement flyers for community board - alphabetized imports and fields - finished implementing flyers for community board * Implement jest testing for flyers - created jest unit testing for FlyerRepo - created FlyerFactory to help with jest testing * Remove isFiltered field from Flyer entity - removed redundant isFiltered field from Flyer entity (already checked in microservice) - updated FlyerRepo and jest testing to reflect changes * Alphabetize imports and exports - alphabetized imports and exports in FlyerRepo, flyer.test, and FlyerFactory * Revert merge conflict changes - reverted merge conflict changes (accept incoming when actually wanted to keep current) * Address review comments - alphabetize imports - add unit test cases for FlyerRepo including searching for 0 flyers and maximum limit in response - added unit test case for FlyerRepo.getTrendingFlyers * Address review comments (Shungo) - added spaces between test cases * Implement community board organizations (#85) - resolve merge conflicts in Flyer.ts and FlyerFactory.ts - update Organization model - implement logic in OrganizationRepo - implement queries and mutations in OrganizationResolver * Implement community board (organization tests) [4/7] (#86) * Implement community board organizations - resolve merge conflicts in Flyer.ts and FlyerFactory.ts - update Organization model - implement logic in OrganizationRepo - implement queries and mutations in OrganizationResolver * Implement jest testing for organizations - implemented jest testing for community board organizations - created organizations.json with 3 onboarded organizations - added FlyerResolver and OrganizationResolver to app.ts * Implement community board (user queries/mutations) [5/7] (#87) * Kidus/filter articles (#48) * Add covid filter to all article query functions * Add filtering capability * Fix getArticleById filter * Create filter migration script * Finish migration script * Add toggle for filtering enforcement * Implement PR changes Co-authored-by: Kidus Zegeye * Fix BRSN's url * Migrate bookmark resolver and update User Repo & Entity * Resolve remaining merge conflicts * Implement chronological sorting * Update prod deployment script * Remove unused MagazineURL index from Magazine DB * Add no rules to publications.json * Remove trailing comma * Begin implementing FlyerRepo - implemented basic FlyerRepo functions - updated Flyer model to support uploading in app instead of through a google form * Implement basic community board features - finished implementing basic organization and flyer repos/resolvers - created jest testing for organization and flyer repos * Remove unnecessary OrganizationRepo function - removed content types route from organization repo and organization resolver * Implement user routes for community board - implemented user routes for community board to allow following orgs and reading flyers - added jest unit test cases for new UserRepo functions * Implement mutations for following and unfollowing organizations - added resolvers to UserResolver for following and unfollowing organizations - updated documentation in UserRepo - added import of FlyerRepo in app.ts * Address pr review comments - fix spelling * Address pr review comments - made style consistent in UserRepo by checking for if(user) in appendReadFlyer, appendReadArticle, and appendReadMagazine --------- Co-authored-by: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Co-authored-by: Kidus Zegeye Co-authored-by: Archit404Error <4architmehta@gmail.com> * Onboard organizations (#90) - add organizations from volume-microservice onto organizations.json * Implement community board (categories and org lists) [6/7] (#89) * Implement community board organizations - resolve merge conflicts in Flyer.ts and FlyerFactory.ts - update Organization model - implement logic in OrganizationRepo - implement queries and mutations in OrganizationResolver * Implement jest testing for organizations - implemented jest testing for community board organizations - created organizations.json with 3 onboarded organizations - added FlyerResolver and OrganizationResolver to app.ts * initial commit * Implement organizations as list for flyer model - updated flyer model to allow a list of organizations and organizationSlugs to be associated with each flyer - updated flyer and organization queries accordingly - added unit jest test cases and update old ones accordingly * Address pr review comments - removed redundant resolvers - updated getOrganizationByCategory test * Address pr review comments (kate) - updated docs in OrganizationResolver * Implement cboard start and end dates (#93) * Implement end dates - implemented start and end dates - changed shoutouts to times clicked - updated testing and documentation to reflect changes * Address pr review comments - removed async from getFlyersBeforeDate and getFlyersAfterDate - updated documentation in FactoryUtils * add incrementTimesClicked to resolver * update organization shoutouts query (#94) * Implement end dates - implemented start and end dates - changed shoutouts to times clicked - updated testing and documentation to reflect changes * Address pr review comments - removed async from getFlyersBeforeDate and getFlyersAfterDate - updated documentation in FactoryUtils * add incrementTimesClicked to resolver * Update organization clicks - changed shoutouts query to clicks in OrganizationResolver * Implement trending flyers logic (#95) - updated trending flyers logic - updated FlyerRepo - updated documentation in FlyerResolver * Fixed notification issue (#97) * Flyer Notifications + Org/Flyer Model Changes (#99) * Fixed notification issue * Added flyer notification * Added flyer notification * Updated Organization and Flyer models * Updated test cases * Inline notification calls * Improve Search Algorithm (#101) * Update README.md start instructions * Update search function for articles * Remove console log * Update articles search to use text search instead of regex * Update search for Flyers and Magazines * Update documentation, remove unused Fuse package * Fix README.md start docs * Filter out past flyers during flyers search * Fix formatting, improve concision * Remove start.md * Changed indexing method to being declared on the models * Update README.md Co-authored-by: Archit Mehta <4architmehta@gmail.com> --------- Co-authored-by: Archit Mehta <4architmehta@gmail.com> * getFlyersByCategorySlug Query (#100) * Fixed notification issue * Added flyer notification * Added flyer notification * Updated Organization and Flyer models * Updated test cases * Added getFlyersByCategorySlug query * Update query description * Inline notification calls * Update flyer.test.ts * Update flyer.test.ts * Merge `vin/create-flyer` to `main` (#104) * Fixed notification issue * Added flyer notification * Added flyer notification * Updated Organization and Flyer models * Updated test cases * Added getFlyersByCategorySlug query * Update query description * Inline notification calls * Add createFlyer mutation * `deleteFlyer` mutation (#103) * Add deleteFlyer mutation * Address PR comments * Update utils.ts * Update FlyerRepo.ts * Trendiness Update and Migration Script for New Flyers Model (#106) * Add migration script to update data based on new Flyers schema * Update Flyer trendiness * Fix comments * Clean up syntax for get trending flyers * Remove unnecessary createSpecificsByIndex function * Zach/trendiness and migration hot fix (#107) * Add migration script to update data based on new Flyers schema * Update Flyer trendiness * Fix comments * Clean up syntax for get trending flyers * Remove unnecessary createSpecificsByIndex function * Fix TypeScript compilation error * Micro PR for Flyers categorySlug migration (#108) * Add migration script to update data based on new Flyers schema * Update Flyer trendiness * Fix comments * Clean up syntax for get trending flyers * Remove unnecessary createSpecificsByIndex function * Fix TypeScript compilation error * Update migration script to account for category slug * Fix formatting for flyers test * Create `editFlyer` mutation (#105) * Add `editFlyer` * Address PR comments * `checkAccessCode` query for Organization authentication (#109) * Added Organization authentication * Updated Authorization * Implemented getAllFlyerCategories and tests (#110) * Implemented getAllFlyerCategories and tests * update jest * update jest * implemented suggestions from zach, vin, and archit * got rid of ESLint comment * fixed names for tests * Added loops to test cases * Fixed test case to create flyers with the same category * Changed 'push' to 'concat' --------- Co-authored-by: Cindy * Changing specifications for getFlyersBeforeDate and getFlyersAfterDate (#112) * Cindy/flyer categories (#113) * Implemented getAllFlyerCategories and tests * update jest * update jest * implemented suggestions from zach, vin, and archit * got rid of ESLint comment * fixed names for tests * Added loops to test cases * Fixed test case to create flyers with the same category * Changed 'push' to 'concat' * Updated `getAllFlyerCategories` --------- Co-authored-by: Cindy Co-authored-by: Vin Bui * Increased HTTP request size (#114) * Added limit to JSON request body * Update package-lock.json * Add error message (#116) * Added limit to JSON request body * Update package-lock.json * Added error message * Update FlyerMiddleware.ts (#119) * Update publications.json (#122) Add WoMENA as a publication * Change Create Flyer route to use form data for image upload (#121) * Add post express route using form data * Remove .idea * revert readme * try catch for large file uploads * Remove uncertain comments * Refactor upload image util to use form data * Cover the case where the Flyer ID that was sent is invalid * Update file key, add documentation * Fixed issue with edit flyer (#124) * Fixed issue with edit flyer * Update app.ts * Adding routes and migration script for bookmarking (#126) * Adding routes and migration script for bookmarking * Refactoring WeeklyDebrief * Fixing unbookmarking functions * Adding (un)bookmark articles tests * More article bookmark tests * Adding more tests for flyers and magazines --------- Co-authored-by: Kidus Zegeye <51487468+kidzegeye@users.noreply.github.com> Co-authored-by: Kidus Zegeye Co-authored-by: Archit Mehta <4architmehta@gmail.com> Co-authored-by: Shungo Najima Co-authored-by: Isaac Han Co-authored-by: Sasha Loayza <104698418+SashaLoayza@users.noreply.github.com> Co-authored-by: Zachary Seidner <58796478+zachseidner1@users.noreply.github.com> Co-authored-by: cindy-x-liang <67083541+cindy-x-liang@users.noreply.github.com> Co-authored-by: Cindy Co-authored-by: Aayush <68517064+Aayush-Agnihotri@users.noreply.github.com> Co-authored-by: Jennifer Gu <57200368+jjennifergu@users.noreply.github.com> --- migrations/update-users-model.js | 41 +++++++++ past-migrations/update-flyers-model.js | 39 +++++++++ src/entities/User.ts | 16 +++- src/entities/WeeklyDebrief.ts | 4 - src/repos/MagazineRepo.ts | 6 +- src/repos/UserRepo.ts | 110 ++++++++++++++++++++++++- src/repos/WeeklyDebriefRepo.ts | 2 - src/resolvers/UserResolver.ts | 48 ++++++++++- src/tests/user.test.ts | 87 ++++++++++++++++--- 9 files changed, 320 insertions(+), 33 deletions(-) create mode 100644 migrations/update-users-model.js create mode 100644 past-migrations/update-flyers-model.js diff --git a/migrations/update-users-model.js b/migrations/update-users-model.js new file mode 100644 index 0000000..fa7457e --- /dev/null +++ b/migrations/update-users-model.js @@ -0,0 +1,41 @@ +module.exports = { + async up(db) { + /** + * Adds the bookmarkedArticles, bookmarkedMagazines, and bookmarkedFlyers fields to all users. + * Removes the numBookmarkedArticles field from all users + */ + const users = await db.collection('users').find({}).toArray(); + users.map(async (user) => { + await db + .collection('users') + .updateOne( + { _id: user._id }, + { $set: { bookmarkedArticles: [], bookmarkedMagazines: [], bookmarkedFlyers: [] } }, + ); + await db + .collection('users') + .updateOne({ _id: user._id }, { $unset: { numBookmarkedArticles: '' } }); + return user; + }); + }, + + async down(db) { + /** + * Removes the bookmarkedArticles, bookmarkedMagazines, and bookmarkedFlyers fields from all users. + * Adds the numBookmarkedArticles field to all users + */ + const users = await db.collection('users').find({}).toArray(); + users.map(async (user) => { + await db + .collection('users') + .updateOne( + { _id: user._id }, + { $unset: { bookmarkedArticles: '', bookmarkedMagazines: '', bookmarkedFlyers: '' } }, + ); + await db + .collection('users') + .updateOne({ _id: user._id }, { $set: { numBookmarkedArticles: 0 } }); + return user; + }); + }, +}; diff --git a/past-migrations/update-flyers-model.js b/past-migrations/update-flyers-model.js new file mode 100644 index 0000000..dab9a54 --- /dev/null +++ b/past-migrations/update-flyers-model.js @@ -0,0 +1,39 @@ +module.exports = { + up(db) { + /** + * Updating flyers model to switch to singular organization object and + * singular organization slug + */ + return db.collection('flyers').updateMany({}, [ + { + $set: { + organization: { $first: '$organizations' }, + organizationSlug: { $first: '$organizationSlugs' }, + categorySlug: { $first: '$organizations.categorySlug' }, + }, + }, + { + $unset: ['organizations', 'organizationSlugs'], + }, + ]); + }, + + down(db) { + /** + * Reset organizations field to array of size 1 containing current flyer's + * organization. Resets organizationSlugs to an array of size 1 containing + * current flyer's organization slug. + */ + return db.collection('flyers').updateMany({}, [ + { + $set: { + organizations: ['$organization'], + organizationSlugs: ['$organizationSlug'], + }, + }, + { + $unset: ['organization', 'organizationSlug', 'categorySlug'], + }, + ]); + }, +}; diff --git a/src/entities/User.ts b/src/entities/User.ts index 43b3f7e..aa867ac 100644 --- a/src/entities/User.ts +++ b/src/entities/User.ts @@ -35,10 +35,6 @@ export class User { @Property({ default: 0 }) numShoutouts?: number; - @Field() - @Property({ default: 0 }) - numBookmarkedArticles?: number; - @Field((type) => [Article]) @Property({ required: true, type: () => Article, default: [] }) readArticles: mongoose.Types.DocumentArray>; @@ -59,6 +55,18 @@ export class User { @Property({ required: true, type: () => Organization, default: [] }) followedOrganizations: mongoose.Types.DocumentArray>; + @Field((type) => [Article]) + @Property({ required: true, type: () => Article, default: [] }) + bookmarkedArticles: mongoose.Types.DocumentArray>; + + @Field((type) => [Magazine]) + @Property({ required: true, type: () => Magazine, default: [] }) + bookmarkedMagazines: mongoose.Types.DocumentArray>; + + @Field((type) => [Flyer]) + @Property({ required: true, type: () => Flyer, default: [] }) + bookmarkedFlyers: mongoose.Types.DocumentArray>; + @Field({ nullable: true }) @Property() weeklyDebrief?: WeeklyDebrief; diff --git a/src/entities/WeeklyDebrief.ts b/src/entities/WeeklyDebrief.ts index b884afd..065613b 100644 --- a/src/entities/WeeklyDebrief.ts +++ b/src/entities/WeeklyDebrief.ts @@ -25,10 +25,6 @@ export default class WeeklyDebrief { @Property({ default: 0 }) numShoutouts?: number; - @Field() - @Property({ default: 0 }) - numBookmarkedArticles?: number; - @Field() @Property({ default: 0 }) numReadArticles?: number; diff --git a/src/repos/MagazineRepo.ts b/src/repos/MagazineRepo.ts index fe7a63e..f55402d 100644 --- a/src/repos/MagazineRepo.ts +++ b/src/repos/MagazineRepo.ts @@ -79,14 +79,14 @@ const getMagazinesByPublicationSlugs = async ( }); }; -function getMagazineByID(id: string) { - return MagazineModel.findById(id).then((magazine) => { +const getMagazineByID = async (id: string): Promise => { + return MagazineModel.findById(new ObjectId(id)).then((magazine) => { if (!isMagazineFiltered(magazine)) { return magazine; } return null; }); -} +}; function getMagazinesByIDs(ids: string[]) { return Promise.all(ids.map((id) => MagazineModel.findById(new ObjectId(id)))).then( diff --git a/src/repos/UserRepo.ts b/src/repos/UserRepo.ts index f687ede..a12fe4e 100644 --- a/src/repos/UserRepo.ts +++ b/src/repos/UserRepo.ts @@ -193,19 +193,116 @@ const incrementShoutouts = async (uuid: string): Promise => { }; /** - * Increment number of bookmarks in user's numBookmarkedArticles + * Add article to a user's bookmarkedArticles */ -const incrementBookmarks = async (uuid: string): Promise => { +const bookmarkArticle = async (uuid: string, articleID: string): Promise => { const user = await UserModel.findOne({ uuid }); if (user) { - user.numBookmarkedArticles++; + const article = await ArticleRepo.getArticleByID(articleID); + const checkDuplicates = (prev: boolean, cur: Article) => prev || cur.id === articleID; + + if (article && !user.bookmarkedArticles.reduce(checkDuplicates, false)) { + user.bookmarkedArticles.push(article); + } + + user.save(); + } + + return user; +}; + +/** + * Add magazine to a user's bookmarkedMagazines + */ +const bookmarkMagazine = async (uuid: string, magazineID: string): Promise => { + const user = await UserModel.findOne({ uuid }); + + if (user) { + const magazine = await MagazineRepo.getMagazineByID(magazineID); + const checkDuplicates = (prev: boolean, cur: Magazine) => prev || cur.id === magazineID; + + if (magazine && !user.bookmarkedMagazines.reduce(checkDuplicates, false)) { + user.bookmarkedMagazines.push(magazine); + } + + user.save(); + } + + return user; +}; + +/** + * Add flyer to a user's bookmarkedFlyers + */ +const bookmarkFlyer = async (uuid: string, flyerID: string): Promise => { + const user = await UserModel.findOne({ uuid }); + + if (user) { + const flyer = await FlyerRepo.getFlyerByID(flyerID); + const checkDuplicates = (prev: boolean, cur: Flyer) => prev || cur.id === flyerID; + + if (flyer && !user.bookmarkedFlyers.reduce(checkDuplicates, false)) { + user.bookmarkedFlyers.push(flyer); + } + user.save(); } return user; }; +/** + * Remove article from a user's bookmarkedArticles + */ +const unbookmarkArticle = async (uuid: string, articleID: string): Promise => { + const user = await UserModel.findOne({ uuid }); + if (user) { + const articleSlugs = user.bookmarkedArticles.map((article) => article.id); + const articleIndex = articleSlugs.indexOf(articleID); + + if (articleIndex === -1) return user; + + user.bookmarkedArticles.splice(articleIndex, 1); + return user.save(); + } + return user; +}; + +/** + * Remove magazine from a user's bookmarkedMagazines + */ +const unbookmarkMagazine = async (uuid: string, magazineID: string): Promise => { + const user = await UserModel.findOne({ uuid }); + if (user) { + const magazineSlugs = user.bookmarkedMagazines.map((magazine) => magazine.id); + const magazineIndex = magazineSlugs.indexOf(magazineID); + + if (magazineIndex === -1) return user; + + user.bookmarkedMagazines.splice(magazineIndex, 1); + return user.save(); + } + return user; +}; + +/** + * Remove flyer from a user's bookmarkedFlyers + */ +const unbookmarkFlyer = async (uuid: string, flyerID: string): Promise => { + const user = await UserModel.findOne({ uuid }); + if (user) { + const flyerSlugs = user.bookmarkedFlyers.map((flyer) => flyer.id); + const flyerIndex = flyerSlugs.indexOf(flyerID); + + if (flyerIndex === -1) return user; + + user.bookmarkedFlyers.splice(flyerIndex, 1); + return user.save(); + } + return user; +}; + export default { appendReadArticle, appendReadMagazine, @@ -217,7 +314,12 @@ export default { getUserByUUID, getUsersFollowingPublication, getUsersFollowingOrganization, - incrementBookmarks, + bookmarkArticle, + bookmarkMagazine, + bookmarkFlyer, + unbookmarkArticle, + unbookmarkMagazine, + unbookmarkFlyer, incrementShoutouts, unfollowPublication, }; diff --git a/src/repos/WeeklyDebriefRepo.ts b/src/repos/WeeklyDebriefRepo.ts index d314146..c90b8aa 100644 --- a/src/repos/WeeklyDebriefRepo.ts +++ b/src/repos/WeeklyDebriefRepo.ts @@ -22,7 +22,6 @@ const createWeeklyDebrief = async ( creationDate, expirationDate, numShoutouts: user.numShoutouts, - numBookmarkedArticles: user.numBookmarkedArticles, readArticles: user.readArticles.slice(0, 2), readMagazines: user.readMagazines.slice(0, 2), numReadArticles: user.readArticles.length, @@ -36,7 +35,6 @@ const createWeeklyDebrief = async ( readArticles: [], readMagazines: [], numShoutouts: 0, - numBookmarkedArticles: 0, weeklyDebrief, }, }, diff --git a/src/resolvers/UserResolver.ts b/src/resolvers/UserResolver.ts index 39f9253..614527d 100644 --- a/src/resolvers/UserResolver.ts +++ b/src/resolvers/UserResolver.ts @@ -84,10 +84,52 @@ class UserResolver { @Mutation((_returns) => User, { nullable: true, - description: 'Increments the number of bookmarks for the given by ', + description: 'Adds the
given by to the field', }) - async bookmarkArticle(@Arg('uuid') uuid: string) { - return await UserRepo.incrementBookmarks(uuid); + async bookmarkArticle(@Arg('uuid') uuid: string, @Arg('articleID') articleID: string) { + return await UserRepo.bookmarkArticle(uuid, articleID); + } + + @Mutation((_returns) => User, { + nullable: true, + description: + 'Adds the given by to the field', + }) + async bookmarkMagazine(@Arg('uuid') uuid: string, @Arg('magazineID') magazineID: string) { + return await UserRepo.bookmarkMagazine(uuid, magazineID); + } + + @Mutation((_returns) => User, { + nullable: true, + description: 'Adds the given by to the field', + }) + async bookmarkFlyer(@Arg('uuid') uuid: string, @Arg('flyerID') flyerID: string) { + return await UserRepo.bookmarkFlyer(uuid, flyerID); + } + + @Mutation((_returns) => User, { + nullable: true, + description: 'Removes the
given by from the ', + }) + async unbookmarkArticle(@Arg('uuid') uuid: string, @Arg('articleID') articleID: string) { + return await UserRepo.unbookmarkArticle(uuid, articleID); + } + + @Mutation((_returns) => User, { + nullable: true, + description: + 'Removes the given by from the ', + }) + async unbookmarkMagazine(@Arg('uuid') uuid: string, @Arg('magazineID') magazineID: string) { + return await UserRepo.unbookmarkMagazine(uuid, magazineID); + } + + @Mutation((_returns) => User, { + nullable: true, + description: 'Removes the given by from the ', + }) + async unbookmarkFlyer(@Arg('uuid') uuid: string, @Arg('flyerID') flyerID: string) { + return await UserRepo.unbookmarkFlyer(uuid, flyerID); } @Mutation((_returns) => [User], { diff --git a/src/tests/user.test.ts b/src/tests/user.test.ts index cee5e8a..f681478 100644 --- a/src/tests/user.test.ts +++ b/src/tests/user.test.ts @@ -169,19 +169,6 @@ describe('weekly debrief tests:', () => { expect(getUsersResponse.numShoutouts).toEqual(1); }); - test('incrementBookmarks - 1 user, 1 shoutout', async () => { - const users = await UserFactory.create(1); - const insertOutput = await UserModel.insertMany(users); - await UserRepo.incrementBookmarks(insertOutput[0].uuid); - - // update database - const pub = await PublicationFactory.getRandomPublication(); - await UserRepo.followPublication(users[0].uuid, pub.slug); - - const getUsersResponse = await UserRepo.getUserByUUID(insertOutput[0].uuid); - expect(getUsersResponse.numBookmarkedArticles).toEqual(1); - }); - test('appendReadFlyer', async () => { const users = await UserFactory.create(1); const flyers = await FlyerFactory.create(1); @@ -227,3 +214,77 @@ describe('weekly debrief tests:', () => { expect(getUsersResponse.readArticles).toHaveLength(1); }); }); + +describe('(un)bookmark tests:', () => { + test('bookmark articles - 1 user, 1 article', async () => { + const users = await UserFactory.create(1); + const articles = await ArticleFactory.create(1); + await UserModel.insertMany(users); + const insertOutput = await ArticleModel.insertMany(articles); + await UserRepo.bookmarkArticle(users[0].uuid, insertOutput[0].id); + + // update database + const pub = await PublicationFactory.getRandomPublication(); + await UserRepo.followPublication(users[0].uuid, pub.slug); + + const getUserResponse = await UserRepo.getUserByUUID(users[0].uuid); + expect(getUserResponse.bookmarkedArticles).toHaveLength(1); + }); + + test('unbookmark articles - 1 user, 1 article', async () => { + const users = await UserFactory.create(1); + const articles = await ArticleFactory.create(1); + await UserModel.insertMany(users); + const insertOutput = await ArticleModel.insertMany(articles); + await UserRepo.bookmarkArticle(users[0].uuid, insertOutput[0].id); + + // update database + const pub1 = await PublicationFactory.getRandomPublication(); + await UserRepo.followPublication(users[0].uuid, pub1.slug); + + await UserRepo.unbookmarkArticle(users[0].uuid, insertOutput[0].id); + + // update database + const pub2 = await PublicationFactory.getRandomPublication(); + await UserRepo.followPublication(users[0].uuid, pub2.slug); + + const getUserResponse = await UserRepo.getUserByUUID(users[0].uuid); + expect(getUserResponse.bookmarkedArticles).toHaveLength(0); + }); + + test('bookmark articles2 - 1 user, 1 article', async () => { + const users = await UserFactory.create(1); + const articles = await ArticleFactory.create(1); + await UserModel.insertMany(users); + const insertOutput = await ArticleModel.insertMany(articles); + await UserRepo.bookmarkArticle(users[0].uuid, insertOutput[0].id); + + // update database + const pub1 = await PublicationFactory.getRandomPublication(); + await UserRepo.followPublication(users[0].uuid, pub1.slug); + + await UserRepo.bookmarkArticle(users[0].uuid, insertOutput[0].id); + + // update database + const pub2 = await PublicationFactory.getRandomPublication(); + await UserRepo.followPublication(users[0].uuid, pub2.slug); + + const getUserResponse = await UserRepo.getUserByUUID(users[0].uuid); + expect(getUserResponse.bookmarkedArticles).toHaveLength(1); + }); + + test('unbookmark articles2 - 1 user, 1 article', async () => { + const users = await UserFactory.create(1); + const articles = await ArticleFactory.create(1); + await UserModel.insertMany(users); + const insertOutput = await ArticleModel.insertMany(articles); + + // update database + const pub = await PublicationFactory.getRandomPublication(); + await UserRepo.followPublication(users[0].uuid, pub.slug); + + await UserRepo.unbookmarkArticle(users[0].uuid, insertOutput[0].id); + const getUserResponse = await UserRepo.getUserByUUID(users[0].uuid); + expect(getUserResponse.bookmarkedArticles).toHaveLength(0); + }); +});