From 8acc8fadb4884ae6bcc297e30d03033ce1aca6e6 Mon Sep 17 00:00:00 2001 From: Aayush <68517064+Aayush-Agnihotri@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:37:23 -0500 Subject: [PATCH] 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 --- migrations/update-users-model.js | 41 +++++++ .../update-flyers-model.js | 0 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, 281 insertions(+), 33 deletions(-) create mode 100644 migrations/update-users-model.js rename {migrations => past-migrations}/update-flyers-model.js (100%) 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/migrations/update-flyers-model.js b/past-migrations/update-flyers-model.js similarity index 100% rename from migrations/update-flyers-model.js rename to past-migrations/update-flyers-model.js 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); + }); +});