From 8a35195040acffdd48e580623e99ff4a8ac6fd86 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 20 Jan 2020 20:31:00 +0100 Subject: [PATCH 1/6] Add chatCreate socket event --- src/lib/markdown.ts | 4 +++- src/routes/HomeRoute.ts | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts index 84e8a32..e4d2044 100644 --- a/src/lib/markdown.ts +++ b/src/lib/markdown.ts @@ -5,7 +5,9 @@ const mdEmoji = require("markdown-it-emoji"); namespace markdown { - const md = new MarkdownIt() + const md = new MarkdownIt({ + linkify: true, + }) .use(html5Media) .use(mdEmoji); diff --git a/src/routes/HomeRoute.ts b/src/routes/HomeRoute.ts index 1fafe4b..97cc980 100644 --- a/src/routes/HomeRoute.ts +++ b/src/routes/HomeRoute.ts @@ -47,6 +47,12 @@ class HomeRoute extends Route { globals.internalEmitter.on(InternalEvents.GQLPOSTCREATE, async (post: Post) => { socket.emit("post", Object.assign(post, {htmlContent: post.htmlContent})); }); + globals.internalEmitter.on(InternalEvents.CHATCREATE, async (chat: ChatRoom) => { + const user = await User.findByPk(socket.handshake.session.userId); + if (await chat.$has("rMembers", user)) { + socket.emit("chatCreate", chat); + } + }); }); const chats = await dataaccess.getAllChats(); From 164ecb77c64065ea9a496c6a74e9bf4263ea8a6c Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 20 Jan 2020 21:01:59 +0100 Subject: [PATCH 2/6] Add new resolver files --- src/app.ts | 10 ++++++++-- src/graphql/MutationResolver.ts | 6 ++++++ src/graphql/QueryResolver.ts | 5 +++++ src/lib/errors/BaseError.ts | 1 + src/lib/models/Post.ts | 1 + 5 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/graphql/MutationResolver.ts create mode 100644 src/graphql/QueryResolver.ts diff --git a/src/app.ts b/src/app.ts index 391f6d7..c6b1c54 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,7 +8,7 @@ import * as graphqlHTTP from "express-graphql"; import * as session from "express-session"; import sharedsession = require("express-socket.io-session"); import * as fsx from "fs-extra"; -import {buildSchema} from "graphql"; +import {buildSchema, GraphQLError} from "graphql"; import {importSchema} from "graphql-import"; import queryComplexity, {directiveEstimator, simpleEstimator} from "graphql-query-complexity"; import * as http from "http"; @@ -192,11 +192,17 @@ class App { }); // @ts-ignore - this.app.use("/graphql", graphqlHTTP(async (request, response, {variables}) => { + this.app.use("/graphql", graphqlHTTP(async (request: any, response: any, {variables}) => { response.setHeader("X-Max-Query-Complexity", config.get("api.maxQueryComplexity")); return { // @ts-ignore all context: {session: request.session}, + formatError: (err: GraphQLError | any) => { + if (err.statusCode) { + response.status(err.statusCode); + } + return err.graphqlError ?? err; + }, graphiql: config.get("api.graphiql"), rootValue: resolver(request, response), schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))), diff --git a/src/graphql/MutationResolver.ts b/src/graphql/MutationResolver.ts new file mode 100644 index 0000000..017f822 --- /dev/null +++ b/src/graphql/MutationResolver.ts @@ -0,0 +1,6 @@ +/** + * A class that provides methods to resolve mutations + */ +export class MutationResolver { + +} diff --git a/src/graphql/QueryResolver.ts b/src/graphql/QueryResolver.ts new file mode 100644 index 0000000..51a9828 --- /dev/null +++ b/src/graphql/QueryResolver.ts @@ -0,0 +1,5 @@ +/** + * A class that provides functions to resolve queries + */ +export class QueryResolver { +} diff --git a/src/lib/errors/BaseError.ts b/src/lib/errors/BaseError.ts index b4ad153..0b91661 100644 --- a/src/lib/errors/BaseError.ts +++ b/src/lib/errors/BaseError.ts @@ -8,6 +8,7 @@ export class BaseError extends Error { * The graphql error with a frontend error message */ public readonly graphqlError: GraphQLError; + public readonly statusCode: number = 400; constructor(message?: string, friendlyMessage?: string) { super(message); diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts index fad5013..b96b88b 100644 --- a/src/lib/models/Post.ts +++ b/src/lib/models/Post.ts @@ -1,5 +1,6 @@ import * as sqz from "sequelize"; import {BelongsTo, BelongsToMany, Column, CreatedAt, ForeignKey, Model, NotNull, Table} from "sequelize-typescript"; +import globals from "../globals"; import markdown from "../markdown"; import {Activity} from "./Activity"; import {PostVote, VoteType} from "./PostVote"; From 57091e522cb8ec8fe03a127e434d7bcfdc8ef1c2 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 20 Jan 2020 22:38:40 +0100 Subject: [PATCH 3/6] Add all resolvers and help types to classes - Add resolvers to Mutation class - Add reslovers to Query class - Add helper classes for several types and errors --- src/graphql/BaseResolver.ts | 17 + src/graphql/BlacklistedResult.ts | 6 + src/graphql/MutationResolver.ts | 463 +++++++++++++++++++++- src/graphql/QueryResolver.ts | 193 ++++++++- src/graphql/SearchResult.ts | 13 + src/graphql/Token.ts | 9 + src/lib/dataAccess.ts | 2 +- src/lib/errors/InvalidEmailError.ts | 10 + src/lib/errors/NotAGroupAdminError.ts | 14 + src/lib/errors/NotAnAdminError.ts | 13 + src/lib/errors/NotTheGroupCreatorError.ts | 14 + src/lib/errors/PostNotFoundError.ts | 10 + src/lib/errors/RequestNotFoundError.ts | 8 +- 13 files changed, 767 insertions(+), 5 deletions(-) create mode 100644 src/graphql/BaseResolver.ts create mode 100644 src/graphql/BlacklistedResult.ts create mode 100644 src/graphql/SearchResult.ts create mode 100644 src/graphql/Token.ts create mode 100644 src/lib/errors/InvalidEmailError.ts create mode 100644 src/lib/errors/NotAGroupAdminError.ts create mode 100644 src/lib/errors/NotAnAdminError.ts create mode 100644 src/lib/errors/NotTheGroupCreatorError.ts create mode 100644 src/lib/errors/PostNotFoundError.ts diff --git a/src/graphql/BaseResolver.ts b/src/graphql/BaseResolver.ts new file mode 100644 index 0000000..753af99 --- /dev/null +++ b/src/graphql/BaseResolver.ts @@ -0,0 +1,17 @@ +import {NotLoggedInGqlError} from "../lib/errors/graphqlErrors"; + +/** + * Base resolver class to provide common methods to all resolver classes + */ +export abstract class BaseResolver { + + /** + * Checks if the user is logged in and throws an exception if not + * @param request + */ + protected ensureLoggedIn(request: any) { + if (!request.session.userId) { + throw new NotLoggedInGqlError(); + } + } +} diff --git a/src/graphql/BlacklistedResult.ts b/src/graphql/BlacklistedResult.ts new file mode 100644 index 0000000..4139424 --- /dev/null +++ b/src/graphql/BlacklistedResult.ts @@ -0,0 +1,6 @@ +export class BlacklistedResult { + constructor( + public blacklisted: boolean, + public phrases: string[], + ) {} +} diff --git a/src/graphql/MutationResolver.ts b/src/graphql/MutationResolver.ts index 017f822..f70de4d 100644 --- a/src/graphql/MutationResolver.ts +++ b/src/graphql/MutationResolver.ts @@ -1,6 +1,467 @@ +import {GraphQLError} from "graphql"; +import * as yaml from "js-yaml"; +import isEmail from "validator/lib/isEmail"; +import dataaccess from "../lib/dataAccess"; +import {BlacklistedError} from "../lib/errors/BlacklistedError"; +import {GroupNotFoundError} from "../lib/errors/GroupNotFoundError"; +import {InvalidEmailError} from "../lib/errors/InvalidEmailError"; +import {NotAGroupAdminError} from "../lib/errors/NotAGroupAdminError"; +import {NotAnAdminError} from "../lib/errors/NotAnAdminError"; +import {NotTheGroupCreatorError} from "../lib/errors/NotTheGroupCreatorError"; +import {PostNotFoundError} from "../lib/errors/PostNotFoundError"; +import globals from "../lib/globals"; +import {InternalEvents} from "../lib/InternalEvents"; +import {Activity, BlacklistedPhrase, ChatMessage, ChatRoom, Event, Group, Post, Request, User} from "../lib/models"; +import {BaseResolver} from "./BaseResolver"; + +const legit = require("legit"); + /** * A class that provides methods to resolve mutations */ -export class MutationResolver { +export class MutationResolver extends BaseResolver { + + /** + * Accepts the usage of cookies and stores the session + * @param args + * @param request + */ + public acceptCookies(args: null, request: any): boolean { + request.session.cookiesAccepted = true; + return true; + } + + /** + * Loggs in and appends the user id to the session + * @param email + * @param passwordHash + * @param request + */ + public async login({email, passwordHash}: { email: string, passwordHash: string }, request: any): Promise { + const user = await dataaccess.getUserByLogin(email, passwordHash); + request.session.userId = user.id; + return user; + } + + /** + * Loggs out by removing the user from the session + * @param args + * @param request + */ + public logout(args: null, request: any) { + this.ensureLoggedIn(request); + delete request.session.userId; + request.session.save((err: any) => { + if (err) { + globals.logger.error(err.message); + globals.logger.debug(err.stack); + } + }); + } + + /** + * Registers a new user account + * @param username + * @param email + * @param passwordHash + * @param request + */ + public async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }, + request: any): Promise { + let mailValid = isEmail(email); + if (mailValid) { + try { + mailValid = (await legit(email)).isValid; + } catch (err) { + globals.logger.warn(`Mail legit check returned: ${err.message}`); + globals.logger.debug(err.stack); + mailValid = false; + } + } + if (!mailValid) { + throw new InvalidEmailError(email); + } + const user = await dataaccess.registerUser(username, email, passwordHash); + request.session.userId = user.id; + return user; + } + + /** + * Sets the frontend settings for the logged in user + * @param settings + * @param request + */ + public async setUserSettings({settings}: { settings: string }, request: any): Promise { + this.ensureLoggedIn(request); + const user = await User.findByPk(request.session.userId); + try { + user.frontendSettings = yaml.safeLoad(settings); + await user.save(); + return user.settings; + } catch (err) { + throw new GraphQLError("Invalid settings json."); + } + } + + /** + * Toggles a vote of a specific type on a post and returns the post and the result + * @param postId + * @param type + * @param request + */ + public async vote({postId, type}: { postId: number, type: dataaccess.VoteType }, request: any): + Promise<{ post: Post, voteType: dataaccess.VoteType }> { + this.ensureLoggedIn(request); + const post = await Post.findByPk(postId); + if (post) { + const voteType = await post.vote(request.session.userId, type); + return { + post, + voteType, + }; + } else { + throw new PostNotFoundError(postId); + } + } + + /** + * Creates a new post + * @param content + * @param activityId + * @param request + */ + public async createPost({content, activityId}: { content: string, activityId?: number }, request: any): + Promise { + this.ensureLoggedIn(request); + if (content.length > 2048) { + throw new GraphQLError("Content too long."); + } + const post = await dataaccess.createPost(content, request.session.userId, activityId); + globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post); + return post; + } + + /** + * Deletes a post if the user is either the author or a site admin. + * @param postId + * @param request + */ + public async deletePost({postId}: { postId: number }, request: any): Promise { + this.ensureLoggedIn(request); + const post = await Post.findByPk(postId, { + include: [{ + as: "rAuthor", + model: User, + }], + }); + const isAdmin = (await User.findOne({where: {id: request.session.userId}})).isAdmin; + if (post.rAuthor.id === request.session.userId || isAdmin) { + return await dataaccess.deletePost(post.id); + } else { + throw new GraphQLError("User is not author of the post."); + } + } + + /** + * Creates a chat with several members + * @param members + * @param request + */ + public async createChat({members}: { members?: number[] }, request: any): Promise { + this.ensureLoggedIn(request); + const chatMembers = [request.session.userId]; + if (members) { + chatMembers.push(...members); + } + return await dataaccess.createChat(...chatMembers); + } + + /** + * Sends a message into a chat the user has joined + * @param chatId + * @param content + * @param request + */ + public async sendMessage({chatId, content}: { chatId: number, content: string }, request: any): + Promise { + this.ensureLoggedIn(request); + const message = await dataaccess.sendChatMessage(request.session.userId, chatId, content); + globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message); + return message; + } + + /** + * Sends a request to a specific user + * @param receiver + * @param type + * @param request + */ + public async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }, request: any): + Promise { + this.ensureLoggedIn(request); + return dataaccess.createRequest(request.session.userId, receiver, type); + } + + /** + * Denies a request + * @param sender + * @param type + * @param request + */ + public async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }, request: any) { + this.ensureLoggedIn(request); + const user = await User.findByPk(request.session.userId); + await user.acceptRequest(sender, type); + return true; + } + + /** + * Accepts a request + * @param sender + * @param type + * @param request + */ + public async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }, request: any) { + this.ensureLoggedIn(request); + const user = await User.findByPk(request.session.userId); + await user.acceptRequest(sender, type); + return true; + } + + /** + * Removes a friend + * @param friendId + * @param request + */ + public async removeFriend({friendId}: { friendId: number }, request: any): Promise { + this.ensureLoggedIn(request); + const user = await User.findByPk(request.session.userId); + return user.removeFriend(friendId); + } + + /** + * Creates a new group + * @param name + * @param members + * @param request + */ + public async createGroup({name, members}: { name: string, members: number[] }, request: any): Promise { + this.ensureLoggedIn(request); + return await dataaccess.createGroup(name, request.session.userId, members); + } + + /** + * Deletes a group if the user is either the creator or a site admin + * @param groupId + * @param request + */ + public async deleteGroup({groupId}: { groupId: number }, request: any): Promise { + this.ensureLoggedIn(request); + const user = await User.findByPk(request.session.userId); + const group = await Group.findByPk(groupId); + if (group) { + if (user.isAdmin || group.creatorId === user.id) { + await group.destroy(); + return true; + } + } else { + throw new GroupNotFoundError(groupId); + } + } + + /** + * Joins a group + * @param groupId + * @param request + */ + public async joinGroup({groupId}: { groupId: number }, request: any): Promise { + this.ensureLoggedIn(request); + return dataaccess.changeGroupMembership(groupId, request.session.userId, + dataaccess.MembershipChangeAction.ADD); + } + + /** + * Leaves a group + * @param groupId + * @param request + */ + public async leaveGroup({groupId}: { groupId: number }, request: any): Promise { + this.ensureLoggedIn(request); + return dataaccess.changeGroupMembership(groupId, request.session.userId, + dataaccess.MembershipChangeAction.REMOVE); + } + + /** + * Adds a user to the group admins + * @param groupId + * @param userId + * @param request + */ + public async addGroupAdmin({groupId, userId}: { groupId: number, userId: number }, request: any): Promise { + this.ensureLoggedIn(request); + const group = await Group.findByPk(groupId); + const user: User = await User.findByPk(request.session.userId); + if (group && !(await group.$has("rAdmins", user)) && (await group.creator()) !== user.id) { + throw new NotAGroupAdminError(groupId); + } + return dataaccess.changeGroupMembership(groupId, userId, + dataaccess.MembershipChangeAction.OP); + } + + /** + * Removes an admin from a group + * @param groupId + * @param userId + * @param request + */ + public async removeGroupAdmin({groupId, userId}: { groupId: number, userId: number }, + request: any): Promise { + this.ensureLoggedIn(request); + const group = await Group.findByPk(groupId); + const isCreator = Number(group.creatorId) === Number(request.session.userId); + const userIsCreator = Number(group.creatorId) === Number(userId); + if (group && !isCreator && Number(userId) !== Number(request.session.userId)) { + throw new NotTheGroupCreatorError(groupId); + } else if (userIsCreator) { + throw new GraphQLError( + "You are not allowed to remove a creator as an admin."); + } + return await dataaccess.changeGroupMembership(groupId, userId, + dataaccess.MembershipChangeAction.DEOP); + } + + /** + * Creates a new event for a specific group + * @param name + * @param dueDate + * @param groupId + * @param request + */ + public async createEvent({name, dueDate, groupId}: { name: string, dueDate: string, groupId: number }, + request: any): Promise { + this.ensureLoggedIn(request); + const date = new Date(Number(dueDate)); + const user: User = await User.findByPk(request.session.userId); + const group = await Group.findByPk(groupId, {include: [{association: "rAdmins"}]}); + if (!(await group.$has("rAdmins", user))) { + throw new NotAGroupAdminError(groupId); + } + const blacklisted = await dataaccess.checkBlacklisted(name); + if (blacklisted.length > 0) { + throw new BlacklistedError(blacklisted.map((p) => p.phrase), "event name"); + } + return group.$create("rEvent", {name, dueDate: date}); + } + + /** + * Deletes an event + * @param eventId + * @param request + */ + public async deleteEvent({eventId}: { eventId: number }, request: any): Promise { + this.ensureLoggedIn(request); + const event = await Event.findByPk(eventId, {include: [Group]}); + const user = await User.findByPk(request.session.userId); + const group = await event.group(); + if (await group.$has("rAdmins", user)) { + await event.destroy(); + return true; + } else { + throw new NotAGroupAdminError(group.id); + } + } + + /** + * Joins an event + * @param eventId + * @param request + */ + public async joinEvent({eventId}: { eventId: number }, request: any): Promise { + this.ensureLoggedIn(request); + const event = await Event.findByPk(eventId); + const self = await User.findByPk(request.session.userId); + await event.$add("rParticipants", self); + return event; + } + + /** + * Leaves an event + * @param eventId + * @param request + */ + public async leaveEvent({eventId}: { eventId: number }, request: any): Promise { + this.ensureLoggedIn(request); + const event = await Event.findByPk(eventId); + const self = await User.findByPk(request.session.userId); + await event.$remove("rParticipants", self); + return event; + } + + /** + * Creates a new activity or throws an error if the activity already exists + * @param name + * @param description + * @param points + * @param request + */ + public async createActivity({name, description, points}: { name: string, description: string, points: number }, + request: any): Promise { + this.ensureLoggedIn(request.session.userId); + const user = await User.findByPk(request.session.userId); + if (!user.isAdmin) { + throw new NotAnAdminError(); + } + const nameExists = await Activity.findOne({where: {name}}); + if (!nameExists) { + return Activity.create({name, description, points}); + } else { + throw new GraphQLError(`An activity with the name '${name}' already exists.`); + } + } + + /** + * Adds a phrase to the blaclist + * @param phrase + * @param languageCode + * @param request + */ + public async addToBlacklist({phrase, languageCode}: {phrase: string, languageCode?: string}, request: any): + Promise { + this.ensureLoggedIn(request); + const user = await User.findByPk(request.session.userId); + if (!user.isAdmin) { + throw new NotAnAdminError(); + } + const phraseExists = await BlacklistedPhrase.findOne( + {where: {phrase, language: languageCode}}); + if (!phraseExists) { + await BlacklistedPhrase.create({phrase, language: languageCode}); + return true; + } else { + return false; + } + } + /** + * Removes a phrase from the blacklist + * @param phrase + * @param languageCode + * @param request + */ + public async removeFromBlacklist({phrase, languageCode}: {phrase: string, languageCode: string}, request: any): + Promise { + this.ensureLoggedIn(request); + const user = await User.findByPk(request.session.userId); + if (!user.isAdmin) { + throw new NotAnAdminError(); + } + const phraseEntry = await BlacklistedPhrase.findOne( + {where: {phrase, language: languageCode}}); + if (phraseEntry) { + await phraseEntry.destroy(); + return true; + } else { + return false; + } + } } diff --git a/src/graphql/QueryResolver.ts b/src/graphql/QueryResolver.ts index 51a9828..91b94b6 100644 --- a/src/graphql/QueryResolver.ts +++ b/src/graphql/QueryResolver.ts @@ -1,5 +1,196 @@ +import {GraphQLError} from "graphql"; +import {Op} from "sequelize"; +import dataaccess from "../lib/dataAccess"; +import {ChatNotFoundError} from "../lib/errors/ChatNotFoundError"; +import {PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; +import {GroupNotFoundError} from "../lib/errors/GroupNotFoundError"; +import {InvalidLoginError} from "../lib/errors/InvalidLoginError"; +import {RequestNotFoundError} from "../lib/errors/RequestNotFoundError"; +import {UserNotFoundError} from "../lib/errors/UserNotFoundError"; +import { + Activity, + BlacklistedPhrase, + ChatRoom, + Event, + Group, + Post, + Request, + User, +} from "../lib/models"; +import {BaseResolver} from "./BaseResolver"; +import {BlacklistedResult} from "./BlacklistedResult"; +import {SearchResult} from "./SearchResult"; +import {Token} from "./Token"; + /** * A class that provides functions to resolve queries */ -export class QueryResolver { +export class QueryResolver extends BaseResolver { + + /** + * Gets a user by id or handle + * @param userId + * @param handle + */ + public async getUser({userId, handle}: {userId?: number, handle?: string}): Promise { + let user: User; + if (userId) { + user = await User.findByPk(userId); + } else if (handle) { + user = await User.findOne({where: {handle}}); + } else { + throw new GraphQLError("No handle or userId provided"); + } + if (user) { + return user; + } else { + throw new UserNotFoundError(userId ?? handle); + } + } + + /** + * Returns the instance of the currently logged in user + * @param args + * @param request + */ + public async getSelf(args: null, request: any): Promise { + this.ensureLoggedIn(request); + return User.findByPk(request.session.userId); + } + + /** + * Returns a post for a given post id. + * @param postId + */ + public async getPost({postId}: {postId: number}): Promise { + const post = await Post.findByPk(postId); + if (post) { + return post; + } else { + throw new PostNotFoundGqlError(postId); + } + } + + /** + * Returns a chat for a given chat id + * @param chatId + */ + public async getChat({chatId}: {chatId: number}): Promise { + const chat = await ChatRoom.findByPk(chatId); + if (chat) { + return chat; + } else { + throw new ChatNotFoundError(chatId); + } + } + + /** + * Returns a group for a given group id. + * @param groupId + */ + public async getGroup({groupId}: {groupId: number}): Promise { + const group = await Group.findByPk(groupId); + if (group) { + return group; + } else { + throw new GroupNotFoundError(groupId); + } + } + + /** + * Returns the request for a given id. + * @param requestId + */ + public async getRequest({requestId}: {requestId: number}): Promise { + const request = await Request.findByPk(requestId); + if (request) { + return request; + } else { + throw new RequestNotFoundError(requestId); + } + } + + /** + * Searches for posts, groups, users, events and returns a search result. + * @param query + * @param first + * @param offset + */ + public async search({query, first, offset}: {query: number, first: number, offset: number}): Promise { + const limit = first; + const users = await User.findAll({ + limit, + offset, + where: { + [Op.or]: [ + {handle: {[Op.iRegexp]: query}}, + {username: {[Op.iRegexp]: query}}, + ], + }, + }); + const groups = await Group.findAll({ + limit, + offset, + where: {name: {[Op.iRegexp]: query}}, + }); + const posts = await Post.findAll({ + limit, + offset, + where: {content: {[Op.iRegexp]: query}}, + }); + const events = await Event.findAll({ + limit, + offset, + where: {name: {[Op.iRegexp]: query}}, + }); + return new SearchResult(users, groups, posts, events); + } + + /** + * Returns the posts with a specific sorting + * @param first + * @param offset + * @param sort + */ + public async getPosts({first, offset, sort}: {first: number, offset: number, sort: dataaccess.SortType}): + Promise { + return await dataaccess.getPosts(first, offset, sort); + } + + /** + * Returns all activities + */ + public async getActivities(): Promise { + return Activity.findAll(); + } + + /** + * Returns the token for a user by login + * @param email + * @param passwordHash + */ + public async getToken({email, passwordHash}: {email: string, passwordHash: string}): Promise { + const user = await dataaccess.getUserByLogin(email, passwordHash); + return new Token(await user.token(), Number(user.authExpire).toString()); + } + + /** + * Returns if a input phrase contains blacklisted phrases and which one + * @param phrase + */ + public async blacklisted({phrase}: {phrase: string}): Promise { + const phrases = await dataaccess.checkBlacklisted(phrase); + return new BlacklistedResult(phrases.length > 0, phrases + .map((p) => p.phrase)); + } + + /** + * Returns all blacklisted phrases with pagination + * @param first + * @param offset + */ + public async getBlacklistedPhrases({first, offset}: {first: number, offset: number}): Promise { + return (await BlacklistedPhrase.findAll({limit: first, offset})) + .map((p) => p.phrase); + } } diff --git a/src/graphql/SearchResult.ts b/src/graphql/SearchResult.ts new file mode 100644 index 0000000..739f023 --- /dev/null +++ b/src/graphql/SearchResult.ts @@ -0,0 +1,13 @@ +import {Group, Post, User, Event} from "../lib/models"; + +/** + * A class to wrap search results returned by the search resolver + */ +export class SearchResult { + constructor( + public users: User[], + public groups: Group[], + public posts: Post[], + public events: Event[], + ) {} +} diff --git a/src/graphql/Token.ts b/src/graphql/Token.ts new file mode 100644 index 0000000..f56d369 --- /dev/null +++ b/src/graphql/Token.ts @@ -0,0 +1,9 @@ +/** + * A class representing a token that can be used with bearer authentication + */ +export class Token { + constructor( + public value: string, + public expires: string, + ) {} +} diff --git a/src/lib/dataAccess.ts b/src/lib/dataAccess.ts index 11b7d46..8fc11c9 100644 --- a/src/lib/dataAccess.ts +++ b/src/lib/dataAccess.ts @@ -205,7 +205,7 @@ namespace dataaccess { * Deletes a post * @param postId */ - export async function deletePost(postId: number): Promise { + export async function deletePost(postId: number): Promise { try { const post = await models.Post.findByPk(postId, {include: [{model: Activity}, {association: "rAuthor"}]}); const activity = await post.activity(); diff --git a/src/lib/errors/InvalidEmailError.ts b/src/lib/errors/InvalidEmailError.ts new file mode 100644 index 0000000..ea97b66 --- /dev/null +++ b/src/lib/errors/InvalidEmailError.ts @@ -0,0 +1,10 @@ +import {BaseError} from "./BaseError"; + +/** + * An error that is thrown when a user tries to register with an invalid email + */ +export class InvalidEmailError extends BaseError { + constructor(email: string) { + super(`'${email}' is not a valid email address!`); + } +} diff --git a/src/lib/errors/NotAGroupAdminError.ts b/src/lib/errors/NotAGroupAdminError.ts new file mode 100644 index 0000000..2538c37 --- /dev/null +++ b/src/lib/errors/NotAGroupAdminError.ts @@ -0,0 +1,14 @@ +import * as status from "http-status"; +import {BaseError} from "./BaseError"; + +/** + * An error that is thrown when a non-admin tries to perform an admin action + */ +export class NotAGroupAdminError extends BaseError { + public readonly statusCode = status.FORBIDDEN; + + constructor(groupId: number) { + super(`You are not an admin of '${groupId}'`); + } + +} diff --git a/src/lib/errors/NotAnAdminError.ts b/src/lib/errors/NotAnAdminError.ts new file mode 100644 index 0000000..b9454d0 --- /dev/null +++ b/src/lib/errors/NotAnAdminError.ts @@ -0,0 +1,13 @@ +import {BaseError} from "./BaseError"; + +/** + * An error that is thrown when a non admin tries to perform an admin action + */ +export class NotAnAdminError extends BaseError { + + public readonly statusCode = httpStatus.FORBIDDEN; + + constructor() { + super("You are not a site admin!"); + } +} diff --git a/src/lib/errors/NotTheGroupCreatorError.ts b/src/lib/errors/NotTheGroupCreatorError.ts new file mode 100644 index 0000000..7b1e748 --- /dev/null +++ b/src/lib/errors/NotTheGroupCreatorError.ts @@ -0,0 +1,14 @@ +import * as status from "http-status"; +import {BaseError} from "./BaseError"; + +/** + * An error that is thrown when a non-admin tries to perform an admin action + */ +export class NotTheGroupCreatorError extends BaseError { + public readonly statusCode = status.FORBIDDEN; + + constructor(groupId: number) { + super(`You are not the creator of '${groupId}'`); + } + +} diff --git a/src/lib/errors/PostNotFoundError.ts b/src/lib/errors/PostNotFoundError.ts new file mode 100644 index 0000000..76fcf77 --- /dev/null +++ b/src/lib/errors/PostNotFoundError.ts @@ -0,0 +1,10 @@ +import {BaseError} from "./BaseError"; + +/** + * An error that is thrown when a post was not found + */ +export class PostNotFoundError extends BaseError { + constructor(postId: number) { + super(`Post '${postId}' not found!`); + } +} diff --git a/src/lib/errors/RequestNotFoundError.ts b/src/lib/errors/RequestNotFoundError.ts index cd94fc5..46fa064 100644 --- a/src/lib/errors/RequestNotFoundError.ts +++ b/src/lib/errors/RequestNotFoundError.ts @@ -5,7 +5,11 @@ import {BaseError} from "./BaseError"; * An error that is thrown when a request for a sender, receiver and type was not found */ export class RequestNotFoundError extends BaseError { - constructor(sender: number, receiver: number, type: dataaccess.RequestType) { - super(`Request with sender '${sender}' and receiver '${receiver}' of type '${type}' not found.`); + constructor(sender: number, receiver?: number, type?: dataaccess.RequestType) { + if (!receiver) { + super(`Request with id '${sender} not found.'`); + } else { + super(`Request with sender '${sender}' and receiver '${receiver}' of type '${type}' not found.`); + } } } From b27ed1def2f7b2d914cb43bf7834ea0babee3b49 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 20 Jan 2020 23:25:39 +0100 Subject: [PATCH 4/6] Replace shorthand resolver with new classes --- package.json | 2 + src/app.ts | 4 + src/graphql/QueryResolver.ts | 5 +- src/graphql/resolvers.ts | 624 +---------------------------------- src/lib/models/User.ts | 2 +- yarn.lock | 5 + 6 files changed, 16 insertions(+), 626 deletions(-) diff --git a/package.json b/package.json index ffd79fe..e575457 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ }, "dependencies": { "@types/body-parser": "^1.17.1", + "@types/lodash": "^4.14.149", "body-parser": "^1.19.0", "compression": "^1.7.4", "config": "^3.2.4", @@ -79,6 +80,7 @@ "http-status": "^1.3.2", "js-yaml": "^3.13.1", "legit": "^1.0.7", + "lodash": "^4.17.15", "markdown-it": "^10.0.0", "markdown-it-emoji": "^1.4.0", "markdown-it-html5-media": "^0.6.0", diff --git a/src/app.ts b/src/app.ts index c6b1c54..eeebd33 100644 --- a/src/app.ts +++ b/src/app.ts @@ -200,7 +200,11 @@ class App { formatError: (err: GraphQLError | any) => { if (err.statusCode) { response.status(err.statusCode); + } else { + response.status(400); } + logger.debug(err.message); + logger.silly(err.stack); return err.graphqlError ?? err; }, graphiql: config.get("api.graphiql"), diff --git a/src/graphql/QueryResolver.ts b/src/graphql/QueryResolver.ts index 91b94b6..04d7cd8 100644 --- a/src/graphql/QueryResolver.ts +++ b/src/graphql/QueryResolver.ts @@ -4,7 +4,6 @@ import dataaccess from "../lib/dataAccess"; import {ChatNotFoundError} from "../lib/errors/ChatNotFoundError"; import {PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; import {GroupNotFoundError} from "../lib/errors/GroupNotFoundError"; -import {InvalidLoginError} from "../lib/errors/InvalidLoginError"; import {RequestNotFoundError} from "../lib/errors/RequestNotFoundError"; import {UserNotFoundError} from "../lib/errors/UserNotFoundError"; import { @@ -17,15 +16,15 @@ import { Request, User, } from "../lib/models"; -import {BaseResolver} from "./BaseResolver"; import {BlacklistedResult} from "./BlacklistedResult"; +import {MutationResolver} from "./MutationResolver"; import {SearchResult} from "./SearchResult"; import {Token} from "./Token"; /** * A class that provides functions to resolve queries */ -export class QueryResolver extends BaseResolver { +export class QueryResolver extends MutationResolver { /** * Gets a user by id or handle diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 04d4cb3..0199d29 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -1,21 +1,4 @@ -import {GraphQLError} from "graphql"; -import * as status from "http-status"; -import * as yaml from "js-yaml"; -import {Op} from "sequelize"; -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"; -import * as models from "../lib/models"; -import {is} from "../lib/regex"; - -const legit = require("legit"); - -// tslint:disable:completed-docs +import {QueryResolver} from "./QueryResolver"; /** * Returns the resolvers for the graphql api. @@ -23,608 +6,5 @@ const legit = require("legit"); * @param res - the response object */ export function resolver(req: any, res: any): any { - return { - async search({first, offset, query}: { first: number, offset: number, query: string }) { - const limit = first; - const users = await models.User.findAll({ - limit, - offset, - where: { - [Op.or]: [ - {handle: {[Op.iRegexp]: query}}, - {username: {[Op.iRegexp]: query}}, - ], - }, - }); - const groups = await models.Group.findAll({ - limit, - offset, - where: {name: {[Op.iRegexp]: query}}, - }); - const posts = await models.Post.findAll({ - limit, - offset, - where: {content: {[Op.iRegexp]: query}}, - }); - const events = await models.Event.findAll({ - limit, - offset, - where: {name: {[Op.iRegexp]: query}}, - }); - return {users, posts, groups, events}; - }, - async findUser({first, offset, name, handle}: - { first: number, offset: number, name: string, handle: string }) { - res.status(status.MOVED_PERMANENTLY); - if (name) { - return models.User.findAll({where: {username: {[Op.like]: `%${name}%`}}, offset, limit: first}); - } else if (handle) { - return models.User.findAll({where: {handle: {[Op.like]: `%${handle}%`}}, offset, limit: first}); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No search parameters provided."); - } - }, - async getSelf() { - if (req.session.userId) { - return models.User.findByPk(req.session.userId); - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async getUser({userId, handle}: { userId: number, handle: string }) { - if (handle) { - return await dataaccess.getUserByHandle(handle); - } else if (userId) { - return models.User.findByPk(userId); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No userId or handle provided."); - } - }, - async getPost({postId}: { postId: number }) { - if (postId) { - return await dataaccess.getPost(postId); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No postId given."); - } - }, - async getChat({chatId}: { chatId: number }) { - if (chatId) { - return models.ChatRoom.findByPk(chatId); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No chatId given."); - } - }, - async getGroup({groupId}: { groupId: number }) { - if (groupId) { - return models.Group.findByPk(groupId); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No group id given."); - } - }, - async getRequest({requestId}: { requestId: number }) { - if (requestId) { - return models.Request.findByPk(requestId); - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No requestId given."); - } - }, - async blacklisted({phrase}: {phrase: string}) { - const phrases = await dataaccess.checkBlacklisted(phrase); - return { - blacklisted: phrases.length > 0, - phrases: phrases.map((p) => p.phrase), - }; - }, - async getBlacklistedPhrases({first, offset}: {first: number, offset: number}) { - return (await models.BlacklistedPhrase.findAll({limit: first, offset})).map((p) => p.phrase); - }, - acceptCookies() { - req.session.cookiesAccepted = true; - return true; - }, - async login({email, passwordHash}: { email: string, passwordHash: string }) { - if (email && passwordHash) { - try { - const user = await dataaccess.getUserByLogin(email, passwordHash); - req.session.userId = user.id; - return user; - } catch (err) { - globals.logger.warn(err.message); - globals.logger.debug(err.stack); - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No email or password given."); - } - }, - logout() { - if (req.session.userId) { - delete req.session.userId; - req.session.save((err: any) => { - if (err) { - globals.logger.error(err.message); - globals.logger.debug(err.stack); - } - }); - return true; - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async getToken({email, passwordHash}: { email: string, passwordHash: string }) { - if (email && passwordHash) { - try { - const user = await dataaccess.getUserByLogin(email, passwordHash); - if (!user) { - res.status(status.BAD_REQUEST); - return new InvalidLoginError(email); - } else { - return { - expires: Number(user.authExpire), - value: user.token(), - }; - } - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No email or password specified."); - } - }, - async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) { - if (username && email && passwordHash) { - let mailValid = isEmail(email); - if (mailValid) { - try { - mailValid = (await legit(email)).isValid; - } catch (err) { - globals.logger.warn(`Mail legit check returned: ${err.message}`); - globals.logger.debug(err.stack); - mailValid = false; - } - } - if (!mailValid) { - res.status(status.BAD_REQUEST); - return new GraphQLError(`'${email}' is not a valid email address!`); - } - try { - const user = await dataaccess.registerUser(username, email, passwordHash); - req.session.userId = user.id; - return user; - } catch (err) { - globals.logger.warn(err.message); - globals.logger.debug(err.stack); - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No username, email or password given."); - } - }, - async setUserSettings({settings}: { settings: string }) { - if (req.session.userId) { - const user = await models.User.findByPk(req.session.userId); - try { - user.frontendSettings = yaml.safeLoad(settings); - await user.save(); - return user.settings; - } catch (err) { - res.status(status.BAD_REQUEST); - return new GraphQLError("Invalid settings json."); - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async vote({postId, type}: { postId: number, type: dataaccess.VoteType }) { - if (postId && type) { - if (req.session.userId) { - const post = await models.Post.findByPk(postId); - if (post) { - const voteType = await post.vote(req.session.userId, type); - return { - post, - voteType, - }; - } else { - res.status(status.BAD_REQUEST); - return new PostNotFoundGqlError(postId); - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No postId or type given."); - } - }, - async createPost({content, activityId}: { content: string, activityId: number }) { - if (content) { - if (req.session.userId) { - if (content.length > 2048) { - return new GraphQLError("Content too long."); - } else { - try { - const post = await dataaccess.createPost(content, req.session.userId, activityId); - globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post); - return post; - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("Can't create empty post."); - } - }, - async deletePost({postId}: { postId: number }) { - if (postId) { - const post = await models.Post.findByPk(postId, { - include: [{ - as: "rAuthor", - model: models.User, - }], - }); - const isAdmin = (await models.User.findOne({where: {id: req.session.userId}})).isAdmin; - if (post.rAuthor.id === req.session.userId || isAdmin) { - try { - return await dataaccess.deletePost(post.id); - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.FORBIDDEN); - return new GraphQLError("User is not author of the post."); - } - } else { - return new GraphQLError("No postId given."); - } - }, - async createChat({members}: { members: number[] }) { - if (req.session.userId) { - const chatMembers = [req.session.userId]; - if (members) { - chatMembers.push(...members); - } - return await dataaccess.createChat(...chatMembers); - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async sendMessage({chatId, content}: { chatId: number, content: string }) { - if (!req.session.userId) { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - if (chatId && content) { - try { - const message = await dataaccess.sendChatMessage(req.session.userId, chatId, content); - globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message); - return message; - } catch (err) { - globals.logger.warn(err.message); - globals.logger.debug(err.stack); - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No chatId or content given."); - } - }, - async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }) { - if (!req.session.userId) { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - if (receiver && type) { - try { - return await dataaccess.createRequest(req.session.userId, receiver, type); - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No receiver or type given."); - } - }, - async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { - if (!req.session.userId) { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - if (sender && type) { - const user = await models.User.findByPk(req.session.userId); - await user.denyRequest(sender, type); - return true; - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No sender or type given."); - } - }, - async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { - if (!req.session.userId) { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - if (sender && type) { - try { - const user = await models.User.findByPk(req.session.userId); - await user.acceptRequest(sender, type); - return true; - } catch (err) { - globals.logger.warn(err.message); - globals.logger.debug(err.stack); - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.BAD_REQUEST); - return new GraphQLError("No sender or type given."); - } - }, - async removeFriend({friendId}: { friendId: number }) { - if (req.session.userId) { - const self = await models.User.findByPk(req.session.userId); - return await self.removeFriend(friendId); - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async getPosts({first, offset, sort}: { first: number, offset: number, sort: dataaccess.SortType }) { - return await dataaccess.getPosts(first, offset, sort); - }, - async createGroup({name, members}: { name: string, members: number[] }) { - if (req.session.userId) { - try { - return await dataaccess.createGroup(name, req.session.userId, members); - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - 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 { - return await dataaccess - .changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.ADD); - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async leaveGroup({id}: { id: number }) { - if (req.session.userId) { - try { - return await dataaccess - .changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.REMOVE); - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async addGroupAdmin({groupId, userId}: { groupId: number, userId: number }) { - if (req.session.userId) { - const group = await models.Group.findByPk(groupId); - const self = await models.User.findByPk(req.session.userId); - if (group && !(await group.$has("rAdmins", self)) && (await group.creator()) !== self.id) { - res.status(status.FORBIDDEN); - return new GraphQLError("You are not a group admin!"); - } - try { - return await dataaccess - .changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.OP); - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async removeGroupAdmin({groupId, userId}: { groupId: number, userId: number }) { - if (req.session.userId) { - const group = await models.Group.findByPk(groupId); - const isCreator = Number(group.creatorId) === Number(req.session.userId); - const userIsCreator = Number(group.creatorId) === Number(userId); - if (group && !isCreator && Number(userId) !== Number(req.session.userId)) { - res.status(status.FORBIDDEN); - return new GraphQLError("You are not the group creator!"); - } else if (userIsCreator) { - res.status(status.FORBIDDEN); - return new GraphQLError("You are not allowed to remove a creator as an admin."); - } - try { - return await dataaccess - .changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.DEOP); - } catch (err) { - res.status(status.BAD_REQUEST); - return err.graphqlError ?? new GraphQLError(err.message); - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async createEvent({name, dueDate, groupId}: { name: string, dueDate: string, groupId: number }) { - if (req.session.userId) { - const date = new Date(Number(dueDate)); - const group = await models.Group.findByPk(groupId, {include: [{association: "rAdmins"}]}); - if (group.rAdmins.find((x) => x.id === req.session.userId)) { - const blacklisted = await dataaccess.checkBlacklisted(name); - if (blacklisted.length > 0) { - res.status(status.BAD_REQUEST); - return new BlacklistedError(blacklisted.map((p) => p.phrase), "event name").graphqlError; - } - return group.$create("rEvent", {name, dueDate: date}); - } else { - res.status(status.FORBIDDEN); - return new GraphQLError("You are not a group admin!"); - } - } else { - res.status(status.UNAUTHORIZED); - 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); - const self = await models.User.findByPk(req.session.userId); - await event.$add("rParticipants", self); - return event; - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async leaveEvent({eventId}: { eventId: number }) { - if (req.session.userId) { - const event = await models.Event.findByPk(eventId); - const self = await models.User.findByPk(req.session.userId); - await event.$remove("rParticipants", self); - return event; - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async getActivities() { - return models.Activity.findAll(); - }, - async createActivity({name, description, points}: - { name: string, description: string, points: number }) { - if (req.session.userId) { - const user = await models.User.findByPk(req.session.userId); - if (user.isAdmin) { - const nameExists = await models.Activity.findOne({where: {name}}); - if (!nameExists) { - return models.Activity.create({name, description, points}); - } else { - return new GraphQLError(`An activity with the name '${name}' already exists.`); - } - } else { - res.status(status.FORBIDDEN); - return new NotAnAdminGqlError(); - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async addToBlacklist({phrase, languageCode}: {phrase: string, languageCode: string}) { - if (req.session.userId) { - const user = await models.User.findByPk(req.session.userId); - if (user.isAdmin) { - const phraseExists = await models.BlacklistedPhrase.findOne( - {where: {phrase, language: languageCode}}); - if (!phraseExists) { - await models.BlacklistedPhrase.create({phrase, language: languageCode}); - return true; - } else { - return false; - } - } else { - return new NotAnAdminGqlError(); - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - async removeFromBlacklist({phrase, languageCode}: {phrase: string, languageCode: string}) { - if (req.session.userId) { - const user = await models.User.findByPk(req.session.userId); - if (user.isAdmin) { - const phraseEntry = await models.BlacklistedPhrase.findOne( - {where: {phrase, language: languageCode}}); - if (phraseEntry) { - await phraseEntry.destroy(); - return true; - } else { - return false; - } - } - } else { - res.status(status.UNAUTHORIZED); - return new NotLoggedInGqlError(); - } - }, - }; + return new QueryResolver(); } diff --git a/src/lib/models/User.ts b/src/lib/models/User.ts index 76d22f4..ad15e01 100644 --- a/src/lib/models/User.ts +++ b/src/lib/models/User.ts @@ -80,7 +80,7 @@ export class User extends Model { * The auth token for bearer authentication */ @Unique - @Column({defaultValue: uuidv4, unique: true, type: sqz.UUIDV4}) + @Column({defaultValue: uuidv4, unique: true, type: sqz.UUID}) public authToken: string; /** diff --git a/yarn.lock b/yarn.lock index 8c6b810..33c2acc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -179,6 +179,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.148.tgz#ffa2786721707b335c6aa1465e6d3d74016fbd3e" integrity sha512-05+sIGPev6pwpHF7NZKfP3jcXhXsIVFnYyVRT4WOB0me62E8OlWfTN+sKyt2/rqN+ETxuHAtgTSK1v71F0yncg== +"@types/lodash@^4.14.149": + version "4.14.149" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" + integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== + "@types/markdown-it@0.0.9": version "0.0.9" resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.9.tgz#a5d552f95216c478e0a27a5acc1b28dcffd989ce" From aafbbb5d523ba074604a8a676c110a572a957455 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 20 Jan 2020 23:36:50 +0100 Subject: [PATCH 5/6] Cleanup code and http 404 for not found - Add 404 status code return for all not found errors --- src/graphql/BlacklistedResult.ts | 6 +++- src/graphql/MutationResolver.ts | 4 +-- src/graphql/QueryResolver.ts | 35 +++++++++-------------- src/graphql/SearchResult.ts | 5 ++-- src/graphql/Token.ts | 3 +- src/lib/dataAccess.ts | 25 +++++++++++----- src/lib/errors/ActivityNotFoundError.ts | 3 ++ src/lib/errors/BlacklistedError.ts | 3 ++ src/lib/errors/ChatNotFoundError.ts | 3 ++ src/lib/errors/GroupNotFoundError.ts | 3 ++ src/lib/errors/NoActionSpecifiedError.ts | 4 +++ src/lib/errors/NotAGroupAdminError.ts | 4 +-- src/lib/errors/NotTheGroupCreatorError.ts | 1 - src/lib/errors/PostNotFoundError.ts | 3 ++ src/lib/errors/RequestNotFoundError.ts | 3 ++ src/lib/errors/UserNotFoundError.ts | 3 ++ src/lib/errors/graphqlErrors.ts | 18 ------------ src/lib/markdown.ts | 2 +- src/lib/models/BlacklistedPhrase.ts | 12 +------- src/lib/models/Event.ts | 2 +- src/lib/models/Group.ts | 2 +- src/lib/models/Post.ts | 1 - src/routes/UploadRoute.ts | 1 - 23 files changed, 74 insertions(+), 72 deletions(-) diff --git a/src/graphql/BlacklistedResult.ts b/src/graphql/BlacklistedResult.ts index 4139424..8153bd1 100644 --- a/src/graphql/BlacklistedResult.ts +++ b/src/graphql/BlacklistedResult.ts @@ -1,6 +1,10 @@ +/** + * A result of a query to check if a phrase contains blacklisted phrases + */ export class BlacklistedResult { constructor( public blacklisted: boolean, public phrases: string[], - ) {} + ) { + } } diff --git a/src/graphql/MutationResolver.ts b/src/graphql/MutationResolver.ts index f70de4d..236f067 100644 --- a/src/graphql/MutationResolver.ts +++ b/src/graphql/MutationResolver.ts @@ -425,7 +425,7 @@ export class MutationResolver extends BaseResolver { * @param languageCode * @param request */ - public async addToBlacklist({phrase, languageCode}: {phrase: string, languageCode?: string}, request: any): + public async addToBlacklist({phrase, languageCode}: { phrase: string, languageCode?: string }, request: any): Promise { this.ensureLoggedIn(request); const user = await User.findByPk(request.session.userId); @@ -448,7 +448,7 @@ export class MutationResolver extends BaseResolver { * @param languageCode * @param request */ - public async removeFromBlacklist({phrase, languageCode}: {phrase: string, languageCode: string}, request: any): + public async removeFromBlacklist({phrase, languageCode}: { phrase: string, languageCode: string }, request: any): Promise { this.ensureLoggedIn(request); const user = await User.findByPk(request.session.userId); diff --git a/src/graphql/QueryResolver.ts b/src/graphql/QueryResolver.ts index 04d7cd8..4b0cce6 100644 --- a/src/graphql/QueryResolver.ts +++ b/src/graphql/QueryResolver.ts @@ -6,16 +6,7 @@ import {PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; import {GroupNotFoundError} from "../lib/errors/GroupNotFoundError"; import {RequestNotFoundError} from "../lib/errors/RequestNotFoundError"; import {UserNotFoundError} from "../lib/errors/UserNotFoundError"; -import { - Activity, - BlacklistedPhrase, - ChatRoom, - Event, - Group, - Post, - Request, - User, -} from "../lib/models"; +import {Activity, BlacklistedPhrase, ChatRoom, Event, Group, Post, Request, User} from "../lib/models"; import {BlacklistedResult} from "./BlacklistedResult"; import {MutationResolver} from "./MutationResolver"; import {SearchResult} from "./SearchResult"; @@ -31,7 +22,7 @@ export class QueryResolver extends MutationResolver { * @param userId * @param handle */ - public async getUser({userId, handle}: {userId?: number, handle?: string}): Promise { + public async getUser({userId, handle}: { userId?: number, handle?: string }): Promise { let user: User; if (userId) { user = await User.findByPk(userId); @@ -52,7 +43,7 @@ export class QueryResolver extends MutationResolver { * @param args * @param request */ - public async getSelf(args: null, request: any): Promise { + public async getSelf(args: null, request: any): Promise { this.ensureLoggedIn(request); return User.findByPk(request.session.userId); } @@ -61,7 +52,7 @@ export class QueryResolver extends MutationResolver { * Returns a post for a given post id. * @param postId */ - public async getPost({postId}: {postId: number}): Promise { + public async getPost({postId}: { postId: number }): Promise { const post = await Post.findByPk(postId); if (post) { return post; @@ -74,7 +65,7 @@ export class QueryResolver extends MutationResolver { * Returns a chat for a given chat id * @param chatId */ - public async getChat({chatId}: {chatId: number}): Promise { + public async getChat({chatId}: { chatId: number }): Promise { const chat = await ChatRoom.findByPk(chatId); if (chat) { return chat; @@ -87,7 +78,7 @@ export class QueryResolver extends MutationResolver { * Returns a group for a given group id. * @param groupId */ - public async getGroup({groupId}: {groupId: number}): Promise { + public async getGroup({groupId}: { groupId: number }): Promise { const group = await Group.findByPk(groupId); if (group) { return group; @@ -100,7 +91,7 @@ export class QueryResolver extends MutationResolver { * Returns the request for a given id. * @param requestId */ - public async getRequest({requestId}: {requestId: number}): Promise { + public async getRequest({requestId}: { requestId: number }): Promise { const request = await Request.findByPk(requestId); if (request) { return request; @@ -115,7 +106,7 @@ export class QueryResolver extends MutationResolver { * @param first * @param offset */ - public async search({query, first, offset}: {query: number, first: number, offset: number}): Promise { + public async search({query, first, offset}: { query: number, first: number, offset: number }): Promise { const limit = first; const users = await User.findAll({ limit, @@ -151,7 +142,7 @@ export class QueryResolver extends MutationResolver { * @param offset * @param sort */ - public async getPosts({first, offset, sort}: {first: number, offset: number, sort: dataaccess.SortType}): + public async getPosts({first, offset, sort}: { first: number, offset: number, sort: dataaccess.SortType }): Promise { return await dataaccess.getPosts(first, offset, sort); } @@ -160,7 +151,7 @@ export class QueryResolver extends MutationResolver { * Returns all activities */ public async getActivities(): Promise { - return Activity.findAll(); + return Activity.findAll(); } /** @@ -168,7 +159,7 @@ export class QueryResolver extends MutationResolver { * @param email * @param passwordHash */ - public async getToken({email, passwordHash}: {email: string, passwordHash: string}): Promise { + public async getToken({email, passwordHash}: { email: string, passwordHash: string }): Promise { const user = await dataaccess.getUserByLogin(email, passwordHash); return new Token(await user.token(), Number(user.authExpire).toString()); } @@ -177,7 +168,7 @@ export class QueryResolver extends MutationResolver { * Returns if a input phrase contains blacklisted phrases and which one * @param phrase */ - public async blacklisted({phrase}: {phrase: string}): Promise { + public async blacklisted({phrase}: { phrase: string }): Promise { const phrases = await dataaccess.checkBlacklisted(phrase); return new BlacklistedResult(phrases.length > 0, phrases .map((p) => p.phrase)); @@ -188,7 +179,7 @@ export class QueryResolver extends MutationResolver { * @param first * @param offset */ - public async getBlacklistedPhrases({first, offset}: {first: number, offset: number}): Promise { + public async getBlacklistedPhrases({first, offset}: { first: number, offset: number }): Promise { return (await BlacklistedPhrase.findAll({limit: first, offset})) .map((p) => p.phrase); } diff --git a/src/graphql/SearchResult.ts b/src/graphql/SearchResult.ts index 739f023..bee76ef 100644 --- a/src/graphql/SearchResult.ts +++ b/src/graphql/SearchResult.ts @@ -1,4 +1,4 @@ -import {Group, Post, User, Event} from "../lib/models"; +import {Event, Group, Post, User} from "../lib/models"; /** * A class to wrap search results returned by the search resolver @@ -9,5 +9,6 @@ export class SearchResult { public groups: Group[], public posts: Post[], public events: Event[], - ) {} + ) { + } } diff --git a/src/graphql/Token.ts b/src/graphql/Token.ts index f56d369..af91a94 100644 --- a/src/graphql/Token.ts +++ b/src/graphql/Token.ts @@ -5,5 +5,6 @@ export class Token { constructor( public value: string, public expires: string, - ) {} + ) { + } } diff --git a/src/lib/dataAccess.ts b/src/lib/dataAccess.ts index 8fc11c9..3b1ad3b 100644 --- a/src/lib/dataAccess.ts +++ b/src/lib/dataAccess.ts @@ -1,5 +1,4 @@ import * as crypto from "crypto"; -import {GraphQLError} from "graphql"; import * as sqz from "sequelize"; import {Sequelize} from "sequelize-typescript"; import {ActivityNotFoundError} from "./errors/ActivityNotFoundError"; @@ -166,11 +165,20 @@ namespace dataaccess { } else { // more performant way to get the votes with plain sql return await sequelize.query( - `SELECT * FROM ( - SELECT *, - (SELECT count(*) FROM post_votes WHERE vote_type = 'UPVOTE' AND post_id = posts.id) AS upvotes , - (SELECT count(*) FROM post_votes WHERE vote_type = 'DOWNVOTE' AND post_id = posts.id) AS downvotes - FROM posts) AS a ORDER BY (a.upvotes - a.downvotes) DESC, a.upvotes DESC, a.id LIMIT ? OFFSET ?`, + `SELECT * + FROM ( + SELECT *, + (SELECT count(*) + FROM post_votes + WHERE vote_type = 'UPVOTE' AND post_id = posts.id) AS upvotes, + (SELECT count(*) + FROM post_votes + WHERE vote_type = 'DOWNVOTE' AND post_id = posts.id) AS downvotes + FROM posts) AS a + ORDER BY (a.upvotes - a.downvotes) DESC, a.upvotes DESC, a.id + LIMIT ? + OFFSET + ?`, {replacements: [first, offset], mapToModel: true, model: models.Post}) as models.Post[]; } } @@ -359,7 +367,10 @@ namespace dataaccess { export async function checkBlacklisted(phrase: string, language: string = "en"): Promise { return sequelize.query(` - SELECT * FROM blacklisted_phrases WHERE ? ~* phrase AND language = ?`, + SELECT * + FROM blacklisted_phrases + WHERE ? ~* phrase + AND language = ?`, {replacements: [phrase, language], mapToModel: true, model: BlacklistedPhrase}); } diff --git a/src/lib/errors/ActivityNotFoundError.ts b/src/lib/errors/ActivityNotFoundError.ts index fb0f5c9..2b51fe8 100644 --- a/src/lib/errors/ActivityNotFoundError.ts +++ b/src/lib/errors/ActivityNotFoundError.ts @@ -4,6 +4,9 @@ import {BaseError} from "./BaseError"; * An error that is thrown when an activity was not found. */ export class ActivityNotFoundError extends BaseError { + + public readonly statusCode = httpStatus.NOT_FOUND; + constructor(id: number) { super(`The activity with the id ${id} could not be found.`); } diff --git a/src/lib/errors/BlacklistedError.ts b/src/lib/errors/BlacklistedError.ts index a4a76c2..a82cf5c 100644 --- a/src/lib/errors/BlacklistedError.ts +++ b/src/lib/errors/BlacklistedError.ts @@ -4,6 +4,9 @@ import {BaseError} from "./BaseError"; * Represents an error that is thrown when a blacklisted phrase is used. */ export class BlacklistedError extends BaseError { + + public readonly statusCode = httpStatus.NOT_ACCEPTABLE; + constructor(public phrases: string[], field: string = "input") { super(`The ${field} contains the blacklisted words: ${phrases.join(", ")}`); } diff --git a/src/lib/errors/ChatNotFoundError.ts b/src/lib/errors/ChatNotFoundError.ts index 17042dc..7af3d38 100644 --- a/src/lib/errors/ChatNotFoundError.ts +++ b/src/lib/errors/ChatNotFoundError.ts @@ -4,6 +4,9 @@ import {BaseError} from "./BaseError"; * An error that is thrown when the chatroom doesn't exist */ export class ChatNotFoundError extends BaseError { + + public readonly statusCode = httpStatus.NOT_FOUND; + constructor(chatId: number) { super(`Chat with id ${chatId} not found.`); } diff --git a/src/lib/errors/GroupNotFoundError.ts b/src/lib/errors/GroupNotFoundError.ts index f63e119..2e618bc 100644 --- a/src/lib/errors/GroupNotFoundError.ts +++ b/src/lib/errors/GroupNotFoundError.ts @@ -4,6 +4,9 @@ import {BaseError} from "./BaseError"; * An error that is thrown when a group was not found for a specified id */ export class GroupNotFoundError extends BaseError { + + public readonly statusCode = httpStatus.NOT_FOUND; + constructor(groupId: number) { super(`Group ${groupId} not found!`); } diff --git a/src/lib/errors/NoActionSpecifiedError.ts b/src/lib/errors/NoActionSpecifiedError.ts index f18fed4..797ffde 100644 --- a/src/lib/errors/NoActionSpecifiedError.ts +++ b/src/lib/errors/NoActionSpecifiedError.ts @@ -4,6 +4,10 @@ import {BaseError} from "./BaseError"; * An error that is thrown when no action was specified on a group membership change */ export class NoActionSpecifiedError extends BaseError { + + public readonly statusCode = httpStatus.NO_CONTENT; + + // @ts-ignore constructor(actions?: any) { if (actions) { super(`No action of '${Object.keys(actions).join(", ")}'`); diff --git a/src/lib/errors/NotAGroupAdminError.ts b/src/lib/errors/NotAGroupAdminError.ts index 2538c37..f125853 100644 --- a/src/lib/errors/NotAGroupAdminError.ts +++ b/src/lib/errors/NotAGroupAdminError.ts @@ -1,11 +1,11 @@ -import * as status from "http-status"; +import * as httpStatus from "http-status"; import {BaseError} from "./BaseError"; /** * An error that is thrown when a non-admin tries to perform an admin action */ export class NotAGroupAdminError extends BaseError { - public readonly statusCode = status.FORBIDDEN; + public readonly statusCode = httpStatus.FORBIDDEN; constructor(groupId: number) { super(`You are not an admin of '${groupId}'`); diff --git a/src/lib/errors/NotTheGroupCreatorError.ts b/src/lib/errors/NotTheGroupCreatorError.ts index 7b1e748..479881f 100644 --- a/src/lib/errors/NotTheGroupCreatorError.ts +++ b/src/lib/errors/NotTheGroupCreatorError.ts @@ -10,5 +10,4 @@ export class NotTheGroupCreatorError extends BaseError { constructor(groupId: number) { super(`You are not the creator of '${groupId}'`); } - } diff --git a/src/lib/errors/PostNotFoundError.ts b/src/lib/errors/PostNotFoundError.ts index 76fcf77..54b20b4 100644 --- a/src/lib/errors/PostNotFoundError.ts +++ b/src/lib/errors/PostNotFoundError.ts @@ -4,6 +4,9 @@ import {BaseError} from "./BaseError"; * An error that is thrown when a post was not found */ export class PostNotFoundError extends BaseError { + + public readonly statusCode = httpStatus.NOT_FOUND; + constructor(postId: number) { super(`Post '${postId}' not found!`); } diff --git a/src/lib/errors/RequestNotFoundError.ts b/src/lib/errors/RequestNotFoundError.ts index 46fa064..fca6895 100644 --- a/src/lib/errors/RequestNotFoundError.ts +++ b/src/lib/errors/RequestNotFoundError.ts @@ -5,6 +5,9 @@ import {BaseError} from "./BaseError"; * An error that is thrown when a request for a sender, receiver and type was not found */ export class RequestNotFoundError extends BaseError { + public readonly statusCode = httpStatus.NOT_FOUND; + + // @ts-ignore constructor(sender: number, receiver?: number, type?: dataaccess.RequestType) { if (!receiver) { super(`Request with id '${sender} not found.'`); diff --git a/src/lib/errors/UserNotFoundError.ts b/src/lib/errors/UserNotFoundError.ts index 642dc74..23038d7 100644 --- a/src/lib/errors/UserNotFoundError.ts +++ b/src/lib/errors/UserNotFoundError.ts @@ -4,6 +4,9 @@ import {BaseError} from "./BaseError"; * An error that is thrown when a specified user was not found */ export class UserNotFoundError extends BaseError { + + public readonly statusCode = httpStatus.NOT_FOUND; + constructor(username: (string | number)) { super(`User ${username} not found!`); } diff --git a/src/lib/errors/graphqlErrors.ts b/src/lib/errors/graphqlErrors.ts index 26c08aa..f40aa5c 100644 --- a/src/lib/errors/graphqlErrors.ts +++ b/src/lib/errors/graphqlErrors.ts @@ -17,21 +17,3 @@ export class PostNotFoundGqlError extends GraphQLError { super(`Post '${postId}' not found!`); } } - -/** - * An error for the frontend that is thrown when a group was not found - */ -export class GroupNotFoundGqlError extends GraphQLError { - constructor(groupId: number) { - super(`Group '${groupId}' not found!`); - } -} - -/** - * An error for the frontend that is thrown when a nonadmin tries to perform an admin operation. - */ -export class NotAnAdminGqlError extends GraphQLError { - constructor() { - super("You are not an admin."); - } -} diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts index e4d2044..adb7793 100644 --- a/src/lib/markdown.ts +++ b/src/lib/markdown.ts @@ -1,6 +1,6 @@ import * as MarkdownIt from "markdown-it/lib"; -const { html5Media } = require("markdown-it-html5-media"); +const {html5Media} = require("markdown-it-html5-media"); const mdEmoji = require("markdown-it-emoji"); namespace markdown { diff --git a/src/lib/models/BlacklistedPhrase.ts b/src/lib/models/BlacklistedPhrase.ts index 5591155..fcd8bff 100644 --- a/src/lib/models/BlacklistedPhrase.ts +++ b/src/lib/models/BlacklistedPhrase.ts @@ -1,15 +1,5 @@ import * as sqz from "sequelize"; -import { - BelongsTo, - BelongsToMany, - Column, - ForeignKey, - HasMany, - Model, - NotNull, - Table, - Unique, -} from "sequelize-typescript"; +import {Column, Model, NotNull, Table, Unique} from "sequelize-typescript"; /** * Represents a blacklisted phrase diff --git a/src/lib/models/Event.ts b/src/lib/models/Event.ts index c0119ed..d30e8ad 100644 --- a/src/lib/models/Event.ts +++ b/src/lib/models/Event.ts @@ -81,7 +81,7 @@ export class Event extends Model { * @param userId * @param request */ - public async deletable({userId}: {userId: number}, request: any): Promise { + public async deletable({userId}: { userId: number }, request: any): Promise { userId = userId ?? request.session.userId; if (userId) { const group = await this.$get("rGroup") as Group; diff --git a/src/lib/models/Group.ts b/src/lib/models/Group.ts index b2b5bf9..4184e34 100644 --- a/src/lib/models/Group.ts +++ b/src/lib/models/Group.ts @@ -150,7 +150,7 @@ export class Group extends Model { * @param userId * @param request */ - public async deletable({userId}: {userId?: number}, request: any): Promise { + public async deletable({userId}: { userId?: number }, request: any): Promise { userId = userId ?? request.session.userId; if (userId) { return this.creatorId === userId; diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts index b96b88b..fad5013 100644 --- a/src/lib/models/Post.ts +++ b/src/lib/models/Post.ts @@ -1,6 +1,5 @@ import * as sqz from "sequelize"; import {BelongsTo, BelongsToMany, Column, CreatedAt, ForeignKey, Model, NotNull, Table} from "sequelize-typescript"; -import globals from "../globals"; import markdown from "../markdown"; import {Activity} from "./Activity"; import {PostVote, VoteType} from "./PostVote"; diff --git a/src/routes/UploadRoute.ts b/src/routes/UploadRoute.ts index 18784e1..6f8b10a 100644 --- a/src/routes/UploadRoute.ts +++ b/src/routes/UploadRoute.ts @@ -5,7 +5,6 @@ import {Router} from "express"; import * as fileUpload from "express-fileupload"; import {UploadedFile} from "express-fileupload"; import * as fsx from "fs-extra"; -import {IncomingMessage} from "http"; import * as status from "http-status"; import * as path from "path"; import * as sharp from "sharp"; From 3457660f75cbdbcb074fd55853b60c2514bbcfa6 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 20 Jan 2020 23:38:50 +0100 Subject: [PATCH 6/6] Fix Style --- src/graphql/QueryResolver.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/graphql/QueryResolver.ts b/src/graphql/QueryResolver.ts index 4b0cce6..a6cf288 100644 --- a/src/graphql/QueryResolver.ts +++ b/src/graphql/QueryResolver.ts @@ -106,7 +106,8 @@ export class QueryResolver extends MutationResolver { * @param first * @param offset */ - public async search({query, first, offset}: { query: number, first: number, offset: number }): Promise { + public async search({query, first, offset}: { query: number, first: number, offset: number }): + Promise { const limit = first; const users = await User.findAll({ limit,