Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling notifications for adding, editing, and deleting a flyer #118

Merged
merged 20 commits into from
Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,6 @@ const main = async () => {
});

server.applyMiddleware({ app });

cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
async function setupWeeklyDebriefRefreshCron() {
// Refresh weekly debriefs and sent notifications once a week
cron.schedule('0 0 * * 0', async () => {
Expand Down
8 changes: 7 additions & 1 deletion src/repos/FlyerRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { OrganizationModel } from '../entities/Organization';
import utils from '../utils';

const { IS_FILTER_ACTIVE } = process.env;

export enum Actions {
Add,
Edit,
Delete,
}
function isFlyerFiltered(flyer: Flyer) {
if (IS_FILTER_ACTIVE === 'true') {
const filter = new Filter({ list: FILTERED_WORDS });
Expand Down Expand Up @@ -251,6 +255,7 @@ const createFlyer = async (
startDate,
title,
});

return FlyerModel.create(newFlyer);
};

Expand Down Expand Up @@ -303,6 +308,7 @@ const editFlyer = async (
// Update flyer fields (if not nul)
if (categorySlug) flyer.categorySlug = categorySlug;
if (endDate) flyer.endDate = new Date(endDate);

if (flyerURL) flyer.flyerURL = flyerURL;
if (imageURL) flyer.imageURL = imageURL;
if (location) flyer.location = location;
Expand Down
84 changes: 82 additions & 2 deletions src/repos/NotificationRepo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as admin from 'firebase-admin';
import { User } from '../entities/User';
import ArticleRepo from './ArticleRepo';
import FlyerRepo from './FlyerRepo';
import FlyerRepo, { Actions } from './FlyerRepo';
import MagazineRepo from './MagazineRepo';
import OrganizationRepo from './OrganizationRepo';
import PublicationRepo from './PublicationRepo';
Expand Down Expand Up @@ -114,10 +114,10 @@ const notifyNewMagazines = async (magazineIDs: string[]): Promise<void> => {
});
});
};

cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
/**
* Send notifications for new flyers posted by organizations.
*/

cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
const notifyNewFlyers = async (flyerIDs: string[]): Promise<void> => {
flyerIDs.forEach(async (f) => {
const flyer = await FlyerRepo.getFlyerByID(f); // eslint-disable-line
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you use eslint-disable-line, can you write a comment explaining why you need it? I believe this tends to be best practice since using comments like this to work around checkers is something you want to avoid generally.

Expand All @@ -137,6 +137,84 @@ const notifyNewFlyers = async (flyerIDs: string[]): Promise<void> => {
});
};

/**
* Send notifications for edited flyers or deleted flyers for those bookmarked by the user.
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
* @param flyerID ID of the flyer being added/changed/deleted
* @param bodyText a string containing the body of the notification
* @param action a string indicating the action being peformed, a for add, e for edit, d for delete
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
*/
const notifyFlyersForBookmarks = async (
flyerID: string,
bodyText: string,
action: Actions,
): Promise<void> => {
const flyer = await FlyerRepo.getFlyerByID(flyerID); // eslint-disable-line
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
const organization = await OrganizationRepo.getOrganizationBySlug(flyer.organizationSlug);
const followers = await UserRepo.getUsersBookmarkedFlyer(flyerID);
let notifTitle = '';
let notifBody = '';
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
followers.forEach(async (follower) => {
switch (action) {
case Actions.Edit:
notifTitle = `New Update from ${organization.name}!`;
notifBody = `${flyer.title} has ${bodyText}`;
break;
case Actions.Delete:
notifTitle = `${organization.name} Event Deleted`;
// the format for toDateString is Day of the Week Month Date Year ex: Tue Sep 05 2023
notifBody = `${flyer.title} on ${flyer.startDate.toDateString()} has been removed`;
break;
default:
notifTitle = '';
notifBody = '';
}

const uniqueData = {
flyerID: flyer.id,
flyerURL: flyer.flyerURL,
};

await sendNotif(follower, notifTitle, notifBody, uniqueData, 'flyer_notif');
});
};

/**
* Send notifications for new flyers by organizations followed by the user.
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
* @param flyerID ID of the flyer being added/changed/deleted
* @param bodyText a string containing the body of the notification
* @param action a string indicating the action being peformed, a for add, e for edit, d for delete
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
*/
const notifyFlyersForOrganizations = async (
flyerID: string,
bodyText: string,
action: Actions,
): Promise<void> => {
const flyer = await FlyerRepo.getFlyerByID(flyerID); // eslint-disable-line
const organization = await OrganizationRepo.getOrganizationBySlug(flyer.organizationSlug);
const followers = await UserRepo.getUsersFollowingOrganization(flyer.organizationSlug);
let notifTitle = '';
let notifBody = '';
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
followers.forEach(async (follower) => {
switch (action) {
case Actions.Add:
notifTitle = `New ${organization.name} Event!`;
// the format for toDateString is Day of the Week Month Date Year ex: Tue Sep 05 2023
notifBody = `${flyer.title} on ${flyer.startDate.toDateString()} at ${flyer.location}`;
break;
default:
notifTitle = '';
notifBody = '';
}

const uniqueData = {
flyerID: flyer.id,
flyerURL: flyer.flyerURL,
};

await sendNotif(follower, notifTitle, notifBody, uniqueData, 'flyer_notif');
});
};

/**
* Send notifications for weekly debrief release.
*/
Expand All @@ -153,5 +231,7 @@ export default {
notifyNewArticles,
notifyNewFlyers,
notifyNewMagazines,
notifyFlyersForBookmarks,
notifyFlyersForOrganizations,
notifyWeeklyDebrief,
};
10 changes: 10 additions & 0 deletions src/repos/UserRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ const getUsersFollowingOrganization = async (orgSlug: string): Promise<User[]> =
return matchedUsers;
};

