Compare commits

...

8 commits

Author SHA1 Message Date
7b6528ee96 Merge pull request '#3-calendar' (#11) from #3-calendar into main
Reviewed-on: Ehrenamt/member-administration-server#11
2024-11-07 10:10:00 +00:00
98eb870385 Merge branch 'main' into #3-calendar
# Conflicts:
#	package-lock.json
#	src/data-source.ts
#	src/routes/admin/index.ts
#	src/type/permissionTypes.ts
2024-11-07 11:09:52 +01:00
1d9919d520 cleanup 2024-11-07 10:58:53 +01:00
91c3fde688 provide ics link 2024-11-07 10:49:34 +01:00
8c597fd68d calendar fixes 2024-10-28 16:02:56 +01:00
136da08b70 permission 2024-10-27 15:48:01 +01:00
bf701163d6 calender crud operations 2024-10-27 11:47:13 +01:00
e7b8257336 base tables 2024-10-26 15:08:05 +02:00
23 changed files with 1048 additions and 1 deletions

88
package-lock.json generated
View file

@ -12,7 +12,9 @@
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^5.0.0-beta.3",
"ics": "^3.8.1",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"ms": "^2.1.3",
"mysql": "^2.18.1",
"node-schedule": "^2.1.1",
@ -1822,6 +1824,17 @@
"node": ">=0.10.0"
}
},
"node_modules/ics": {
"version": "3.8.1",
"resolved": "https://registry.npmjs.org/ics/-/ics-3.8.1.tgz",
"integrity": "sha512-UqQlfkajfhrS4pUGQfGIJMYz/Jsl/ob3LqcfEhUmLbwumg+ZNkU0/6S734Vsjq3/FYNpEcZVKodLBoe+zBM69g==",
"license": "ISC",
"dependencies": {
"nanoid": "^3.1.23",
"runes2": "^1.1.2",
"yup": "^1.2.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -2217,6 +2230,15 @@
"optional": true,
"peer": true
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -2252,6 +2274,24 @@
"thenify-all": "^1.0.0"
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
@ -2597,6 +2637,12 @@
"node": ">=0.4.0"
}
},
"node_modules/property-expr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
"integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
"license": "MIT"
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@ -2823,6 +2869,12 @@
"node": ">= 0.10"
}
},
"node_modules/runes2": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz",
"integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==",
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -3366,6 +3418,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tiny-case": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
"integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
"license": "MIT"
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@ -3374,6 +3432,12 @@
"node": ">=0.6"
}
},
"node_modules/toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
"license": "MIT"
},
"node_modules/tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
@ -3455,6 +3519,18 @@
"license": "Unlicense",
"optional": true
},
"node_modules/type-fest": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
"integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=12.20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@ -3918,6 +3994,18 @@
"engines": {
"node": ">=6"
}
},
"node_modules/yup": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz",
"integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==",
"license": "MIT",
"dependencies": {
"property-expr": "^2.0.5",
"tiny-case": "^1.0.3",
"toposort": "^2.0.2",
"type-fest": "^2.19.0"
}
}
}
}

View file

@ -27,7 +27,9 @@
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^5.0.0-beta.3",
"ics": "^3.8.1",
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"ms": "^2.1.3",
"mysql": "^2.18.1",
"node-schedule": "^2.1.1",

View file

@ -0,0 +1,24 @@
export interface CreateCalendarCommand {
starttime: Date;
endtime: Date;
title: string;
content: string;
location: string;
allDay: boolean;
typeId: number;
}
export interface UpdateCalendarCommand {
id: string;
starttime: Date;
endtime: Date;
title: string;
content: string;
location: string;
allDay: boolean;
typeId: number;
}
export interface DeleteCalendarCommand {
id: string;
}

View file

@ -0,0 +1,96 @@
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<number>}
*/
static async create(createCalendar: CreateCalendarCommand): Promise<number> {
return await dataSource
.createQueryBuilder()
.insert()
.into(calendar)
.values({
starttime: createCalendar.starttime,
endtime: createCalendar.endtime,
title: createCalendar.title,
content: createCalendar.content,
location: createCalendar.location,
allDay: createCalendar.allDay,
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<void>}
*/
static async update(updateCalendar: UpdateCalendarCommand): Promise<void> {
let sequence = await dataSource
.getRepository(calendar)
.createQueryBuilder("calendar")
.where("id = :id", { id: updateCalendar.id })
.getOneOrFail()
.then((res) => {
return res.sequence;
});
return await dataSource
.createQueryBuilder()
.update(calendar)
.set({
starttime: updateCalendar.starttime,
endtime: updateCalendar.endtime,
title: updateCalendar.title,
content: updateCalendar.content,
location: updateCalendar.location,
allDay: updateCalendar.allDay,
type: await dataSource
.getRepository(calendarType)
.createQueryBuilder("type")
.where("id = :id", { id: updateCalendar.typeId })
.getOneOrFail(),
sequence: sequence + 1,
})
.where("id = :id", { id: updateCalendar.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed updating award", err);
});
}
/**
* @description delete calendar
* @param DeleteCalendarCommand
* @returns {Promise<void>}
*/
static async delete(deleteCalendar: DeleteCalendarCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(calendar)
.where("id = :id", { id: deleteCalendar.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed deleting calendar", err);
});
}
}

View file

@ -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;
}

