diff --git a/src/middlewares/term/addTerm.ts b/src/middlewares/term/addTerm.ts new file mode 100644 index 0000000000000000000000000000000000000000..73449a4d3679edbb27e2c0ceb051daf4c35104e8 --- /dev/null +++ b/src/middlewares/term/addTerm.ts @@ -0,0 +1,40 @@ +import { NextFunction, Request, Response } from "express"; + +import Term from "../../models/TermSchema"; +import { validateFields } from "../utils/validateFields"; + +const fields = [ + { name: "name", required: true }, + { name: "createDate", required: false }, + { name: "startDate", required: true }, + { name: "endDate", required: true }, +]; + +/** + * Add a new Term + */ +const addTerm = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const term = new Term(); + + // Validate and set fields from request body + validateFields({ fields, reqBody: req.body }); + fields.forEach((field) => { + const value = req.body[field.name]; + if (value) term.set(field.name, req.body[field.name]); + }); + term.createDate = new Date(); + await term.save(); + + res.data.newObjectId = term._id; + next(); + } catch (err) { + next(err); + } +}; + +export default addTerm; diff --git a/src/middlewares/term/deleteTerm.ts b/src/middlewares/term/deleteTerm.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf5c325a241130aec67667a8ad88b06fa8b5d8fa --- /dev/null +++ b/src/middlewares/term/deleteTerm.ts @@ -0,0 +1,26 @@ +import { NextFunction, Request, Response } from "express"; + +import File from "../../models/FileSchema"; +import Profile from "../../models/ProfileSchema"; +import Term from "../../models/TermSchema"; + +/** + * termId -> deletes the Term + */ +const deleteTerm = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const removedTerm = await Term.findByIdAndDelete(req.params.termId); + if (!removedTerm) + return res.status(404).json({ message: "Term not found!" }); + await File.findByIdAndDelete(removedTerm?.backgroundFile); + next(); + } catch (err) { + next(err); + } +}; + +export default deleteTerm; diff --git a/src/middlewares/term/filterMembersByState.ts b/src/middlewares/term/filterMembersByState.ts new file mode 100644 index 0000000000000000000000000000000000000000..0fa32511ca3046e65c3ebde7c7530a34573a5bc9 --- /dev/null +++ b/src/middlewares/term/filterMembersByState.ts @@ -0,0 +1,39 @@ +import { NextFunction, Request, Response } from "express"; +import Term, { MemberState } from "../../models/TermSchema"; + +import File from "../../models/FileSchema"; +import Profile from "../../models/ProfileSchema"; + +const convertState = (state: string) => { + switch (state) { + case "accepted": + return MemberState.Accepted; + case "applied": + return MemberState.Applied; + case "rejected": + return MemberState.Rejected; + } + return null; +}; + +/** + * filteredState, res.data.members -> filter Term members by state + */ +const filterMembersByState = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + const state = convertState(req.params.filteredState); + if (state) + res.data.members = res.data.term?.members?.filter( + (member) => member.state == state + ); + next(); + } catch (err) { + next(err); + } +}; + +export default filterMembersByState; diff --git a/src/middlewares/term/getLatestTerm.ts b/src/middlewares/term/getLatestTerm.ts new file mode 100644 index 0000000000000000000000000000000000000000..9cd890ed6b622752a092962338ed975c2da08561 --- /dev/null +++ b/src/middlewares/term/getLatestTerm.ts @@ -0,0 +1,24 @@ +import { NextFunction, Request, Response } from "express"; + +import Profile from "../../models/ProfileSchema"; +import Term from "../../models/TermSchema"; + +const getLatestTerm = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + res.data.term = await Term.findOne({}, {}, { sort: { createDate: -1 } }) + .lean() + .exec(); + if (!res.data.term) + return res.status(404).json({ message: "There are no Terms" }); + + next(); + } catch (err) { + next(err); + } +}; + +export default getLatestTerm; diff --git a/src/middlewares/term/getMember.ts b/src/middlewares/term/getMember.ts new file mode 100644 index 0000000000000000000000000000000000000000..45c9882b2b138d513be3df022d681e8ccbbe4dd4 --- /dev/null +++ b/src/middlewares/term/getMember.ts @@ -0,0 +1,27 @@ +import { NextFunction, Request, Response } from "express"; + +import File from "../../models/FileSchema"; +import Profile from "../../models/ProfileSchema"; +import Term from "../../models/TermSchema"; + +/** + * userId -> Found Term + */ +const getMember = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + res.data.member = res.data.term?.members?.find( + (member) => member.user == req.params.userId + ); + if (!res.data.member) + return res.status(404).json({ message: "Member not found!" }); + next(); + } catch (err) { + next(err); + } +}; + +export default getMember; diff --git a/src/middlewares/term/getMemberState.ts b/src/middlewares/term/getMemberState.ts new file mode 100644 index 0000000000000000000000000000000000000000..4054af99a8637a787cd3cc4aff157cd42594e39c --- /dev/null +++ b/src/middlewares/term/getMemberState.ts @@ -0,0 +1,23 @@ +import { NextFunction, Request, Response } from "express"; + +import File from "../../models/FileSchema"; +import Profile from "../../models/ProfileSchema"; +import Term from "../../models/TermSchema"; + +/** + * member -> memberState + */ +const getMemberState = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + res.data.memberState = res.data.member!.state; + next(); + } catch (err) { + next(err); + } +}; + +export default getMemberState; diff --git a/src/middlewares/term/getMembers.ts b/src/middlewares/term/getMembers.ts new file mode 100644 index 0000000000000000000000000000000000000000..23f5a9ce0f9bfb40a342ec59611c705d9c570d12 --- /dev/null +++ b/src/middlewares/term/getMembers.ts @@ -0,0 +1,23 @@ +import { NextFunction, Request, Response } from "express"; + +import File from "../../models/FileSchema"; +import Profile from "../../models/ProfileSchema"; +import Term from "../../models/TermSchema"; + +/** + * res.data.term.members -> res.data.members + */ +const getMembers = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + res.data.members = res.data.term?.members; + next(); + } catch (err) { + next(err); + } +}; + +export default getMembers; diff --git a/src/middlewares/term/getTerm.ts b/src/middlewares/term/getTerm.ts new file mode 100644 index 0000000000000000000000000000000000000000..2a9799b38711e806684ba27331fa63e532570ad0 --- /dev/null +++ b/src/middlewares/term/getTerm.ts @@ -0,0 +1,26 @@ +import { NextFunction, Request, Response } from "express"; + +import Profile from "../../models/ProfileSchema"; +import Term from "../../models/TermSchema"; + +/** + * termId -> term + */ +const getTerm = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + res.data.term = await Term.findById(req.params.termId).lean().exec(); + + if (!res.data.term) + return res.status(404).json({ message: "Term not found" }); + + next(); + } catch (err) { + next(err); + } +}; + +export default getTerm; diff --git a/src/middlewares/term/getTermsList.ts b/src/middlewares/term/getTermsList.ts new file mode 100644 index 0000000000000000000000000000000000000000..e22b0637ec5a8251047f3ccd8b8bee4659031fcd --- /dev/null +++ b/src/middlewares/term/getTermsList.ts @@ -0,0 +1,22 @@ +import { NextFunction, Request, Response } from "express"; + +import Profile from "../../models/ProfileSchema"; +import Term from "../../models/TermSchema"; + +/** + * -> All Terms to res.data.terms + */ +const getTermsList = () => async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + res.data.terms = await Term.find().lean().exec(); + next(); + } catch (err) { + next(err); + } +}; + +export default getTermsList; diff --git a/src/middlewares/term/responseMember.ts b/src/middlewares/term/responseMember.ts new file mode 100644 index 0000000000000000000000000000000000000000..e1348185409f5e8dfd411eeee2722d523f8c039d --- /dev/null +++ b/src/middlewares/term/responseMember.ts @@ -0,0 +1,14 @@ +import { NextFunction, Request, Response, response } from "express"; + +/** + * Return the found Member + */ +const responseMember = () => (req: Request, res: Response) => { + if (!res.data.member) { + res.status(404).json({ message: "Member not found!" }); + } else { + return res.json(res.data.member); + } +}; + +export default responseMember; diff --git a/src/middlewares/term/responseMemberState.ts b/src/middlewares/term/responseMemberState.ts new file mode 100644 index 0000000000000000000000000000000000000000..295d9111a06b760e8f08e4cf6c2a873901f0ada9 --- /dev/null +++ b/src/middlewares/term/responseMemberState.ts @@ -0,0 +1,14 @@ +import { NextFunction, Request, Response, response } from "express"; + +/** + * Return the found MemberState + */ +const responseMemberState = () => (req: Request, res: Response) => { + if (!res.data.memberState) { + res.status(404).json({ message: "MemberState not found!" }); + } else { + return res.json(res.data.memberState); + } +}; + +export default responseMemberState; diff --git a/src/middlewares/term/responseMembersList.ts b/src/middlewares/term/responseMembersList.ts new file mode 100644 index 0000000000000000000000000000000000000000..3568bdf737c16ba3571d30ca9ddccb6ca0632747 --- /dev/null +++ b/src/middlewares/term/responseMembersList.ts @@ -0,0 +1,10 @@ +import { NextFunction, Request, Response, response } from "express"; + +/** + * Return the found Members + */ +const responseMembersList = () => (req: Request, res: Response) => { + return res.json(res.data.members); +}; + +export default responseMembersList; diff --git a/src/middlewares/term/responseTerm.ts b/src/middlewares/term/responseTerm.ts new file mode 100644 index 0000000000000000000000000000000000000000..3977d625c8e028e8de5ed7de50d575574cbd4972 --- /dev/null +++ b/src/middlewares/term/responseTerm.ts @@ -0,0 +1,18 @@ +import { NextFunction, Request, Response, response } from "express"; + +/** + * Return the found Term + */ +const responseTerm = (sendMembers = true) => (req: Request, res: Response) => { + if (!res.data.term) { + res.status(404).json({ message: "Term not found!" }); + } else { + if (!sendMembers) { + const { members, ...kept } = res.data.term; + return res.json({ term: kept }); + } + return res.json(res.data.term); + } +}; + +export default responseTerm; diff --git a/src/middlewares/term/responseTermsList.ts b/src/middlewares/term/responseTermsList.ts new file mode 100644 index 0000000000000000000000000000000000000000..c53c5207632f015a3a7ef54452b3113c277bdc70 --- /dev/null +++ b/src/middlewares/term/responseTermsList.ts @@ -0,0 +1,15 @@ +import { NextFunction, Request, Response, response } from "express"; + +/** + * Return the found Term + */ +const responseTermsList = (sendMembers = true) => ( + req: Request, + res: Response +) => { + if (!sendMembers) + return res.json(res.data.terms?.map(({ members, ...keep }) => keep)); + return res.json(res.data.terms); +}; + +export default responseTermsList; diff --git a/src/middlewares/user/getUsersList.ts b/src/middlewares/user/getUsersList.ts index cfff3060347c754d930fa7dbb46c623d31e5b0f3..a188f2955dfbd1c71e98ff094d8b45284ee5ab89 100644 --- a/src/middlewares/user/getUsersList.ts +++ b/src/middlewares/user/getUsersList.ts @@ -11,7 +11,7 @@ const getUsersList = () => async ( next: NextFunction ) => { try { - res.data.profiles = await Profile.find(); + res.data.profiles = await Profile.find().lean().exec(); next(); } catch (err) { next(err); diff --git a/src/models/TermSchema.ts b/src/models/TermSchema.ts index 5a1de32d37b9afd4d153f2425944b0c903138c68..368f580cb903dc99a4b46ecac18bda91a8af0eaf 100644 --- a/src/models/TermSchema.ts +++ b/src/models/TermSchema.ts @@ -6,35 +6,41 @@ export enum MemberState { Rejected = "REJECTED", } +export interface IMember { + user: string; + state: MemberState; + cardNumber: number; + cardReceiveDate?: Date; +} + export interface ITerm extends Document { backgroundFile?: string; name: string; + createDate?: Date; startDate: Date; endDate: Date; - members: { - user: string; - state: MemberState; - cardNumber: number; - cardReceiveDate?: Date; - }[]; + members: IMember[]; } const TermSchema = new Schema({ backgroundFile: { type: Schema.Types.ObjectId, ref: "File", required: false }, name: { type: String, required: true }, + createDate: { type: Date, required: true }, startDate: { type: Date, required: true }, endDate: { type: Date, required: true }, members: [ { - required: false, - type: { - user: { type: Schema.Types.ObjectId, ref: "Profile", required: true }, - state: Object.keys(MemberState).map( + user: { type: Schema.Types.ObjectId, ref: "Profile", required: true }, + state: { + type: String, + enum: Object.keys(MemberState).map( (k) => MemberState[k as keyof typeof MemberState] ), - cardNumber: { type: Number, required: true }, - cardReceiveDate: { type: Date, required: false }, + required: true, + default: MemberState.Applied, }, + cardNumber: { type: Number, required: true }, + cardReceiveDate: { type: Date, required: false }, }, ], }); diff --git a/src/routes/terms.ts b/src/routes/terms.ts index 980891c01213a7b111fed04eb4febd153fe9da8c..08d014ebfc376f1e47d84d15e9e6bce0c978de98 100644 --- a/src/routes/terms.ts +++ b/src/routes/terms.ts @@ -1,21 +1,124 @@ import { Application } from "express"; +import addTerm from "../middlewares/term/addTerm"; +import createdResponse from "../middlewares/utils/createdResponse"; +import deleteTerm from "../middlewares/term/deleteTerm"; import example from "../middlewares/example"; +import filterMembersByState from "../middlewares/term/filterMembersByState"; +import getLatestTerm from "../middlewares/term/getLatestTerm"; +import getMember from "../middlewares/term/getMember"; +import getMemberState from "../middlewares/term/getMemberState"; +import getMembers from "../middlewares/term/getMembers"; +import getTerm from "../middlewares/term/getTerm"; +import getTermsList from "../middlewares/term/getTermsList"; +import isRegistered from "../middlewares/auth/isRegistered"; +import isStaffOrAdmin from "../middlewares/auth/isStaffOrAdmin"; +import noContentResponse from "../middlewares/utils/noContentResponse"; +import responseMember from "../middlewares/term/responseMember"; +import responseMemberState from "../middlewares/term/responseMemberState"; +import responseMembersList from "../middlewares/term/responseMembersList"; +import responseTerm from "../middlewares/term/responseTerm"; +import responseTermsList from "../middlewares/term/responseTermsList"; +import setOwnUserId from "../middlewares/user/setOwnUserId"; const termsRoute = (prefix: string, app: Application): void => { - // Get all Terms - app.get(`${prefix}/`, example()); - // Get a Term - app.post(`${prefix}/term/:termId`, example()); + // Get all Terms without members + app.get( + `${prefix}/`, + isRegistered(), + getTermsList(), + responseTermsList(false) + ); + // Get all Terms with members + app.get( + `${prefix}/wmembers`, + isRegistered(), + isStaffOrAdmin(), + getTermsList(), + responseTermsList(true) + ); + // Create a Term + app.post( + `${prefix}/`, + isRegistered(), + isStaffOrAdmin(), + addTerm(), + createdResponse() + ); + // Get a Term without members + app.get( + `${prefix}/term/:termId`, + isRegistered(), + getTerm(), + responseTerm(false) + ); + // Get a Term with members + app.get( + `${prefix}/term/:termId/wmembers`, + isRegistered(), + isStaffOrAdmin(), + getTerm(), + responseTerm(true) + ); + // Delete a Term + app.delete( + `${prefix}/term/:termId`, + isRegistered(), + isStaffOrAdmin(), + deleteTerm(), + noContentResponse() + ); // Get members of a Term - app.get(`${prefix}/term/:termId/members`, example()); + app.get( + `${prefix}/term/:termId/members`, + isRegistered(), + isStaffOrAdmin(), + getTerm(), + getMembers(), + responseMembersList() + ); // Get filtered members of a Term (accepted/rejected/applied) - app.get(`${prefix}/term/:termId/members/:stateFilter`, example()); + app.get( + `${prefix}/term/:termId/members/:stateFilter`, + isRegistered(), + isStaffOrAdmin(), + getTerm(), + filterMembersByState(), + responseMembersList() + ); // Get own member state in a Term - app.get(`${prefix}/term/:termId/state/me`, example()); + app.get( + `${prefix}/term/:termId/state/me`, + isRegistered(), + setOwnUserId(), + getTerm(), + getMember(), + getMemberState(), + responseMemberState() + ); // Get a user's state in a Term - app.get(`${prefix}/term/:termId/state/user/:userId`, example()); - // Get the latest Term - app.get(`${prefix}/latest`, example()); + app.get( + `${prefix}/term/:termId/state/user/:userId`, + isRegistered(), + isStaffOrAdmin(), + getTerm(), + getMember(), + getMemberState(), + responseMemberState() + ); + // Get the latest Term without members + app.get( + `${prefix}/latest`, + isRegistered(), + getLatestTerm(), + responseTerm(false) + ); + // Get the latest Term with members + app.get( + `${prefix}/latest`, + isRegistered(), + getLatestTerm(), + responseTerm(true) + ); // Apply own user to the latest Term app.post(`${prefix}/apply/me/latest`, example()); // Apply a user to the latest Term diff --git a/src/utils/declarations/response.d.ts b/src/utils/declarations/response.d.ts index 878f90fe2a223b2fd6c3d7c9fdfb525877c4f513..1e9abe0d3454c291e80dc4661051a83e2e76951f 100644 --- a/src/utils/declarations/response.d.ts +++ b/src/utils/declarations/response.d.ts @@ -2,7 +2,7 @@ import { INews } from "../../models/NewsSchema"; import { IProfile } from "../../models/ProfileSchema"; import { IFile } from "../../models/FileSchema"; import { IWarning } from "../../models/WarningSchema"; -import { ITerm } from "../../models/TermSchema"; +import { IMember, ITerm } from "../../models/TermSchema"; declare global { namespace Express { @@ -13,6 +13,11 @@ declare global { 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; newsObject?: Partial<INews> | null; profiles?: Partial<IProfile>[] | null;