diff --git a/config/default.toml b/config/default.toml index 8f581fc..5b76c38 100644 --- a/config/default.toml +++ b/config/default.toml @@ -35,13 +35,6 @@ secret = "REPLACE WITH SAFE RANDOM GENERATED SECRET" cookieMaxAge = 6048e+5 # 7 days -# Configuration for markdown rendering -[markdown] - -# The plugins used in the markdown parser -plugins = ["markdown-it-emoji"] - - # Configuration for logging [logging] diff --git a/package.json b/package.json index d68e268..ffd79fe 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "legit": "^1.0.7", "markdown-it": "^10.0.0", "markdown-it-emoji": "^1.4.0", + "markdown-it-html5-media": "^0.6.0", "pg": "^7.12.1", "pug": "^2.0.4", "reflect-metadata": "^0.1.13", diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index a6ae294..04d4cb3 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -6,6 +6,7 @@ import isEmail from "validator/lib/isEmail"; import dataaccess from "../lib/dataAccess"; import {BlacklistedError} from "../lib/errors/BlacklistedError"; import {NotAnAdminGqlError, NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; +import {GroupNotFoundError} from "../lib/errors/GroupNotFoundError"; import {InvalidLoginError} from "../lib/errors/InvalidLoginError"; import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; @@ -409,6 +410,25 @@ export function resolver(req: any, res: any): any { return new NotLoggedInGqlError(); } }, + async deleteGroup({groupId}: {groupId: number}) { + if (req.session.userId) { + const group = await models.Group.findByPk(groupId); + if (!group) { + res.status(status.BAD_REQUEST); + return new GroupNotFoundError(groupId).graphqlError; + } + if (group.creatorId === req.session.userId) { + await group.destroy(); + return true; + } else { + res.status(status.FORBIDDEN); + return new GraphQLError("You are not the group admin."); + } + } else { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + }, async joinGroup({id}: { id: number }) { if (req.session.userId) { try { @@ -502,6 +522,27 @@ export function resolver(req: any, res: any): any { return new NotLoggedInGqlError(); } }, + async deleteEvent({eventId}: {eventId: number}) { + if (req.session.userId) { + const event = await models.Event.findByPk(eventId, {include: [models.Group]}); + const user = await models.User.findByPk(req.session.userId); + if (!event) { + res.status(status.BAD_REQUEST); + return new GraphQLError(`No event with id '${eventId}' found.`); + } + const group = await event.group(); + if (await group.$has("rAdmins", user)) { + await event.destroy(); + return true; + } else { + res.status(status.FORBIDDEN); + return new NotAnAdminGqlError(); + } + } else { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + }, async joinEvent({eventId}: { eventId: number }) { if (req.session.userId) { const event = await models.Event.findByPk(eventId); diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index 376ed7d..99103c3 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -94,6 +94,9 @@ type Mutation { "Creates a new group with a given name and additional members" createGroup(name: String!, members: [ID!]): Group! + "Deletes a group." + deleteGroup(groupId: ID!): Boolean! + "Joins a group with the given id" joinGroup(id: ID!): Group @@ -109,6 +112,9 @@ type Mutation { "Creates a new event with a epoch due date on a group." createEvent(name: String, dueDate: String, groupId: ID!): Event! + "Deletes an event." + deleteEvent(eventId: ID!): Boolean! + "Joins a event." joinEvent(eventId: ID!): Event @@ -411,7 +417,10 @@ type Group { events(first: Int=10, offset: Int=0): [Event!]! @complexity(value: 1, multipliers: ["first"]) "If the user with the specified id has joined the group" - joined(userId: Int): Boolean! + joined(userId: ID): Boolean! + + "If the group is deletable by the specified or logged in user." + deletable(userId: ID): Boolean! } type Event { @@ -431,7 +440,10 @@ type Event { participants(first: Int=10, offset: Int=0): [User!]! @complexity(value: 1, multipliers: ["first"]) "Returns if the user with the specified id has joined the event" - joined(userId: Int): Boolean + joined(userId: ID): Boolean! + + "Returns if the Event is deletable by the logged in or specified user" + deletable(userId: ID): Boolean! } "respresents an access token entry with the value as the acutal token and expires as the date the token expires." diff --git a/src/lib/errors/BlacklistedError.ts b/src/lib/errors/BlacklistedError.ts index 288af38..a4a76c2 100644 --- a/src/lib/errors/BlacklistedError.ts +++ b/src/lib/errors/BlacklistedError.ts @@ -5,6 +5,6 @@ import {BaseError} from "./BaseError"; */ export class BlacklistedError extends BaseError { constructor(public phrases: string[], field: string = "input") { - super(`The ${field} contains the blacklisted words: ${phrases.join(",")}`); + super(`The ${field} contains the blacklisted words: ${phrases.join(", ")}`); } } diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts index 8dbbe03..84e8a32 100644 --- a/src/lib/markdown.ts +++ b/src/lib/markdown.ts @@ -1,21 +1,13 @@ -import * as config from "config"; import * as MarkdownIt from "markdown-it/lib"; -import globals from "./globals"; -namespace markdown { +const { html5Media } = require("markdown-it-html5-media"); +const mdEmoji = require("markdown-it-emoji"); - const md = new MarkdownIt(); +namespace markdown { - for (const pluginName of config.get("markdown.plugins") as string[]) { - try { - const plugin = require(pluginName); - if (plugin) { - md.use(plugin); - } - } catch (err) { - globals.logger.warn(`Markdown-it plugin '${pluginName}' not found!`); - } - } + const md = new MarkdownIt() + .use(html5Media) + .use(mdEmoji); /** * Renders the markdown string inline (without blocks). diff --git a/src/lib/models/Event.ts b/src/lib/models/Event.ts index 6d3fcd9..c0119ed 100644 --- a/src/lib/models/Event.ts +++ b/src/lib/models/Event.ts @@ -55,7 +55,7 @@ export class Event extends Model { * @param first * @param offset */ - public async participants({first, offset}: { first: number, offset: number }): Promise { + public async participants({first, offset}: { first?: number, offset?: number }): Promise { const limit = first ?? 10; offset = offset ?? 0; return await this.$get("rParticipants", {limit, offset}) as User[]; @@ -75,4 +75,20 @@ export class Event extends Model { return false; } } + + /** + * Returns if the event is deletable by the specified user + * @param userId + * @param request + */ + public async deletable({userId}: {userId: number}, request: any): Promise { + userId = userId ?? request.session.userId; + if (userId) { + const group = await this.$get("rGroup") as Group; + const user = await User.findByPk(userId); + return group.$has("rAdmins", user); + } else { + return false; + } + } } diff --git a/src/lib/models/Group.ts b/src/lib/models/Group.ts index bd0e86d..b2b5bf9 100644 --- a/src/lib/models/Group.ts +++ b/src/lib/models/Group.ts @@ -135,7 +135,7 @@ export class Group extends Model { * @param userId * @param request */ - public async joined({userId}: { userId: number }, request: any): Promise { + public async joined({userId}: { userId?: number }, request: any): Promise { userId = userId ?? request.session.userId; if (userId) { const members = await this.$get("rMembers", {where: {id: userId}}) as User[]; @@ -144,4 +144,18 @@ export class Group extends Model { return false; } } + + /** + * Returns if the group is deletable by the given user + * @param userId + * @param request + */ + public async deletable({userId}: {userId?: number}, request: any): Promise { + userId = userId ?? request.session.userId; + if (userId) { + return this.creatorId === userId; + } else { + return false; + } + } } diff --git a/src/routes/UploadRoute.ts b/src/routes/UploadRoute.ts index 420a473..18784e1 100644 --- a/src/routes/UploadRoute.ts +++ b/src/routes/UploadRoute.ts @@ -193,6 +193,7 @@ export class UploadRoute extends Route { private async processAndStoreImage(data: Buffer, width = 512, height = 512, fit: ImageFit = "cover"): Promise { const fileBasename = UploadRoute.getFileName() + "." + config.get("api.imageFormat"); + await fsx.ensureDir(this.dataDir); const filePath = path.join(this.dataDir, fileBasename); let image = await sharp(data) .resize(width, height, { diff --git a/yarn-error.log b/yarn-error.log index 00bd400..d0380b8 100644 --- a/yarn-error.log +++ b/yarn-error.log @@ -1,5 +1,5 @@ Arguments: - /usr/bin/node /usr/bin/yarn add @types/legit --dev + /usr/bin/node /usr/bin/yarn add @types/markdown-it-html5-media --dev PATH: /home/trivernis/Documents/Programming/node/greenvironment-server/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/opt/cuda/bin:/opt/intel/mediasdk/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl @@ -14,7 +14,7 @@ Platform: linux x64 Trace: - Error: https://registry.yarnpkg.com/@types%2flegit: Not found + Error: https://registry.yarnpkg.com/@types%2fmarkdown-it-html5-media: Not found at Request.params.callback [as _callback] (/usr/lib/node_modules/yarn/lib/cli.js:66947:18) at Request.self.callback (/usr/lib/node_modules/yarn/lib/cli.js:140665:22) at Request.emit (events.js:321:20) @@ -88,6 +88,8 @@ npm manifest: "typescript": "^3.7.2" }, "dependencies": { + "@types/body-parser": "^1.17.1", + "body-parser": "^1.19.0", "compression": "^1.7.4", "config": "^3.2.4", "connect-session-sequelize": "^6.0.0", @@ -108,6 +110,7 @@ npm manifest: "legit": "^1.0.7", "markdown-it": "^10.0.0", "markdown-it-emoji": "^1.4.0", + "markdown-it-html5-media": "^0.6.0", "pg": "^7.12.1", "pug": "^2.0.4", "reflect-metadata": "^0.1.13", @@ -165,7 +168,7 @@ Lockfile: resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.29.tgz#7cd933c902c4fc83046517a1bef973886d00bdb6" integrity sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw== - "@types/body-parser@*": + "@types/body-parser@*", "@types/body-parser@^1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.1.tgz#18fcf61768fb5c30ccc508c21d6fd2e8b3bf7897" integrity sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w== @@ -879,7 +882,7 @@ Lockfile: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== - body-parser@1.19.0: + body-parser@1.19.0, body-parser@^1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== @@ -1743,6 +1746,11 @@ Lockfile: engine.io-parser "~2.2.0" ws "^7.1.2" + entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + entities@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" @@ -3246,6 +3254,13 @@ Lockfile: resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz#9bee0e9a990a963ba96df6980c4fddb05dfb4dcc" integrity sha1-m+4OmpkKljupbfaYDE/dsF37Tcw= + markdown-it-html5-media@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/markdown-it-html5-media/-/markdown-it-html5-media-0.6.0.tgz#ed82bbdaec0545ba0879f0454c5a9d73171d3a76" + integrity sha512-3zxk00H5adz5n/LaC+yncTfZhSxa7igVKsKNfegUJw7gNAxMEA7D6YLHzSM+isJSnTLbiqU7663hfitv/ybzeg== + dependencies: + markdown-it "^8.4.0" + markdown-it@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" @@ -3257,6 +3272,17 @@ Lockfile: mdurl "^1.0.1" uc.micro "^1.0.5" + markdown-it@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" + integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ== + dependencies: + argparse "^1.0.7" + entities "~1.1.1" + linkify-it "^2.0.0" + mdurl "^1.0.1" + uc.micro "^1.0.5" + matchdep@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" diff --git a/yarn.lock b/yarn.lock index e877cd3..8c6b810 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1613,6 +1613,11 @@ engine.io@~3.4.0: engine.io-parser "~2.2.0" ws "^7.1.2" +entities@~1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" + integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== + entities@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" @@ -3116,6 +3121,13 @@ markdown-it-emoji@^1.4.0: resolved "https://registry.yarnpkg.com/markdown-it-emoji/-/markdown-it-emoji-1.4.0.tgz#9bee0e9a990a963ba96df6980c4fddb05dfb4dcc" integrity sha1-m+4OmpkKljupbfaYDE/dsF37Tcw= +markdown-it-html5-media@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/markdown-it-html5-media/-/markdown-it-html5-media-0.6.0.tgz#ed82bbdaec0545ba0879f0454c5a9d73171d3a76" + integrity sha512-3zxk00H5adz5n/LaC+yncTfZhSxa7igVKsKNfegUJw7gNAxMEA7D6YLHzSM+isJSnTLbiqU7663hfitv/ybzeg== + dependencies: + markdown-it "^8.4.0" + markdown-it@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" @@ -3127,6 +3139,17 @@ markdown-it@^10.0.0: mdurl "^1.0.1" uc.micro "^1.0.5" +markdown-it@^8.4.0: + version "8.4.2" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-8.4.2.tgz#386f98998dc15a37722aa7722084f4020bdd9b54" + integrity sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ== + dependencies: + argparse "^1.0.7" + entities "~1.1.1" + linkify-it "^2.0.0" + mdurl "^1.0.1" + uc.micro "^1.0.5" + matchdep@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e"