From 9b4b9124b435df3c79145f6c328651c9284fff16 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rafael=20L=C3=A1szl=C3=B3?= <rlacko99@gmail.com>
Date: Thu, 24 Dec 2020 19:44:44 +0100
Subject: [PATCH] Term middlewares

---
 src/middlewares/term/addTerm.ts              |  40 ++++++
 src/middlewares/term/deleteTerm.ts           |  26 ++++
 src/middlewares/term/filterMembersByState.ts |  39 ++++++
 src/middlewares/term/getLatestTerm.ts        |  24 ++++
 src/middlewares/term/getMember.ts            |  27 ++++
 src/middlewares/term/getMemberState.ts       |  23 ++++
 src/middlewares/term/getMembers.ts           |  23 ++++
 src/middlewares/term/getTerm.ts              |  26 ++++
 src/middlewares/term/getTermsList.ts         |  22 ++++
 src/middlewares/term/responseMember.ts       |  14 +++
 src/middlewares/term/responseMemberState.ts  |  14 +++
 src/middlewares/term/responseMembersList.ts  |  10 ++
 src/middlewares/term/responseTerm.ts         |  18 +++
 src/middlewares/term/responseTermsList.ts    |  15 +++
 src/middlewares/user/getUsersList.ts         |   2 +-
 src/models/TermSchema.ts                     |  30 +++--
 src/routes/terms.ts                          | 123 +++++++++++++++++--
 src/utils/declarations/response.d.ts         |   7 +-
 18 files changed, 459 insertions(+), 24 deletions(-)
 create mode 100644 src/middlewares/term/addTerm.ts
 create mode 100644 src/middlewares/term/deleteTerm.ts
 create mode 100644 src/middlewares/term/filterMembersByState.ts
 create mode 100644 src/middlewares/term/getLatestTerm.ts
 create mode 100644 src/middlewares/term/getMember.ts
 create mode 100644 src/middlewares/term/getMemberState.ts
 create mode 100644 src/middlewares/term/getMembers.ts
 create mode 100644 src/middlewares/term/getTerm.ts
 create mode 100644 src/middlewares/term/getTermsList.ts
 create mode 100644 src/middlewares/term/responseMember.ts
 create mode 100644 src/middlewares/term/responseMemberState.ts
 create mode 100644 src/middlewares/term/responseMembersList.ts
 create mode 100644 src/middlewares/term/responseTerm.ts
 create mode 100644 src/middlewares/term/responseTermsList.ts

diff --git a/src/middlewares/term/addTerm.ts b/src/middlewares/term/addTerm.ts
new file mode 100644
index 00000000..73449a4d
--- /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 00000000..cf5c325a
--- /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 00000000..0fa32511
--- /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 00000000..9cd890ed
--- /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 00000000..45c9882b
--- /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 00000000..4054af99
--- /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 00000000..23f5a9ce
--- /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 00000000..2a9799b3
--- /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 00000000..e22b0637
--- /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 00000000..e1348185
--- /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 00000000..295d9111
--- /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 00000000..3568bdf7
--- /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 00000000..3977d625
--- /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 00000000..c53c5207
--- /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 cfff3060..a188f295 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 5a1de32d..368f580c 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 980891c0..08d014eb 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 878f90fe..1e9abe0d 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;
-- 
GitLab