/**
* Return all users who have a flyer bookmarked
*/
const getUsersBookmarkedFlyer = async (flyerID: string): Promise<User[]> => {
const matchedUsers = await UserModel.find({
bookmarkedFlyers: { $elemMatch: { _id: flyerID } },
});
return matchedUsers;
};
/**
* Add article to a user's readArticles
*/
Expand Down Expand Up @@ -314,6 +323,7 @@ export default {
getUserByUUID,
getUsersFollowingPublication,
getUsersFollowingOrganization,
getUsersBookmarkedFlyer,
bookmarkArticle,
bookmarkMagazine,
bookmarkFlyer,
Expand Down
39 changes: 36 additions & 3 deletions src/resolvers/FlyerResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {
import { Context } from 'vm';
import { Flyer } from '../entities/Flyer';
import { DEFAULT_LIMIT, DEFAULT_OFFSET } from '../common/constants';
import FlyerRepo from '../repos/FlyerRepo';
import FlyerRepo, { Actions } from '../repos/FlyerRepo';
import FlyerMiddleware from '../middlewares/FlyerMiddleware';
import NotificationRepo from '../repos/NotificationRepo';

@Resolver((_of) => Flyer)
class FlyerResolver {
Expand Down Expand Up @@ -192,7 +193,7 @@ class FlyerResolver {
@Arg('title') title: string,
@Ctx() ctx: Context,
) {
return FlyerRepo.createFlyer(
const flyer = await FlyerRepo.createFlyer(
categorySlug,
endDate,
flyerURL,
Expand All @@ -202,12 +203,20 @@ class FlyerResolver {
startDate,
title,
);
NotificationRepo.notifyFlyersForOrganizations(
flyer.id,
' just added a new flyer!',
Actions.Add,
);
return flyer;
}

@Mutation((_returns) => Flyer, {
description: `Delete a flyer with the id <id>.`,
})
async deleteFlyer(@Arg('id') id: string) {
const flyer = await FlyerRepo.getFlyerByID(id);
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved
NotificationRepo.notifyFlyersForBookmarks(flyer.id, ' just deleted a flyer', Actions.Delete);
return FlyerRepo.deleteFlyer(id);
}

Expand All @@ -228,7 +237,7 @@ class FlyerResolver {
@Arg('title', { nullable: true }) title: string,
@Ctx() ctx: Context,
) {
return FlyerRepo.editFlyer(
const flyer = await FlyerRepo.editFlyer(
id,
categorySlug,
endDate,
Expand All @@ -238,6 +247,30 @@ class FlyerResolver {
startDate,
title,
);
let editedResponse = '';
if ((endDate && location) || (location && startDate)) {
// if endDate and location or startDate and location are nonempty, notification body would return date and location changed
editedResponse = 'changed its date and location';
} else if (endDate && startDate) {
// if both endDate and startDate values changed, the notifcation body would just return that the event changed its date
editedResponse = 'changed its date';
} else if (endDate) {
// if only the endDate changed for the event, then the notification body would print out specifically what the date was changed to
const date = new Date(endDate);
// the format for toDateString is Day of the Week Month Date Year ex: Tue Sep 05 2023
editedResponse = `changed its end date to ${date.toDateString()}`;
} else if (location) {
// if only the location changed for the event, then the notification body would print out specifically what the location was changed to
editedResponse = `changed its location to ${location}`;
} else if (startDate) {
// if only the startDate changed for the event, then the notification body would print out specifically what the date was changed to
const date = new Date(startDate);
// the format for toDateString is Day of the Week Month Date Year ex: Tue Sep 05 2023
editedResponse = `changed its start date to ${date.toDateString()}`;
}
cindy-x-liang marked this conversation as resolved.
Show resolved Hide resolved

NotificationRepo.notifyFlyersForBookmarks(flyer.id, editedResponse, Actions.Edit);
return flyer;
}
}

Expand Down
7 changes: 7 additions & 0 deletions src/resolvers/OrganizationResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ class OrganizationResolver {
async clicks(@Root() organization: Organization): Promise<number> {
return OrganizationRepo.getClicks(organization);
}

@FieldResolver((_returns) => Number, {
description: "Returns the total times clicked of an <Organization's> <Flyers>",
})
async numFlyers(@Root() organization: Organization): Promise<number> {
return OrganizationRepo.getNumFlyers(organization);
}
}

export default OrganizationResolver;