diff --git a/src/command/calendarCommand.ts b/src/command/calendarCommand.ts new file mode 100644 index 0000000..d216657 --- /dev/null +++ b/src/command/calendarCommand.ts @@ -0,0 +1,22 @@ +export interface CreateCalendarCommand { + date: Date; + starttime: Date; + endtime: Date; + title: string; + content: string; + typeId: number; +} + +export interface UpdateCalendarCommand { + id: number; + date: Date; + starttime: Date; + endtime: Date; + title: string; + content: string; + typeId: number; +} + +export interface DeleteCalendarCommand { + id: number; +} diff --git a/src/command/calendarCommandHandler.ts b/src/command/calendarCommandHandler.ts new file mode 100644 index 0000000..927b542 --- /dev/null +++ b/src/command/calendarCommandHandler.ts @@ -0,0 +1,85 @@ +import { dataSource } from "../data-source"; +import { calendar } from "../entity/calendar"; +import { calendarType } from "../entity/calendarType"; +import InternalException from "../exceptions/internalException"; +import { CreateCalendarCommand, DeleteCalendarCommand, UpdateCalendarCommand } from "./calendarCommand"; + +export default abstract class CalendarCommandHandler { + /** + * @description create calendar + * @param CreateCalendarCommand + * @returns {Promise} + */ + static async create(createCalendar: CreateCalendarCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(calendar) + .values({ + date: createCalendar.date, + starttime: createCalendar.starttime, + endtime: createCalendar.endtime, + title: createCalendar.title, + content: createCalendar.content, + type: await dataSource + .getRepository(calendarType) + .createQueryBuilder("type") + .where("id = :id", { id: createCalendar.typeId }) + .getOneOrFail(), + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating calendar", err); + }); + } + + /** + * @description update calendar + * @param UpdateCalendarCommand + * @returns {Promise} + */ + static async update(updateCalendar: UpdateCalendarCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(calendar) + .set({ + date: updateCalendar.date, + starttime: updateCalendar.starttime, + endtime: updateCalendar.endtime, + title: updateCalendar.title, + content: updateCalendar.content, + type: await dataSource + .getRepository(calendarType) + .createQueryBuilder("type") + .where("id = :id", { id: updateCalendar.typeId }) + .getOneOrFail(), + }) + .where("id = :id", { id: updateCalendar.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed updating award", err); + }); + } + + /** + * @description delete calendar + * @param DeleteCalendarCommand + * @returns {Promise} + */ + static async delete(deleteCalendar: DeleteCalendarCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(calendar) + .where("id = :id", { id: deleteCalendar.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed deleting calendar", err); + }); + } +} diff --git a/src/command/calendarTypeCommand.ts b/src/command/calendarTypeCommand.ts new file mode 100644 index 0000000..fdab618 --- /dev/null +++ b/src/command/calendarTypeCommand.ts @@ -0,0 +1,16 @@ +export interface CreateCalendarTypeCommand { + type: string; + nscdr: boolean; + color: string; +} + +export interface UpdateCalendarTypeCommand { + id: number; + type: string; + nscdr: boolean; + color: string; +} + +export interface DeleteCalendarTypeCommand { + id: number; +} diff --git a/src/command/calendarTypeCommandHandler.ts b/src/command/calendarTypeCommandHandler.ts new file mode 100644 index 0000000..0425425 --- /dev/null +++ b/src/command/calendarTypeCommandHandler.ts @@ -0,0 +1,70 @@ +import { dataSource } from "../data-source"; +import { calendarType } from "../entity/calendarType"; +import InternalException from "../exceptions/internalException"; +import { CreateCalendarTypeCommand, DeleteCalendarTypeCommand, UpdateCalendarTypeCommand } from "./calendarTypeCommand"; + +export default abstract class CalendarTypeCommandHandler { + /** + * @description create calendarType + * @param CreateCalendarTypeCommand + * @returns {Promise} + */ + static async create(createCalendarType: CreateCalendarTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(calendarType) + .values({ + type: createCalendarType.type, + nscdr: createCalendarType.nscdr, + color: createCalendarType.color, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating calendarType", err); + }); + } + + /** + * @description update calendarType + * @param UpdateCalendarTypeCommand + * @returns {Promise} + */ + static async update(updateCalendarType: UpdateCalendarTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(calendarType) + .set({ + type: updateCalendarType.type, + nscdr: updateCalendarType.nscdr, + color: updateCalendarType.color, + }) + .where("id = :id", { id: updateCalendarType.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed updating award", err); + }); + } + + /** + * @description delete calendarType + * @param DeleteCalendarTypeCommand + * @returns {Promise} + */ + static async delete(deleteCalendarType: DeleteCalendarTypeCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(calendarType) + .where("id = :id", { id: deleteCalendarType.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed deleting calendarType", err); + }); + } +} diff --git a/src/controller/admin/calendarController.ts b/src/controller/admin/calendarController.ts new file mode 100644 index 0000000..7fbfb67 --- /dev/null +++ b/src/controller/admin/calendarController.ts @@ -0,0 +1,197 @@ +import { Request, Response } from "express"; +import CalendarService from "../../service/calendarService"; +import CalendarFactory from "../../factory/admin/calendar"; +import CalendarTypeService from "../../service/calendarTypeService"; +import CalendarTypeFactory from "../../factory/admin/calendarType"; +import { CreateCalendarCommand, DeleteCalendarCommand, UpdateCalendarCommand } from "../../command/calendarCommand"; +import CalendarCommandHandler from "../../command/calendarCommandHandler"; +import { + CreateCalendarTypeCommand, + DeleteCalendarTypeCommand, + UpdateCalendarTypeCommand, +} from "../../command/calendarTypeCommand"; +import CalendarTypeCommandHandler from "../../command/calendarTypeCommandHandler"; + +/** + * @description get all calendar items + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllCalendarItems(req: Request, res: Response): Promise { + let items = await CalendarService.getAll(); + + res.json(CalendarFactory.mapToBase(items)); +} + +/** + * @description get calendar item by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getCalendarItemById(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + let item = await CalendarService.getById(id); + + res.json(CalendarFactory.mapToSingle(item)); +} + +/** + * @description get all calendar types + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllCalendarTypes(req: Request, res: Response): Promise { + let types = await CalendarTypeService.getAll(); + + res.json(CalendarTypeFactory.mapToBase(types)); +} + +/** + * @description get calendar type by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getCalendarTypeById(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + let type = await CalendarTypeService.getById(id); + + res.json(CalendarTypeFactory.mapToSingle(type)); +} + +/** + * @description create calendar item + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createCalendarItem(req: Request, res: Response): Promise { + const date = req.body.date; + const starttime = req.body.starttime; + const endtime = req.body.endtime; + const title = req.body.title; + const content = req.body.content; + const typeId = req.body.typeId; + + let createItem: CreateCalendarCommand = { + date, + starttime, + endtime, + title, + content, + typeId, + }; + let id = await CalendarCommandHandler.create(createItem); + + res.send(id); +} + +/** + * @description create calendar type + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createCalendarType(req: Request, res: Response): Promise { + const type = req.body.type; + const nscdr = req.body.nscdr; + const color = req.body.color; + + let createType: CreateCalendarTypeCommand = { + type, + nscdr, + color, + }; + let id = await CalendarTypeCommandHandler.create(createType); + + res.send(id); +} + +/** + * @description update calendar item + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateCalendarItem(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + const date = req.body.date; + const starttime = req.body.starttime; + const endtime = req.body.endtime; + const title = req.body.title; + const content = req.body.content; + const typeId = req.body.typeId; + + let updateItem: UpdateCalendarCommand = { + id, + date, + starttime, + endtime, + title, + content, + typeId, + }; + await CalendarCommandHandler.update(updateItem); + + res.sendStatus(204); +} + +/** + * @description update calendar type + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateCalendarType(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + const type = req.body.type; + const nscdr = req.body.nscdr; + const color = req.body.color; + + let updateType: UpdateCalendarTypeCommand = { + id, + type, + nscdr, + color, + }; + await CalendarTypeCommandHandler.update(updateType); + + res.sendStatus(204); +} + +/** + * @description delete calendar item + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteCalendarItem(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + + let deleteItem: DeleteCalendarCommand = { + id, + }; + await CalendarCommandHandler.delete(deleteItem); + + res.sendStatus(204); +} + +/** + * @description delete calendar type + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteCalendarType(req: Request, res: Response): Promise { + const id = parseInt(req.params.id); + + let deleteType: DeleteCalendarTypeCommand = { + id, + }; + await CalendarTypeCommandHandler.delete(deleteType); + + res.sendStatus(204); +} diff --git a/src/controller/publicController.ts b/src/controller/publicController.ts new file mode 100644 index 0000000..ecaf6e0 --- /dev/null +++ b/src/controller/publicController.ts @@ -0,0 +1,25 @@ +import { Request, Response } from "express"; +import CalendarFactory from "../factory/admin/calendar"; +import CalendarService from "../service/calendarService"; +import CalendarTypeService from "../service/calendarTypeService"; +import { calendar } from "../entity/calendar"; + +/** + * @description get all calendar items by types or nscdr + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getCalendarItemsByTypes(req: Request, res: Response): Promise { + let types = Array.isArray(req.query.types) ? req.query.types : [req.query.types]; + + let items: Array = []; + if (types.length == 0) { + let typeIds = await CalendarTypeService.getByTypes(types as Array); + items = await CalendarService.getByTypes(typeIds.map((t) => t.id)); + } else { + items = await CalendarService.getByTypeNSCDR(); + } + + res.json(CalendarFactory.mapToBase(items)); +} diff --git a/src/entity/calendarType.ts b/src/entity/calendarType.ts index f7184ef..5f696cc 100644 --- a/src/entity/calendarType.ts +++ b/src/entity/calendarType.ts @@ -12,6 +12,9 @@ export class calendarType { @Column({ type: "boolean" }) // none specified cal dav request nscdr: boolean; + @Column({ type: "varchar", length: 255 }) + color: string; + @OneToMany(() => calendar, (c) => c.type, { nullable: false, onDelete: "RESTRICT", diff --git a/src/factory/admin/calendar.ts b/src/factory/admin/calendar.ts new file mode 100644 index 0000000..460552c --- /dev/null +++ b/src/factory/admin/calendar.ts @@ -0,0 +1,31 @@ +import { calendar } from "../../entity/calendar"; +import { CalendarViewModel } from "../../viewmodel/admin/calendar.models"; +import CalendarTypeFactory from "./calendarType"; + +export default abstract class CalendarFactory { + /** + * @description map record to calendar + * @param {calendar} record + * @returns {CalendarViewModel} + */ + public static mapToSingle(record: calendar): CalendarViewModel { + return { + id: record.id, + date: record.date, + starttime: record.starttime, + endtime: record.endtime, + title: record.title, + content: record.content, + type: CalendarTypeFactory.mapToSingle(record.type), + }; + } + + /** + * @description map records to calendar + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/calendarType.ts b/src/factory/admin/calendarType.ts new file mode 100644 index 0000000..d1f6850 --- /dev/null +++ b/src/factory/admin/calendarType.ts @@ -0,0 +1,27 @@ +import { calendarType } from "../../entity/calendarType"; +import { CalendarTypeViewModel } from "../../viewmodel/admin/calendarType.models"; + +export default abstract class CalendarTypeFactory { + /** + * @description map record to calendarType + * @param {calendarType} record + * @returns {CalendarTypeViewModel} + */ + public static mapToSingle(record: calendarType): CalendarTypeViewModel { + return { + id: record.id, + type: record.type, + nscdr: record.nscdr, + color: record.color, + }; + } + + /** + * @description map records to calendarType + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/migrations/1729947763295-calendar.ts b/src/migrations/1729947763295-calendar.ts index ee199fc..f8b5688 100644 --- a/src/migrations/1729947763295-calendar.ts +++ b/src/migrations/1729947763295-calendar.ts @@ -14,6 +14,7 @@ export class Calendar1729947763295 implements MigrationInterface { { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, { name: "type", type: "varchar", length: "255", isNullable: false }, { name: "nscdr", type: "tinyint", isNullable: false }, + { name: "color", type: "varchar", length: "255", isNullable: false }, ], }) ); diff --git a/src/routes/admin/calendar.ts b/src/routes/admin/calendar.ts new file mode 100644 index 0000000..37fd30c --- /dev/null +++ b/src/routes/admin/calendar.ts @@ -0,0 +1,57 @@ +import express, { Request, Response } from "express"; +import { + getCalendarItemById, + getAllCalendarItems, + getAllCalendarTypes, + getCalendarTypeById, + createCalendarItem, + createCalendarType, + updateCalendarItem, + updateCalendarType, + deleteCalendarItem, + deleteCalendarType, +} from "../../controller/admin/calendarController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/items", async (req: Request, res: Response) => { + await getAllCalendarItems(req, res); +}); + +router.get("/item/:id", async (req: Request, res: Response) => { + await getCalendarItemById(req, res); +}); + +router.get("/types", async (req: Request, res: Response) => { + await getAllCalendarTypes(req, res); +}); + +router.get("/type/:id", async (req: Request, res: Response) => { + await getCalendarTypeById(req, res); +}); + +router.post("/item", async (req: Request, res: Response) => { + await createCalendarItem(req, res); +}); + +router.post("/type", async (req: Request, res: Response) => { + await createCalendarType(req, res); +}); + +router.patch("/item/:id", async (req: Request, res: Response) => { + await updateCalendarItem(req, res); +}); + +router.patch("/type/:id", async (req: Request, res: Response) => { + await updateCalendarType(req, res); +}); + +router.delete("/item/:id", async (req: Request, res: Response) => { + await deleteCalendarItem(req, res); +}); + +router.delete("/type/:id", async (req: Request, res: Response) => { + await deleteCalendarType(req, res); +}); + +export default router; diff --git a/src/routes/admin/index.ts b/src/routes/admin/index.ts index c0c1ab9..63a70ff 100644 --- a/src/routes/admin/index.ts +++ b/src/routes/admin/index.ts @@ -9,6 +9,8 @@ import qualification from "./qualification"; import member from "./member"; +import calendar from "./calendar"; + import role from "./role"; import user from "./user"; @@ -34,6 +36,8 @@ router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "setti router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "member"), member); +router.use("/calendar", PermissionHelper.passCheckMiddleware("read", "club", "calendar"), calendar); + router.use("/role", PermissionHelper.passCheckMiddleware("read", "user", "role"), role); router.use("/user", PermissionHelper.passCheckMiddleware("read", "user", "user"), user); diff --git a/src/routes/index.ts b/src/routes/index.ts index 2dcff79..070508c 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -6,6 +6,7 @@ import allowSetup from "../middleware/allowSetup"; import authenticate from "../middleware/authenticate"; import errorHandler from "../middleware/errorHandler"; +import publicAvailable from "./public"; import setup from "./setup"; import auth from "./auth"; import admin from "./admin/index"; @@ -21,6 +22,7 @@ export default (app: Express) => { app.use(cors()); app.options("*", cors()); + app.use("/public", publicAvailable); app.use("/setup", allowSetup, setup); app.use("/auth", auth); app.use(authenticate); diff --git a/src/routes/public.ts b/src/routes/public.ts new file mode 100644 index 0000000..56b4784 --- /dev/null +++ b/src/routes/public.ts @@ -0,0 +1,10 @@ +import express from "express"; +import { getCalendarItemsByTypes } from "../controller/publicController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/calendar", async (req, res) => { + await getCalendarItemsByTypes(req, res); +}); + +export default router; diff --git a/src/service/calendarService.ts b/src/service/calendarService.ts new file mode 100644 index 0000000..99f0e7d --- /dev/null +++ b/src/service/calendarService.ts @@ -0,0 +1,78 @@ +import { dataSource } from "../data-source"; +import { calendar } from "../entity/calendar"; +import InternalException from "../exceptions/internalException"; + +export default abstract class CalendarService { + /** + * @description get all calendars + * @returns {Promise>} + */ + static async getAll(): Promise> { + return await dataSource + .getRepository(calendar) + .createQueryBuilder("calendar") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("calendars not found", err); + }); + } + + /** + * @description get calendar by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(calendar) + .createQueryBuilder("calendar") + .where("calendar.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("calendar not found by id", err); + }); + } + + /** + * @description get calendar by types + * @returns {Promise>} + */ + static async getByTypes(types: Array): Promise> { + return await dataSource + .getRepository(calendar) + .createQueryBuilder("calendar") + .leftJoinAndSelect("calendar.type", "type") + .where("type.id IN (:...types)", { types: types }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("calendars not found by types", err); + }); + } + + /** + * @description get calendar by types nscdr + * @returns {Promise>} + */ + static async getByTypeNSCDR(): Promise> { + return await dataSource + .getRepository(calendar) + .createQueryBuilder("calendar") + .leftJoinAndSelect("calendar.type", "type") + .where("type.nscdr = :nscdr)", { nscdr: true }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("calendars not found by type nscdr", err); + }); + } +} diff --git a/src/service/calendarTypeService.ts b/src/service/calendarTypeService.ts new file mode 100644 index 0000000..08e76ea --- /dev/null +++ b/src/service/calendarTypeService.ts @@ -0,0 +1,58 @@ +import { dataSource } from "../data-source"; +import { calendarType } from "../entity/calendarType"; +import InternalException from "../exceptions/internalException"; + +export default abstract class CalendarTypeService { + /** + * @description get all calendar types + * @returns {Promise>} + */ + static async getAll(): Promise> { + return await dataSource + .getRepository(calendarType) + .createQueryBuilder("calendarType") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("calendarTypes not found", err); + }); + } + + /** + * @description get calendar type by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(calendarType) + .createQueryBuilder("calendarType") + .where("calendarType.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("calendarType not found by id", err); + }); + } + + /** + * @description get calendar by names + * @returns {Promise>} + */ + static async getByTypes(names: Array): Promise> { + return await dataSource + .getRepository(calendarType) + .createQueryBuilder("calendarType") + .where("calendarType.type IN (:...names)", { names: names }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("calendarTypes not found by names", err); + }); + } +} diff --git a/src/viewmodel/admin/calendar.models.ts b/src/viewmodel/admin/calendar.models.ts new file mode 100644 index 0000000..536882a --- /dev/null +++ b/src/viewmodel/admin/calendar.models.ts @@ -0,0 +1,11 @@ +import { CalendarTypeViewModel } from "./calendarType.models"; + +export interface CalendarViewModel { + id: number; + date: Date; + starttime: Date; + endtime: Date; + title: string; + content: string; + type: CalendarTypeViewModel; +} diff --git a/src/viewmodel/admin/calendarType.models.ts b/src/viewmodel/admin/calendarType.models.ts new file mode 100644 index 0000000..e57dcb1 --- /dev/null +++ b/src/viewmodel/admin/calendarType.models.ts @@ -0,0 +1,6 @@ +export interface CalendarTypeViewModel { + id: number; + type: string; + nscdr: boolean; + color: string; +}