diff --git a/package-lock.json b/package-lock.json index 807bfd9..187b61a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", @@ -1440,6 +1442,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", @@ -1742,6 +1755,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", @@ -1777,6 +1799,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", @@ -2014,6 +2054,12 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "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", @@ -2159,6 +2205,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", @@ -2657,6 +2709,12 @@ "node": ">=0.8" } }, + "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", @@ -2665,6 +2723,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/ts-node": { "version": "10.7.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", @@ -2726,6 +2790,18 @@ "node": "*" } }, + "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", @@ -3120,6 +3196,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" + } } } } diff --git a/package.json b/package.json index a7988ad..c8a1140 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/command/calendarCommandHandler.ts b/src/command/calendarCommandHandler.ts index 082695f..9592347 100644 --- a/src/command/calendarCommandHandler.ts +++ b/src/command/calendarCommandHandler.ts @@ -43,6 +43,14 @@ export default abstract class CalendarCommandHandler { * @returns {Promise} */ static async update(updateCalendar: UpdateCalendarCommand): Promise { + 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) @@ -58,6 +66,7 @@ export default abstract class CalendarCommandHandler { .createQueryBuilder("type") .where("id = :id", { id: updateCalendar.typeId }) .getOneOrFail(), + sequence: sequence + 1, }) .where("id = :id", { id: updateCalendar.id }) .execute() diff --git a/src/controller/admin/calendarController.ts b/src/controller/admin/calendarController.ts index 7a23803..0b319b1 100644 --- a/src/controller/admin/calendarController.ts +++ b/src/controller/admin/calendarController.ts @@ -31,7 +31,7 @@ export async function getAllCalendarItems(req: Request, res: Response): Promise< * @returns {Promise<*>} */ export async function getCalendarItemById(req: Request, res: Response): Promise { - const id = parseInt(req.params.id); + const id = req.params.id; let item = await CalendarService.getById(id); res.json(CalendarFactory.mapToSingle(item)); diff --git a/src/controller/publicController.ts b/src/controller/publicController.ts index ecaf6e0..c1b2c91 100644 --- a/src/controller/publicController.ts +++ b/src/controller/publicController.ts @@ -3,6 +3,8 @@ import CalendarFactory from "../factory/admin/calendar"; 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 @@ -21,5 +23,49 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom items = await CalendarService.getByTypeNSCDR(); } - res.json(CalendarFactory.mapToBase(items)); + moment() + .format("YYYY-M-D-H-m") + .split("-") + .map((a) => parseInt(a)); + 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); } diff --git a/src/entity/calendar.ts b/src/entity/calendar.ts index ffdeceb..041a998 100644 --- a/src/entity/calendar.ts +++ b/src/entity/calendar.ts @@ -6,6 +6,8 @@ import { PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, + AfterUpdate, + BeforeUpdate, } from "typeorm"; import { calendarType } from "./calendarType"; @@ -32,6 +34,9 @@ export class calendar { @Column({ type: "boolean", default: false }) allDay: boolean; + @Column({ type: "int", default: 1 }) + sequence: number; + @CreateDateColumn() createdAt: Date; diff --git a/src/migrations/1729947763295-calendar.ts b/src/migrations/1729947763295-calendar.ts index 82146fe..bd30062 100644 --- a/src/migrations/1729947763295-calendar.ts +++ b/src/migrations/1729947763295-calendar.ts @@ -30,6 +30,7 @@ export class Calendar1729947763295 implements MigrationInterface { { 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", diff --git a/src/service/calendarService.ts b/src/service/calendarService.ts index 87aaac4..48c771d 100644 --- a/src/service/calendarService.ts +++ b/src/service/calendarService.ts @@ -25,7 +25,7 @@ export default abstract class CalendarService { * @description get calendar by id * @returns {Promise} */ - static async getById(id: number): Promise { + static async getById(id: string): Promise { return await dataSource .getRepository(calendar) .createQueryBuilder("calendar") @@ -68,7 +68,7 @@ export default abstract class CalendarService { .getRepository(calendar) .createQueryBuilder("calendar") .leftJoinAndSelect("calendar.type", "type") - .where("type.nscdr = :nscdr)", { nscdr: true }) + .where("type.nscdr = :nscdr", { nscdr: true }) .getMany() .then((res) => { return res;