View file

@ -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<number>}
*/
static async create(createCalendarType: CreateCalendarTypeCommand): Promise<number> {
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<void>}
*/
static async update(updateCalendarType: UpdateCalendarTypeCommand): Promise<void> {
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<void>}
*/
static async delete(deleteCalendarType: DeleteCalendarTypeCommand): Promise<void> {
return await dataSource
.createQueryBuilder()
.delete()
.from(calendarType)
.where("id = :id", { id: deleteCalendarType.id })
.execute()
.then(() => {})
.catch((err) => {
throw new InternalException("Failed deleting calendarType", err);
});
}
}

View file

@ -0,0 +1,201 @@
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<any> {
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<any> {
const id = 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<any> {
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<any> {
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<any> {
const starttime = req.body.starttime;
const endtime = req.body.endtime;
const title = req.body.title;
const content = req.body.content;
const location = req.body.location;
const allDay = req.body.allDay;
const typeId = req.body.typeId;
let createItem: CreateCalendarCommand = {
starttime,
endtime,
title,
content,
location,
allDay,
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<any> {
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<any> {
const id = req.params.id;
const starttime = req.body.starttime;
const endtime = req.body.endtime;
const title = req.body.title;
const content = req.body.content;
const location = req.body.location;
const allDay = req.body.allDay;
const typeId = req.body.typeId;
let updateItem: UpdateCalendarCommand = {
id,
starttime,
endtime,
title,
content,
location,
allDay,
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<any> {
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<any> {
const id = 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<any> {
const id = parseInt(req.params.id);
let deleteType: DeleteCalendarTypeCommand = {
id,
};
await CalendarTypeCommandHandler.delete(deleteType);
res.sendStatus(204);
}

View file

@ -0,0 +1,66 @@
import { Request, Response } from "express";
import CalendarService from "../service/calendarService";
import CalendarTypeService from "../service/calendarTypeService";
import { calendar } from "../entity/calendar";
import { createEvents } from "ics";
import moment from "moment";
/**
* @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<any> {
let types = Array.isArray(req.query.types) ? req.query.types : [req.query.types];
let items: Array<calendar> = [];
if (types.length == 0) {
let typeIds = await CalendarTypeService.getByTypes(types as Array<string>);
items = await CalendarService.getByTypes(typeIds.map((t) => t.id));
} else {
items = await CalendarService.getByTypeNSCDR();
}
let events = createEvents(
items.map((i) => ({
calName: process.env.CLUB_NAME,
uid: i.id,
sequence: 1,
start: moment(i.starttime)
.format("YYYY-M-D-H-m")
.split("-")
.map((a) => parseInt(a)) as [number, number, number, number, number],
end: moment(i.endtime)
.format("YYYY-M-D-H-m")
.split("-")
.map((a) => parseInt(a)) as [number, number, number, number, number],
title: i.title,
description: i.content,
location: i.location,
categories: [i.type.type],
created: moment(i.createdAt)
.format("YYYY-M-D-H-m")
.split("-")
.map((a) => parseInt(a)) as [number, number, number, number, number],
lastModified: moment(i.updatedAt)
.format("YYYY-M-D-H-m")
.split("-")
.map((a) => parseInt(a)) as [number, number, number, number, number],
transp: "OPAQUE" as "OPAQUE",
url: "https://www.ff-merching.de",
alarms: [
{
action: "display",
description: "Erinnerung",
trigger: {
minutes: 30,
before: true,
},
},
],
}))
);
res.type("ics").send(events.value);
}

View file

@ -36,6 +36,9 @@ import { protocolPresence } from "./entity/protocolPresence";
import { protocolVoting } from "./entity/protocolVoting";
import { protocolPrintout } from "./entity/protocolPrintout";
import { Protocol1729347911107 } from "./migrations/1729347911107-protocol";
import { calendar } from "./entity/calendar";
import { calendarType } from "./entity/calendarType";
import { Calendar1729947763295 } from "./migrations/1729947763295-calendar";
const dataSource = new DataSource({
type: DB_TYPE as any,
@ -71,6 +74,8 @@ const dataSource = new DataSource({
protocolPresence,
protocolVoting,
protocolPrintout,
calendar,
calendarType,
],
migrations: [
Initial1724317398939,
@ -82,6 +87,7 @@ const dataSource = new DataSource({
Memberdata1726301836849,
CommunicationFields1727439800630,
Protocol1729347911107,
Calendar1729947763295,
],
migrationsRun: true,
migrationsTransactionMode: "each",

52
src/entity/calendar.ts Normal file
View file

@ -0,0 +1,52 @@
import {
Column,
Entity,
ManyToOne,
PrimaryColumn,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
AfterUpdate,
BeforeUpdate,
} from "typeorm";
import { calendarType } from "./calendarType";
@Entity()
export class calendar {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column({ type: "datetime", nullable: false })
starttime: Date;
@Column({ type: "datetime", nullable: false })
endtime: Date;
@Column({ type: "varchar", length: 255, nullable: false })
title: string;
@Column({ type: "text", nullable: true })
content: string;
@Column({ type: "text", nullable: true })
location: string;
@Column({ type: "boolean", default: false })
allDay: boolean;
@Column({ type: "int", default: 1 })
sequence: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@ManyToOne(() => calendarType, (t) => t.calendar, {
nullable: false,
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
})
type: calendarType;
}

View file

@ -0,0 +1,24 @@
import { Column, Entity, OneToMany, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
import { calendar } from "./calendar";
@Entity()
export class calendarType {
@PrimaryColumn({ generated: "increment", type: "int" })
id: number;
@Column({ type: "varchar", length: 255 })
type: string;
@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",
onUpdate: "RESTRICT",
})
calendar: calendar[];
}

View file

@ -0,0 +1,34 @@
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,
starttime: record.starttime,
endtime: record.endtime,
title: record.title,
content: record.content,
location: record.location,
allDay: record.allDay,
createdAt: record.createdAt,
updatedAt: record.updatedAt,
type: CalendarTypeFactory.mapToSingle(record.type),
};
}
/**
* @description map records to calendar
* @param {Array<calendar>} records
* @returns {Array<CalendarViewModel>}
*/
public static mapToBase(records: Array<calendar>): Array<CalendarViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -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<calendarType>} records
* @returns {Array<CalendarTypeViewModel>}
*/
public static mapToBase(records: Array<calendarType>): Array<CalendarTypeViewModel> {
return records.map((r) => this.mapToSingle(r));
}
}

View file

@ -0,0 +1,68 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm";
import { DB_TYPE } from "../env.defaults";
export class Calendar1729947763295 implements MigrationInterface {
name = "Calendar1729947763295";
public async up(queryRunner: QueryRunner): Promise<void> {
const variableType_int = DB_TYPE == "mysql" ? "int" : "integer";
await queryRunner.createTable(
new Table({
name: "calendar_type",
columns: [
{ 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 },
],
})
);
await queryRunner.createTable(
new Table({
name: "calendar",
columns: [
{ name: "id", type: "varchar", length: "36", isPrimary: true, isGenerated: true, generationStrategy: "uuid" },
{ name: "starttime", type: "datetime", isNullable: false },
{ name: "endtime", type: "datetime", isNullable: false },
{ name: "title", type: "varchar", length: "255", isNullable: false },
{ name: "content", type: "text", isNullable: true },
{ name: "allDay", type: "tinyint", isNullable: false, default: 0 },
{ name: "location", type: "text", isNullable: true },
{ name: "sequence", type: variableType_int, default: 1 },
{ name: "createdAt", type: "datetime", precision: 6, isNullable: false, default: "CURRENT_TIMESTAMP(6)" },
{
name: "updatedAt",
type: "datetime",
precision: 6,
isNullable: false,
default: "CURRENT_TIMESTAMP(6)",
onUpdate: "CURRENT_TIMESTAMP(6)",
},
{ name: "typeId", type: variableType_int, isNullable: false },
],
})
);
await queryRunner.createForeignKey(
"calendar",
new TableForeignKey({
columnNames: ["typeId"],
referencedColumnNames: ["id"],
referencedTableName: "calendar_type",
onDelete: "RESTRICT",
onUpdate: "RESTRICT",
})
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
const table = await queryRunner.getTable("calendar");
const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("typeId") !== -1);
await queryRunner.dropForeignKey("calendar", foreignKey);
await queryRunner.dropTable("calendar");
await queryRunner.dropTable("calendar_type");
}
}

View file

@ -0,0 +1,98 @@
import express, { Request, Response } from "express";
import {
getCalendarItemById,
getAllCalendarItems,
getAllCalendarTypes,
getCalendarTypeById,
createCalendarItem,
createCalendarType,
updateCalendarItem,
updateCalendarType,
deleteCalendarItem,
deleteCalendarType,
} from "../../controller/admin/calendarController";
import PermissionHelper from "../../helpers/permissionHelper";
var router = express.Router({ mergeParams: true });
router.get(
"/items",
PermissionHelper.passCheckMiddleware("read", "club", "calendar"),
async (req: Request, res: Response) => {
await getAllCalendarItems(req, res);
}
);
router.get(
"/item/:id",
PermissionHelper.passCheckMiddleware("read", "club", "calendar"),
async (req: Request, res: Response) => {
await getCalendarItemById(req, res);
}
);
router.get(
"/types",
PermissionHelper.passCheckMiddleware("read", "settings", "calendar_type"),
async (req: Request, res: Response) => {
await getAllCalendarTypes(req, res);
}
);
router.get(
"/type/:id",
PermissionHelper.passCheckMiddleware("read", "settings", "calendar_type"),
async (req: Request, res: Response) => {
await getCalendarTypeById(req, res);
}
);
router.post(
"/item",
PermissionHelper.passCheckMiddleware("create", "club", "calendar"),
async (req: Request, res: Response) => {
await createCalendarItem(req, res);
}
);
router.post(
"/type",
PermissionHelper.passCheckMiddleware("create", "settings", "calendar_type"),
async (req: Request, res: Response) => {
await createCalendarType(req, res);
}
);
router.patch(
"/item/:id",
PermissionHelper.passCheckMiddleware("update", "club", "calendar"),
async (req: Request, res: Response) => {
await updateCalendarItem(req, res);
}
);
router.patch(
"/type/:id",
PermissionHelper.passCheckMiddleware("update", "settings", "calendar_type"),
async (req: Request, res: Response) => {
await updateCalendarType(req, res);
}
);
router.delete(
"/item/:id",
PermissionHelper.passCheckMiddleware("delete", "club", "calendar"),
async (req: Request, res: Response) => {
await deleteCalendarItem(req, res);
}
);
router.delete(
"/type/:id",
PermissionHelper.passCheckMiddleware("delete", "settings", "calendar_type"),
async (req: Request, res: Response) => {
await deleteCalendarType(req, res);
}
);
export default router;

View file

@ -10,6 +10,8 @@ import qualification from "./qualification";
import member from "./member";
import protocol from "./protocol";
import calendar from "./calendar";
import role from "./role";
import user from "./user";
@ -36,6 +38,7 @@ router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "setti
router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "member"), member);
router.use("/protocol", PermissionHelper.passCheckMiddleware("read", "club", "protocol"), protocol);
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);

View file

@ -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);

10
src/routes/public.ts Normal file
View file

@ -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;

View file

@ -0,0 +1,80 @@
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<Array<calendar>>}
*/
static async getAll(): Promise<Array<calendar>> {
return await dataSource
.getRepository(calendar)
.createQueryBuilder("calendar")
.leftJoinAndSelect("calendar.type", "type")
.getMany()
.then((res) => {
return res;
})
.catch((err) => {
throw new InternalException("calendars not found", err);
});
}
/**
* @description get calendar by id
* @returns {Promise<calendar>}
*/
static async getById(id: string): Promise<calendar> {
return await dataSource
.getRepository(calendar)
.createQueryBuilder("calendar")
.leftJoinAndSelect("calendar.type", "type")
.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<Array<calendar>>}
*/
static async getByTypes(types: Array<number>): Promise<Array<calendar>> {
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<Array<calendar>>}
*/
static async getByTypeNSCDR(): Promise<Array<calendar>> {
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);
});
}
}

