From a05738c03d9f8e57bb869e189f76b02fa55ee2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20L=C3=A1szl=C3=B3?= <rlacko99@gmail.com> Date: Mon, 4 Jan 2021 21:12:17 +0100 Subject: [PATCH] Notices API --- src/index.ts | 14 --- src/middlewares/news/responseNews.ts | 2 +- src/middlewares/term/responseTermsList.ts | 2 +- .../user/notice/addNoticeToUser.ts | 34 +++++++ src/middlewares/user/notice/deleteNotice.ts | 23 +++++ .../user/notice/getUserNoticeNum.ts | 31 +++++++ src/middlewares/user/notice/getUserNotices.ts | 34 +++++++ .../user/notice/markNoticeAsSeen.ts | 23 +++++ .../user/notice/responseNoticesList.ts | 7 ++ .../user/notice/responseNoticesNum.ts | 7 ++ src/middlewares/user/responseUsersList.ts | 2 +- .../user/warning/responseWarningsList.ts | 2 +- src/models/ProfileSchema.ts | 11 ++- src/routes/notifications.ts | 28 ------ src/routes/setErrorHandler.ts | 27 ++++++ src/routes/setRouters.ts | 8 +- src/routes/users/notice.ts | 92 +++++++++++++++++++ src/utils/declarations/response.d.ts | 16 ++-- 18 files changed, 301 insertions(+), 62 deletions(-) create mode 100644 src/middlewares/user/notice/addNoticeToUser.ts create mode 100644 src/middlewares/user/notice/deleteNotice.ts create mode 100644 src/middlewares/user/notice/getUserNoticeNum.ts create mode 100644 src/middlewares/user/notice/getUserNotices.ts create mode 100644 src/middlewares/user/notice/markNoticeAsSeen.ts create mode 100644 src/middlewares/user/notice/responseNoticesList.ts create mode 100644 src/middlewares/user/notice/responseNoticesNum.ts delete mode 100644 src/routes/notifications.ts create mode 100644 src/routes/setErrorHandler.ts create mode 100644 src/routes/users/notice.ts diff --git a/src/index.ts b/src/index.ts index d8ecbaf4..426b6111 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,18 +58,4 @@ app.use((req: Request, res: Response, next: NextFunction) => { setRouters(app); -app.use((err: any, req: Request, res: Response) => { - if (err instanceof ErrorHandler) return handleError(err, res); - if ( - err instanceof mongoose.Error.CastError || - err instanceof mongoose.Error.ValidationError || - err instanceof multer.MulterError - ) - return handleError(new ErrorHandler(400, err.message), res); - - //Flush out the stack to the console - console.error(err.stack); - res.status(500).send("Houston, we have a problem!"); -}); - app.listen(8000, () => console.log(`Example app listening on port 8000!`)); diff --git a/src/middlewares/news/responseNews.ts b/src/middlewares/news/responseNews.ts index d570a738..2470df93 100644 --- a/src/middlewares/news/responseNews.ts +++ b/src/middlewares/news/responseNews.ts @@ -4,7 +4,7 @@ import { NextFunction, Request, Response, response } from "express"; * Return the found news from res.data.news */ const responseNews = () => (req: Request, res: Response) => { - res.json(res.data.news); + res.json(res.data.news || []); }; export default responseNews; diff --git a/src/middlewares/term/responseTermsList.ts b/src/middlewares/term/responseTermsList.ts index a1a91447..4baef97f 100644 --- a/src/middlewares/term/responseTermsList.ts +++ b/src/middlewares/term/responseTermsList.ts @@ -4,7 +4,7 @@ import { NextFunction, Request, Response, response } from "express"; * Return the found Term */ const responseTermsList = () => (req: Request, res: Response) => { - return res.json(res.data.terms); + return res.json(res.data.terms || []); }; export default responseTermsList; diff --git a/src/middlewares/user/notice/addNoticeToUser.ts b/src/middlewares/user/notice/addNoticeToUser.ts new file mode 100644 index 00000000..e879afd0 --- /dev/null +++ b/src/middlewares/user/notice/addNoticeToUser.ts @@ -0,0 +1,34 @@ +import { Mongoose, ObjectId, Types } from "mongoose"; +import { NextFunction, Request, Response } from "express"; +import Profile, { INotice, IWarning } from "../../../models/ProfileSchema"; + +import { validateFields } from "../../utils/validateFields"; + +const addNoticeToUser = ( + fields: { name: Partial<keyof INotice>; required: boolean }[] +) => async (req: Request, res: Response, next: NextFunction) => { + try { + validateFields({ fields, reqBody: req.body }); + + const notice: INotice = { + text: req.body.text, + redirect: req.body.redirect || "", + isSeen: false, + _id: String(new Types.ObjectId()), + }; + + await Profile.updateOne( + { _id: req.params.userId }, + { $push: { notices: notice } }, + { runValidators: true } + ) + .lean() + .exec(); + + next(); + } catch (err) { + next(err); + } +}; + +export default addNoticeToUser; diff --git a/src/middlewares/user/notice/deleteNotice.ts b/src/middlewares/user/notice/deleteNotice.ts new file mode 100644 index 00000000..9956c82f --- /dev/null +++ b/src/middlewares/user/notice/deleteNotice.ts @@ -0,0 +1,23 @@ +import { NextFunction, Request, Response } from "express"; +import Profile, { IWarning } from "../../../models/ProfileSchema"; + +const deleteNotice = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + await Profile.updateOne( + { _id: req.params.userId }, + { $pull: { notices: { _id: req.params.noticeId } } } + ) + .lean() + .exec(); + + next(); + } catch (err) { + next(err); + } +}; + +export default deleteNotice; diff --git a/src/middlewares/user/notice/getUserNoticeNum.ts b/src/middlewares/user/notice/getUserNoticeNum.ts new file mode 100644 index 00000000..e64c8844 --- /dev/null +++ b/src/middlewares/user/notice/getUserNoticeNum.ts @@ -0,0 +1,31 @@ +import { NextFunction, Request, Response, request } from "express"; +import Profile, { IProfile } from "../../../models/ProfileSchema"; + +import { FilterQuery } from "mongoose"; + +const getUserNoticeNum = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const profile = await Profile.findOne( + { _id: req.params.userId }, + { + noticesNum: { $size: { $ifNull: ["$notices", []] } }, + } + ) + .lean() + .exec(); + + if (!profile) return res.status(404).json({ message: "Profile not found" }); + + res.data.number = profile!.noticesNum; + + next(); + } catch (err) { + next(err); + } +}; + +export default getUserNoticeNum; diff --git a/src/middlewares/user/notice/getUserNotices.ts b/src/middlewares/user/notice/getUserNotices.ts new file mode 100644 index 00000000..f97bfb05 --- /dev/null +++ b/src/middlewares/user/notice/getUserNotices.ts @@ -0,0 +1,34 @@ +import { NextFunction, Request, Response, request } from "express"; + +import { FilterQuery } from "mongoose"; +import Profile from "../../../models/ProfileSchema"; + +const getUserNotices = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const profile = await Profile.findOne( + { _id: req.params.userId }, + { + "notices._id": 1, + "notices.text": 1, + "notices.redirect": 1, + "notices.isSeen": 1, + } + ) + .lean() + .exec(); + + if (!profile) return res.status(404).json({ message: "Profile not found" }); + + res.data.notices = profile.notices; + + next(); + } catch (err) { + next(err); + } +}; + +export default getUserNotices; diff --git a/src/middlewares/user/notice/markNoticeAsSeen.ts b/src/middlewares/user/notice/markNoticeAsSeen.ts new file mode 100644 index 00000000..260d28f4 --- /dev/null +++ b/src/middlewares/user/notice/markNoticeAsSeen.ts @@ -0,0 +1,23 @@ +import { NextFunction, Request, Response } from "express"; +import Profile, { IWarning } from "../../../models/ProfileSchema"; + +const markNoticeAsSeen = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + await Profile.updateOne( + { _id: req.params.userId, "notices._id": req.params.noticeId }, + { $set: { "notices.$.isSeen": true } } + ) + .lean() + .exec(); + + next(); + } catch (err) { + next(err); + } +}; + +export default markNoticeAsSeen; diff --git a/src/middlewares/user/notice/responseNoticesList.ts b/src/middlewares/user/notice/responseNoticesList.ts new file mode 100644 index 00000000..1348cb5a --- /dev/null +++ b/src/middlewares/user/notice/responseNoticesList.ts @@ -0,0 +1,7 @@ +import { NextFunction, Request, Response, response } from "express"; + +const responseNoticesList = () => (req: Request, res: Response) => { + res.json(res.data.notices || []); +}; + +export default responseNoticesList; diff --git a/src/middlewares/user/notice/responseNoticesNum.ts b/src/middlewares/user/notice/responseNoticesNum.ts new file mode 100644 index 00000000..97182bd0 --- /dev/null +++ b/src/middlewares/user/notice/responseNoticesNum.ts @@ -0,0 +1,7 @@ +import { NextFunction, Request, Response, response } from "express"; + +const responseNoticesNum = () => (req: Request, res: Response) => { + res.json({ noticesNum: res.data.number || 0 }); +}; + +export default responseNoticesNum; diff --git a/src/middlewares/user/responseUsersList.ts b/src/middlewares/user/responseUsersList.ts index b397c412..d4ba617e 100644 --- a/src/middlewares/user/responseUsersList.ts +++ b/src/middlewares/user/responseUsersList.ts @@ -4,7 +4,7 @@ import { NextFunction, Request, Response, response } from "express"; * Return the found users from res.data.profiles */ const responseUsersList = () => (req: Request, res: Response) => { - res.json(res.data.profiles); + res.json(res.data.profiles || []); }; export default responseUsersList; diff --git a/src/middlewares/user/warning/responseWarningsList.ts b/src/middlewares/user/warning/responseWarningsList.ts index 7a511c62..3bd82314 100644 --- a/src/middlewares/user/warning/responseWarningsList.ts +++ b/src/middlewares/user/warning/responseWarningsList.ts @@ -1,7 +1,7 @@ import { NextFunction, Request, Response, response } from "express"; const responseWarningsList = () => (req: Request, res: Response) => { - res.json(res.data.warnings); + res.json(res.data.warnings || []); }; export default responseWarningsList; diff --git a/src/models/ProfileSchema.ts b/src/models/ProfileSchema.ts index dc9a24c1..461c88e9 100644 --- a/src/models/ProfileSchema.ts +++ b/src/models/ProfileSchema.ts @@ -5,7 +5,7 @@ export enum Role { User = "USER", } -export interface INotification { +export interface INotice { _id: string; text: string; redirect?: string; @@ -31,7 +31,7 @@ export interface IProfile extends Document { isStaffMember?: boolean; staffMemberText?: string; warnings: IWarning[]; - notifications: INotification[]; + notices: INotice[]; } const ProfileSchema = new Schema({ @@ -54,7 +54,7 @@ const ProfileSchema = new Schema({ name: { type: String, required: true }, isStaffMember: { type: Boolean, required: false, default: false }, staffMemberText: { type: String, required: false }, - notifications: [ + notices: [ { text: { type: String, required: true }, redirect: { type: String, required: false }, @@ -70,4 +70,7 @@ const ProfileSchema = new Schema({ ], }); -export default model<IProfile>("Profile", ProfileSchema); +export default model<IProfile & { noticesNum: number }>( + "Profile", + ProfileSchema +); diff --git a/src/routes/notifications.ts b/src/routes/notifications.ts deleted file mode 100644 index 98f29e0b..00000000 --- a/src/routes/notifications.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Application } from "express"; -import example from "../middlewares/example"; - -// getOwnNotificationCount {number} -// getOwnNotifications {id, text, redirectUrl} -// deleteOwnNotification (notificationId) -// addUserNotification (userId) { text, redirectUrl} -// addTermMembersNotification (termId) { text, redirectUrl} -// addAllusersNotification { text, redirectUrl} - -const notificationsRoute = (prefix: string, app: Application): void => { - // Get all Notifications - app.get(`${prefix}/`, example()); - // Get a Notification - app.get(`${prefix}/notification/:notificationId`, example()); - // Delete a Notification - app.delete(`${prefix}/notification/:notificationId`, example()); - // Get own notifications - app.get(`${prefix}/me`, example()); - // Notify all users - app.post(`${prefix}/notify/all`, example()); - // Notify a given user - app.post(`${prefix}/notify/user/:userId`, example()); - // Notify accepted term members - app.post(`${prefix}/notify/term/:termId/accepted`, example()); -}; - -export default notificationsRoute; diff --git a/src/routes/setErrorHandler.ts b/src/routes/setErrorHandler.ts new file mode 100644 index 00000000..122ef20f --- /dev/null +++ b/src/routes/setErrorHandler.ts @@ -0,0 +1,27 @@ +import { Application, NextFunction, Request, Response } from "express"; +import { ErrorHandler, handleError } from "../middlewares/utils/ErrorHandler"; + +import { Error as MongooseError } from "mongoose"; +import complete from "../middlewares/auth/complete"; +import isLoggedIn from "../middlewares/auth/isLoggedIn"; +import login from "../middlewares/auth/login"; +import logout from "../middlewares/auth/logout"; +import multer from "multer"; + +const setErrorHandler = (app: Application): void => { + app.use((err: any, req: Request, res: Response, next: NextFunction) => { + if (err instanceof ErrorHandler) return handleError(err, res); + if ( + err instanceof MongooseError.CastError || + err instanceof MongooseError.ValidationError || + err instanceof multer.MulterError + ) + return handleError(new ErrorHandler(400, err.message), res); + + //Flush out the stack to the console + console.error(err.stack); + res.status(500).send("Houston, we have a problem!"); + }); +}; + +export default setErrorHandler; diff --git a/src/routes/setRouters.ts b/src/routes/setRouters.ts index bc232c53..e1b11761 100644 --- a/src/routes/setRouters.ts +++ b/src/routes/setRouters.ts @@ -2,9 +2,10 @@ import { Application } from "express"; import authRoute from "./auth"; import filesRoute from "./files"; import newsRoute from "./news"; -import notificationsRoute from "./notifications"; +import setErrorHandler from "./setErrorHandler"; import termsRoute from "./terms"; import userMembershipRoute from "./users/membership"; +import userNotificeRoute from "./users/notice"; import userWarningsRoute from "./users/warning"; import usersRoute from "./users/user"; @@ -17,11 +18,12 @@ export const setRouters = (app: Application): void => { newsRoute(`${prefix}/news`, app); - notificationsRoute(`${prefix}/notifications`, app); - termsRoute(`${prefix}/terms`, app); usersRoute(`${prefix}/users`, app); userMembershipRoute(`${prefix}/users`, app); userWarningsRoute(`${prefix}/users`, app); + userNotificeRoute(`${prefix}/users`, app); + + setErrorHandler(app); }; diff --git a/src/routes/users/notice.ts b/src/routes/users/notice.ts new file mode 100644 index 00000000..a3401a4b --- /dev/null +++ b/src/routes/users/notice.ts @@ -0,0 +1,92 @@ +import { Application } from "express"; +import addNoticeToUser from "../../middlewares/user/notice/addNoticeToUser"; +import deleteNotice from "../../middlewares/user/notice/deleteNotice"; +import example from "../../middlewares/example"; +import getUserNoticeNum from "../../middlewares/user/notice/getUserNoticeNum"; +import getUserNotices from "../../middlewares/user/notice/getUserNotices"; +import isRegistered from "../../middlewares/auth/isRegistered"; +import markNoticeAsSeen from "../../middlewares/user/notice/markNoticeAsSeen"; +import noContentResponse from "../../middlewares/utils/noContentResponse"; +import responseNoticesList from "../../middlewares/user/notice/responseNoticesList"; +import responseNoticesNum from "../../middlewares/user/notice/responseNoticesNum"; +import setOwnUserId from "../../middlewares/user/setOwnUserId"; + +const userNotificeRoute = (prefix: string, app: Application): void => { + /** + * Get own notifications + * Role: NORMAL + */ + app.get( + `${prefix}/me/notices`, + isRegistered(), + setOwnUserId(), + getUserNotices(), + responseNoticesList() + ); + + /** + * Get own notification number + * Role: NORMAL + */ + app.get( + `${prefix}/me/notices/number`, + isRegistered(), + setOwnUserId(), + getUserNoticeNum(), + responseNoticesNum() + ); + + /** + * Add notification to User + * Role: ADMIN + */ + app.post( + `${prefix}/user/:userId/notify`, + isRegistered(), + addNoticeToUser([ + { name: "text", required: true }, + { name: "redirect", required: false }, + ]), + noContentResponse() + ); + + /** + * Remove own notification + * Role: NORMAL + */ + app.delete( + `${prefix}/me/notice/:noticeId`, + isRegistered(), + setOwnUserId(), + deleteNotice(), + noContentResponse() + ); + + /** + * Add notification to Term members + * Role: ADMIN + */ + // TODO addTermMembersNotification (termId) { text, redirectUrl} + app.post(`${prefix}/term/:termId/notify`, example()); + + /** + * Add notification to all Users + * Role: ADMIN + */ + // TODO addAllusersNotification { text, redirectUrl} + app.post(`${prefix}/notify`, example()); + + /** + * Mark Notice as Seen + * Role: NORMAL + */ + app.put( + `${prefix}/me/notice/:noticeId`, + isRegistered(), + setOwnUserId(), + markNoticeAsSeen(), + noContentResponse() + ); +}; + +export default userNotificeRoute; diff --git a/src/utils/declarations/response.d.ts b/src/utils/declarations/response.d.ts index c912e8d5..11b1f8f5 100644 --- a/src/utils/declarations/response.d.ts +++ b/src/utils/declarations/response.d.ts @@ -1,7 +1,6 @@ import { INews } from "../../models/NewsSchema"; -import { IProfile } from "../../models/ProfileSchema"; +import { INotice, IProfile, IWarning } from "../../models/ProfileSchema"; import { IFile } from "../../models/FileSchema"; -import { IWarning } from "../../models/WarningSchema"; import { IMember, ITerm } from "../../models/TermSchema"; declare global { @@ -9,21 +8,20 @@ declare global { export interface Response { data: { profile?: Partial<IProfile> | null; + profiles?: Partial<IProfile>[] | null; + warning?: Partial<IWarning> | null; warnings?: Partial<IWarning>[] | null; newObjectId?: string | null; - value?: string | null; term?: Partial<ITerm> | null; terms?: Partial<ITerm>[] | null; member?: Partial<IMember> | null; members?: Partial<IMember>[] | null; - memberState?: IMember["state"] | null; news?: Partial<INews>[] | null; oneNews?: Partial<INews> | null; - - profiles?: Partial<IProfile>[] | null; - error?: string | null; - files?: Partial<IFile>[] | null; - warning?: Partial<IWarning> | null; + notice?: Partial<INotice> | null; + notices?: Partial<INotice>[] | null; + value?: string | null; + number?: number | null; }; } } -- GitLab