View file

@ -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<Array<calendarType>>}
*/
static async getAll(): Promise<Array<calendarType>> {
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<calendarType>}
*/
static async getById(id: number): Promise<calendarType> {
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<Array<calendarType>>}
*/
static async getByTypes(names: Array<string>): Promise<Array<calendarType>> {
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);
});
}
}

View file

@ -10,6 +10,7 @@ export type PermissionModule =
| "executive_position"
| "communication"
| "membership_status"
| "calendar_type"
| "user"
| "role";
@ -45,12 +46,13 @@ export const permissionModules: Array<PermissionModule> = [
"executive_position",
"communication",
"membership_status",
"calendar_type",
"user",
"role",
];
export const permissionTypes: Array<PermissionType> = ["read", "create", "update", "delete"];
export const sectionsAndModules: SectionsAndModulesObject = {
club: ["member", "calendar", "newsletter", "protocol"],
settings: ["qualification", "award", "executive_position", "communication", "membership_status"],
settings: ["qualification", "award", "executive_position", "communication", "membership_status", "calendar_type"],
user: ["user", "role"],
};

View file

@ -0,0 +1,14 @@
import { CalendarTypeViewModel } from "./calendarType.models";
export interface CalendarViewModel {
id: string;
starttime: Date;
endtime: Date;
title: string;
content: string;
location: string;
allDay: boolean;
createdAt: Date;
updatedAt: Date;
type: CalendarTypeViewModel;
}

View file

@ -0,0 +1,6 @@
export interface CalendarTypeViewModel {
id: number;
type: string;
nscdr: boolean;
color: string;
}