From 569b4a67f9f750b30190c77e09aeae8bc5e6b532 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 23 Dec 2019 23:12:44 +0100 Subject: [PATCH 01/15] Added findUser implementation - added findUser to the graphql resolvers - changed default limit to 20 - removed special worker logging - added error handling for worker initialization --- src/app.ts | 5 ++- src/graphql/resolvers.ts | 12 +++++++ src/graphql/schema.graphql | 5 +-- src/index.ts | 68 ++++++++------------------------------ 4 files changed, 28 insertions(+), 62 deletions(-) diff --git a/src/app.ts b/src/app.ts index c21518a..7bd1139 100644 --- a/src/app.ts +++ b/src/app.ts @@ -35,7 +35,7 @@ class App { this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); - this.sequelize = new Sequelize(globals.config.database.connectionUri ); + this.sequelize = new Sequelize(globals.config.database.connectionUri); } /** @@ -82,11 +82,10 @@ class App { } this.app.use((req, res, next) => { logger.verbose(`${req.method} ${req.url}`); - process.send({cmd: "notifyRequest"}); next(); }); this.app.use(routes.router); - // listen for graphql requrest + // listen for graphql requests this.app.use("/graphql", graphqlHTTP((request, response) => { return { // @ts-ignore all diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index eec84e4..162370e 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -7,6 +7,7 @@ import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; import * as models from "../lib/models"; import {is} from "../lib/regex"; +import {Op} from "sequelize"; /** * Returns the resolvers for the graphql api. @@ -15,6 +16,17 @@ import {is} from "../lib/regex"; */ export function resolver(req: any, res: any): any { return { + async findUser({first, offset, name, handle}: + {first: number, offset: number, name: string, handle: string}) { + 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); diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index e061f49..3ca17d9 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -17,11 +17,8 @@ type Query { "returns the request object for its id" getRequest(requestId: ID!): Request - "find a post by the posted date or content" - findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] - "find a user by user name or handle" - findUser(first: Int, offset: Int, name: String, handle: String): [User] + findUser(first: Int = 20, offset: Int = 0, name: String, handle: String): [User] "returns the post filtered by the sort type with pagination." getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post] diff --git a/src/index.ts b/src/index.ts index 96c7c0d..119f82c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,61 +16,19 @@ interface IClusterData { if (cluster.isMaster) { console.log(`[CLUSTER-M] Master ${process.pid} is running`); - const clusterData: IClusterData = { - reqCount: 0, - workerCount: () => Object.keys(cluster.workers).length, - // @ts-ignore - workerRes: {}, - }; - setInterval(() => { - clusterData.workerRes.M = { - cpu: process.cpuUsage(), - mem: process.memoryUsage(), - }; - }, 1000); - - const log = (msg: string) => { - process.stdout.write(" ".padEnd(100) + "\r"); - process.stdout.write(msg); - process.stdout.write( - `W: ${clusterData.workerCount()},R: ${clusterData.reqCount},M: ${(() => { - let usageString = ""; - for (const [key, value] of Object.entries(clusterData.workerRes)) { - usageString += `${ - Math.round((value as IResourceUsage).mem.heapUsed / 100000) / 10}MB,`.padEnd(8); - } - return usageString; - })()}`.padEnd(99) + "\r"); - }; cluster.settings.silent = true; - cluster.on("exit", (worker, code, signal) => { - log(`[CLUSTER-M] Worker ${worker.process.pid} died!\n`); - delete clusterData.workerRes[worker.id]; - log("[CLUSTER-M] Starting new worker\n"); + cluster.on("exit", (worker, code) => { + console.error(`[CLUSTER-M] Worker ${worker.id} died! (code: ${code})`); + console.log("[CLUSTER-M] Starting new worker"); cluster.fork(); }); cluster.on("online", (worker) => { worker.process.stdout.on("data", (data) => { - log(`[CLUSTER-${worker.id}] ${data}`); + process.stdout.write(`[CLUSTER-${worker.id}] ${data}`); }); }); - cluster.on("message", (worker, message) => { - switch (message.cmd) { - case "notifyRequest": - clusterData.reqCount++; - log(""); - break; - case "notifyResources": - // @ts-ignore - clusterData.workerRes[worker.id] = message.data; - log(""); - break; - default: - break; - } - }); for (let i = 0; i < numCPUs; i++) { cluster.fork(); @@ -81,15 +39,15 @@ if (cluster.isMaster) { * async main function wrapper. */ (async () => { - setInterval(() => { - process.send({cmd: "notifyResources", data: { - cpu: process.cpuUsage(), - mem: process.memoryUsage(), - }}); - }, 1000); - const app = new App(cluster.worker.id); - await app.init(); - app.start(); + try { + const app = new App(cluster.worker.id); + await app.init(); + app.start(); + } catch (err) { + console.error(err.message); + console.error(err.stack); + process.exit(1); + } })(); console.log(`[CLUSTER] Worker ${process.pid} started`); From f103be95b3540782e8bc9252a924106d915f9a78 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Mon, 23 Dec 2019 23:15:29 +0100 Subject: [PATCH 02/15] Fixed style issue --- src/graphql/resolvers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index e07b797..5b302aa 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -1,13 +1,13 @@ import {GraphQLError} from "graphql"; import * as status from "http-status"; import * as yaml from "js-yaml"; +import {Op} from "sequelize"; import dataaccess from "../lib/dataAccess"; import {NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; import * as models from "../lib/models"; import {is} from "../lib/regex"; -import {Op} from "sequelize"; /** * Returns the resolvers for the graphql api. From 051f9206bb7e10b398d882421f82aae084c2d697 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 24 Dec 2019 13:07:09 +0100 Subject: [PATCH 03/15] Implemented search function - added graphql field search that searches for users/groups/events/posts - marked searchUser as deprecated --- src/app.ts | 19 ++++++++++------- src/graphql/resolvers.ts | 42 ++++++++++++++++++++++++++++++++++---- src/graphql/schema.graphql | 23 ++++++++++++++++++++- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/src/app.ts b/src/app.ts index 06449d8..fb9eef7 100644 --- a/src/app.ts +++ b/src/app.ts @@ -80,15 +80,20 @@ class App { if (globals.config.server?.cors) { this.app.use(cors()); } - // handle authentification via bearer in the Authorization header + // handle authentication via bearer in the Authorization header this.app.use(async (req, res, next) => { - if (!req.session.userId && req.headers.authorization) { - const bearer = req.headers.authorization.split("Bearer ")[1]; - if (bearer) { - const user = await dataaccess.getUserByToken(bearer); - // @ts-ignore - req.session.userId = user.id; + try { + if (!req.session.userId && req.headers.authorization) { + const bearer = req.headers.authorization.split("Bearer ")[1]; + if (bearer) { + const user = await dataaccess.getUserByToken(bearer); + // @ts-ignore + req.session.userId = user.id; + } } + } catch (err) { + logger.error(err.message); + logger.debug(err.stack); } next(); }); diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 5b302aa..af83dcd 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -9,6 +9,10 @@ import {InternalEvents} from "../lib/InternalEvents"; import * as models from "../lib/models"; import {is} from "../lib/regex"; +class Resolver { + +} + /** * Returns the resolvers for the graphql api. * @param req - the request object @@ -16,8 +20,38 @@ import {is} from "../lib/regex"; */ 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}) { + { 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) { @@ -113,7 +147,7 @@ export function resolver(req: any, res: any): any { return new NotLoggedInGqlError(); } }, - async getToken({email, passwordHash}: {email: string, passwordHash: string}) { + async getToken({email, passwordHash}: { email: string, passwordHash: string }) { if (email && passwordHash) { try { const user = await dataaccess.getUserByLogin(email, passwordHash); @@ -151,7 +185,7 @@ export function resolver(req: any, res: any): any { return new GraphQLError("No username, email or password given."); } }, - async setUserSettings({settings}: {settings: string}) { + async setUserSettings({settings}: { settings: string }) { if (req.session.userId) { const user = await models.User.findByPk(req.session.userId); try { @@ -392,7 +426,7 @@ export function resolver(req: any, res: any): any { }, async createEvent({name, dueDate, groupId}: { name: string, dueDate: string, groupId: number }) { if (req.session.userId) { - const date = new Date(dueDate); + const date = new Date(Number(dueDate)); const group = await models.Group.findByPk(groupId); return group.$create("rEvent", {name, dueDate: date}); } else { diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index fea3b7d..51210e0 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -17,9 +17,12 @@ type Query { "returns the request object for its id" getRequest(requestId: ID!): Request - "find a user by user name or handle" + "DEPRECATED! Find a user by user name or handle" findUser(first: Int = 20, offset: Int = 0, name: String, handle: String): [User] + "searches for users, groups, events, posts and returns a search result" + search(query: String!, first: Int = 20, offset: Int = 0): SearchResult! + "returns the post filtered by the sort type with pagination." getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post] @@ -376,10 +379,28 @@ type Event { "respresents an access token entry with the value as the acutal token and expires as the date the token expires." type Token { + "The token itself." value: String! + + "The timestamp when the token expires." expires: String! } +"The result of a search." +type SearchResult { + "The users that were found in the search." + users: [User!]! + + "The posts that were found in the search." + posts: [Post!]! + + "The groups that were found in the search." + groups: [Group!]! + + "The events that were found in the search." + events: [Event!]! +} + "represents the type of vote performed on a post" enum VoteType { UPVOTE From bddcc84fbac629f8d4126739f05898f4293a6710 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 28 Dec 2019 23:29:02 +0100 Subject: [PATCH 04/15] Fixed denyRequest - fixed mutation denyRequest requiring the wrong params - updated changelog --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ src/graphql/schema.graphql | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8393828..4aa5c74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- settings field to own user data to store frontend settings +- Jenkinsfile +- Mocha Tests +- worker initialization error handling +- bearer token authentification for testing purposes + +### Removed + +- special worker logging + +### Changed + +- changed the running behaviour to run in cluster threads via node.js cluster api +- gql field userVote requires a userId +- default findUser param limit to 20 + +### Fixed + +- sequelize initialization being logged without winston +- userVote is always null (#47) +- findUser not being implemented +- style issues +- graphql schema for denyRequest using the wrong parameters + + ## [0.9] - 2019-10-29 ### Added diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index 51210e0..58e9b03 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -59,7 +59,7 @@ type Mutation { acceptRequest(sender: ID!, type: RequestType): Boolean "lets you deny a request for a given request id" - denyRequest(requestId: ID!): Boolean + denyRequest(sender: ID!, type: RequestType): Boolean "removes a friend" removeFriend(friendId: ID!): Boolean From e8499079d90b6b2b88aac16fec4bdef2a1b26ae1 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Fri, 3 Jan 2020 16:49:51 +0100 Subject: [PATCH 05/15] Fixed bug "deletePost doesn't work" --- src/graphql/resolvers.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index af83dcd..fceba25 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -241,7 +241,10 @@ export function resolver(req: any, res: any): any { }, async deletePost({postId}: { postId: number }) { if (postId) { - const post = await models.Post.findByPk(postId, {include: [models.User]}); + const post = await models.Post.findByPk(postId, {include: [{ + as: "rAuthor", + model: models.User, + }]}); if (post.rAuthor.id === req.session.userId) { return await dataaccess.deletePost(post.id); } else { From e25095627e55dfa0b8c5eaa75c1ce25f45078ec9 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 7 Jan 2020 16:27:39 +0100 Subject: [PATCH 06/15] Added deleteable field to Post model --- CHANGELOG.md | 5 +++ src/graphql/resolvers.ts | 38 +++++++++++------- src/graphql/schema.graphql | 3 ++ src/lib/dataAccess.ts | 47 +++++++++++++++-------- src/lib/errors/DuplicatedRequestError.ts | 7 ++++ src/lib/errors/GroupAlreadyExistsError.ts | 7 ++++ src/lib/models/Group.ts | 15 +++++++- src/lib/models/Post.ts | 8 ++++ src/lib/models/Request.ts | 13 +++++++ 9 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 src/lib/errors/DuplicatedRequestError.ts create mode 100644 src/lib/errors/GroupAlreadyExistsError.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa5c74..8715cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - findUser not being implemented - style issues - graphql schema for denyRequest using the wrong parameters +- sendRequest allowing duplicates + +### Added + +- Added `deleteable' field on post ## [0.9] - 2019-10-29 diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index af83dcd..c78e14c 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -125,7 +125,7 @@ export function resolver(req: any, res: any): any { globals.logger.warn(err.message); globals.logger.debug(err.stack); res.status(status.BAD_REQUEST); - return err.graphqlError || err.message; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.BAD_REQUEST); @@ -156,11 +156,11 @@ export function resolver(req: any, res: any): any { value: user.token(), }; } catch (err) { - res.status(400); - return err.graphqlError; + res.status(status.BAD_REQUEST); + return err.graphqlError ?? new GraphQLError(err.message); } } else { - res.status(400); + res.status(status.BAD_REQUEST); return new GraphQLError("No email or password specified."); } }, @@ -178,7 +178,7 @@ export function resolver(req: any, res: any): any { globals.logger.warn(err.message); globals.logger.debug(err.stack); res.status(status.BAD_REQUEST); - return err.graphqlError || err.message; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.BAD_REQUEST); @@ -193,7 +193,7 @@ export function resolver(req: any, res: any): any { await user.save(); return user.settings; } catch (err) { - res.status(400); + res.status(status.BAD_REQUEST); return new GraphQLError("Invalid settings json."); } } else { @@ -278,7 +278,7 @@ export function resolver(req: any, res: any): any { globals.logger.warn(err.message); globals.logger.debug(err.stack); res.status(status.BAD_REQUEST); - return err.graphqlError || err.message; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.BAD_REQUEST); @@ -291,7 +291,12 @@ export function resolver(req: any, res: any): any { return new NotLoggedInGqlError(); } if (receiver && type) { - return await dataaccess.createRequest(req.session.userId, 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."); @@ -325,7 +330,7 @@ export function resolver(req: any, res: any): any { globals.logger.warn(err.message); globals.logger.debug(err.stack); res.status(status.BAD_REQUEST); - return err.graphqlError || err.message; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.BAD_REQUEST); @@ -346,7 +351,12 @@ export function resolver(req: any, res: any): any { }, async createGroup({name, members}: { name: string, members: number[] }) { if (req.session.userId) { - return await dataaccess.createGroup(name, req.session.userId, members); + 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(); } @@ -358,7 +368,7 @@ export function resolver(req: any, res: any): any { .changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.ADD); } catch (err) { res.status(status.BAD_REQUEST); - return err.graphqlError; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.UNAUTHORIZED); @@ -372,7 +382,7 @@ export function resolver(req: any, res: any): any { .changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.REMOVE); } catch (err) { res.status(status.BAD_REQUEST); - return err.graphqlError; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.UNAUTHORIZED); @@ -392,7 +402,7 @@ export function resolver(req: any, res: any): any { .changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.OP); } catch (err) { res.status(status.BAD_REQUEST); - return err.graphqlError; + return err.graphqlError ?? new GraphQLError(err.message); } } else { @@ -417,7 +427,7 @@ export function resolver(req: any, res: any): any { .changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.DEOP); } catch (err) { res.status(status.BAD_REQUEST); - return err.graphqlError; + return err.graphqlError ?? new GraphQLError(err.message); } } else { res.status(status.UNAUTHORIZED); diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index 58e9b03..c5307cd 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -284,6 +284,9 @@ type Post { "the type of vote the user performed on the post" userVote(userId: ID!): VoteType + + "if the post can be deleted by the specified user" + deleteable(userId: ID!): Boolean } "represents a request of any type" diff --git a/src/lib/dataAccess.ts b/src/lib/dataAccess.ts index 4c67ea8..9e19163 100644 --- a/src/lib/dataAccess.ts +++ b/src/lib/dataAccess.ts @@ -2,7 +2,9 @@ import * as crypto from "crypto"; import * as sqz from "sequelize"; import {Sequelize} from "sequelize-typescript"; import {ChatNotFoundError} from "./errors/ChatNotFoundError"; +import {DuplicatedRequestError} from "./errors/DuplicatedRequestError"; import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError"; +import {GroupAlreadyExistsError} from "./errors/GroupAlreadyExistsError"; import {GroupNotFoundError} from "./errors/GroupNotFoundError"; import {InvalidLoginError} from "./errors/InvalidLoginError"; import {NoActionSpecifiedError} from "./errors/NoActionSpecifiedError"; @@ -231,9 +233,16 @@ namespace dataaccess { export async function createRequest(sender: number, receiver: number, requestType?: RequestType) { requestType = requestType || RequestType.FRIENDREQUEST; - const request = await models.Request.create({senderId: sender, receiverId: receiver, requestType}); - globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, request); - return request; + const requestExists = !!await models.Request.findOne({where: + {senderId: sender, receiverId: receiver, requestType}}); + + if (!requestExists) { + const request = await models.Request.create({senderId: sender, receiverId: receiver, requestType}); + globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, request); + return request; + } else { + throw new DuplicatedRequestError(); + } } /** @@ -243,19 +252,25 @@ namespace dataaccess { * @param members */ export async function createGroup(name: string, creator: number, members: number[]): Promise { - members = members || []; - return sequelize.transaction(async (t) => { - members.push(creator); - const groupChat = await createChat(...members); - const group = await models.Group.create({name, creatorId: creator, chatId: groupChat.id}, {transaction: t}); - const creatorUser = await models.User.findByPk(creator, {transaction: t}); - await group.$add("rAdmins", creatorUser, {transaction: t}); - for (const member of members) { - const user = await models.User.findByPk(member, {transaction: t}); - await group.$add("rMembers", user, {transaction: t}); - } - return group; - }); + const groupNameExists = !!await models.Group.findOne({where: {name}}); + if (!groupNameExists) { + members = members || []; + return sequelize.transaction(async (t) => { + members.push(creator); + const groupChat = await createChat(...members); + const group = await models.Group + .create({name, creatorId: creator, chatId: groupChat.id}, {transaction: t}); + const creatorUser = await models.User.findByPk(creator, {transaction: t}); + await group.$add("rAdmins", creatorUser, {transaction: t}); + for (const member of members) { + const user = await models.User.findByPk(member, {transaction: t}); + await group.$add("rMembers", user, {transaction: t}); + } + return group; + }); + } else { + throw new GroupAlreadyExistsError(name); + } } /** diff --git a/src/lib/errors/DuplicatedRequestError.ts b/src/lib/errors/DuplicatedRequestError.ts new file mode 100644 index 0000000..404b3c0 --- /dev/null +++ b/src/lib/errors/DuplicatedRequestError.ts @@ -0,0 +1,7 @@ +import {BaseError} from "./BaseError"; + +export class DuplicatedRequestError extends BaseError { + constructor() { + super(`Request already exists.`); + } +} diff --git a/src/lib/errors/GroupAlreadyExistsError.ts b/src/lib/errors/GroupAlreadyExistsError.ts new file mode 100644 index 0000000..02dffe2 --- /dev/null +++ b/src/lib/errors/GroupAlreadyExistsError.ts @@ -0,0 +1,7 @@ +import {BaseError} from "./BaseError"; + +export class GroupAlreadyExistsError extends BaseError { + constructor(name: string) { + super(`A group with the name "${name}" already exists.`); + } +} diff --git a/src/lib/models/Group.ts b/src/lib/models/Group.ts index 92b2b2c..7bf801f 100644 --- a/src/lib/models/Group.ts +++ b/src/lib/models/Group.ts @@ -1,4 +1,14 @@ -import {BelongsTo, BelongsToMany, Column, ForeignKey, HasMany, Model, NotNull, Table} from "sequelize-typescript"; +import { + BelongsTo, + BelongsToMany, + Column, + ForeignKey, + HasMany, + Model, + NotNull, + Table, + Unique, +} from "sequelize-typescript"; import {ChatRoom} from "./ChatRoom"; import {Event} from "./Event"; import {GroupAdmin} from "./GroupAdmin"; @@ -8,7 +18,8 @@ import {User} from "./User"; @Table({underscored: true}) export class Group extends Model { @NotNull - @Column({allowNull: false}) + @Unique + @Column({allowNull: false, unique: true}) public name: string; @NotNull diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts index 0feade3..d304e38 100644 --- a/src/lib/models/Post.ts +++ b/src/lib/models/Post.ts @@ -96,4 +96,12 @@ export class Post extends Model { const votes = await this.$get("rVotes", {where: {id: userId}}) as Array; return votes[0]?.PostVote?.voteType; } + + /** + * Returns if the post can be deleted by the user with the given id. + * @param userId + */ + public async deleteable({userId}: {userId: number}): Promise { + return Number(userId) === Number(this.authorId); + } } diff --git a/src/lib/models/Request.ts b/src/lib/models/Request.ts index 52b4058..c1faf8d 100644 --- a/src/lib/models/Request.ts +++ b/src/lib/models/Request.ts @@ -35,10 +35,23 @@ export class Request extends Model { @BelongsTo(() => User, "receiverId") public rReceiver: User; + /** + * Wrapper to return the request type for the request + */ + public get type(): RequestType { + return this.requestType; + } + + /** + * The receiver of the request + */ public async receiver(): Promise { return await this.$get("rReceiver") as User; } + /** + * The sender of the request. + */ public async sender(): Promise { return await this.$get("rSender") as User; } From 21641cbc20b018f3ac73c5bd2a3012363aff1366 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Tue, 7 Jan 2020 21:03:57 +0100 Subject: [PATCH 07/15] Fixed typo - changed deleteable to deletable --- CHANGELOG.md | 5 +---- src/graphql/schema.graphql | 2 +- src/lib/models/Post.ts | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8715cdf..eeffabb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Mocha Tests - worker initialization error handling - bearer token authentification for testing purposes +- Added `deletable' field on post ### Removed @@ -33,10 +34,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - graphql schema for denyRequest using the wrong parameters - sendRequest allowing duplicates -### Added - -- Added `deleteable' field on post - ## [0.9] - 2019-10-29 diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index c5307cd..4068f71 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -286,7 +286,7 @@ type Post { userVote(userId: ID!): VoteType "if the post can be deleted by the specified user" - deleteable(userId: ID!): Boolean + deletable(userId: ID!): Boolean } "represents a request of any type" diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts index d304e38..1b39243 100644 --- a/src/lib/models/Post.ts +++ b/src/lib/models/Post.ts @@ -101,7 +101,7 @@ export class Post extends Model { * Returns if the post can be deleted by the user with the given id. * @param userId */ - public async deleteable({userId}: {userId: number}): Promise { + public async deletable({userId}: {userId: number}): Promise { return Number(userId) === Number(this.authorId); } } From f7dae45ab91f7bc9d9c308b8f45330e07d999db7 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Wed, 8 Jan 2020 14:06:47 +0100 Subject: [PATCH 08/15] Admins - added is_admin column to users - added ability for admins to delete posts that are not their own --- CHANGELOG.md | 2 ++ src/graphql/resolvers.ts | 3 ++- src/graphql/schema.graphql | 3 +++ src/lib/models/Post.ts | 7 ++++++- src/lib/models/User.ts | 4 ++++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeffabb..3db8cf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - worker initialization error handling - bearer token authentification for testing purposes - Added `deletable' field on post +- Admin field that for admin users +- ability for admins to delete posts ### Removed diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 66a3ad2..5b53d85 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -245,7 +245,8 @@ export function resolver(req: any, res: any): any { as: "rAuthor", model: models.User, }]}); - if (post.rAuthor.id === req.session.userId) { + const isAdmin = (await models.User.findOne({where: {id: req.session.userId}})).isAdmin; + if (post.rAuthor.id === req.session.userId || isAdmin) { return await dataaccess.deletePost(post.id); } else { res.status(status.FORBIDDEN); diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index 4068f71..657d049 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -256,6 +256,9 @@ type Profile implements UserData { "the custom settings for the frontend" settings: String! + + "if the user is an admin" + isAdmin: Boolean } "represents a single user post" diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts index 1b39243..e9fef2b 100644 --- a/src/lib/models/Post.ts +++ b/src/lib/models/Post.ts @@ -102,6 +102,11 @@ export class Post extends Model { * @param userId */ public async deletable({userId}: {userId: number}): Promise { - return Number(userId) === Number(this.authorId); + + const isAuthor = Number(userId) === Number(this.authorId); + if (!isAuthor) { + return (await User.findOne({where: {id: userId}})).isAdmin; + } + return isAuthor; } } diff --git a/src/lib/models/User.ts b/src/lib/models/User.ts index 0ebb547..5901054 100644 --- a/src/lib/models/User.ts +++ b/src/lib/models/User.ts @@ -61,6 +61,10 @@ export class User extends Model { @Column({defaultValue: () => Date.now() + 7200000}) public authExpire: Date; + @NotNull + @Column({defaultValue: false, allowNull: false}) + public isAdmin: boolean; + @BelongsToMany(() => User, () => Friendship, "userId") public rFriends: User[]; From 05e69aacf3a3862b72f4953c09501c9ae5e3a23a Mon Sep 17 00:00:00 2001 From: Trivernis Date: Wed, 8 Jan 2020 14:54:11 +0100 Subject: [PATCH 09/15] Event changes - changed creation to group-admins only --- CHANGELOG.md | 1 + src/graphql/resolvers.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db8cf7..c3183f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - changed the running behaviour to run in cluster threads via node.js cluster api - gql field userVote requires a userId - default findUser param limit to 20 +- only group admins can create group events ### Fixed diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 5b53d85..94222f8 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -441,8 +441,13 @@ export function resolver(req: any, res: any): any { 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); - return group.$create("rEvent", {name, dueDate: date}); + const group = await models.Group.findByPk(groupId, {include: [{association: "rAdmins"}]}); + if (group.rAdmins.find((x) => x.id === req.session.userId)) { + 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(); From 4b67c0be411be6814e4d2c182f068a79d45f7f95 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Wed, 8 Jan 2020 15:36:56 +0100 Subject: [PATCH 10/15] Fixed invalid login on token generation --- src/graphql/resolvers.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index 94222f8..a03814a 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -4,6 +4,8 @@ import * as yaml from "js-yaml"; import {Op} from "sequelize"; import dataaccess from "../lib/dataAccess"; import {NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; +import {InvalidLoginError} from "../lib/errors/InvalidLoginError"; +import {UserNotFoundError} from "../lib/errors/UserNotFoundError"; import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; import * as models from "../lib/models"; @@ -151,10 +153,15 @@ export function resolver(req: any, res: any): any { if (email && passwordHash) { try { const user = await dataaccess.getUserByLogin(email, passwordHash); - return { - expires: Number(user.authExpire), - value: user.token(), - }; + 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); From da90ba0c162ec999450fbc28379cf2045c120aaa Mon Sep 17 00:00:00 2001 From: Trivernis Date: Wed, 8 Jan 2020 19:58:41 +0100 Subject: [PATCH 11/15] Added file upload - added ability to upload profile pictures --- CHANGELOG.md | 1 + package.json | 6 +- src/app.ts | 27 ++++++ yarn.lock | 236 +++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 262 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3183f0..e470a77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `deletable' field on post - Admin field that for admin users - ability for admins to delete posts +- ability to upload file at `/upload` with the name profilePicture ### Removed diff --git a/package.json b/package.json index 831c96a..6d5721c 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,12 @@ "@types/node": "^12.7.12", "@types/pg": "^7.11.0", "@types/sequelize": "^4.28.5", + "@types/sharp": "^0.23.1", "@types/socket.io": "^2.1.2", "@types/socket.io-redis": "^1.0.25", "@types/validator": "^10.11.3", + "@types/express-fileupload": "^1.1.0", + "@types/uuid": "^3.4.6", "chai": "^4.2.0", "delete": "^1.1.0", "gulp": "^4.0.2", @@ -54,12 +57,12 @@ "typescript": "^3.7.2" }, "dependencies": { - "@types/uuid": "^3.4.6", "compression": "^1.7.4", "connect-session-sequelize": "^6.0.0", "cookie-parser": "^1.4.4", "cors": "^2.8.5", "express": "^4.17.1", + "express-fileupload": "^1.1.6", "express-graphql": "^0.9.0", "express-session": "^1.16.2", "express-socket.io-session": "^1.3.5", @@ -75,6 +78,7 @@ "reflect-metadata": "^0.1.13", "sequelize": "^5.19.6", "sequelize-typescript": "^1.0.0", + "sharp": "^0.23.4", "socket.io": "^2.2.0", "socket.io-redis": "^5.2.0", "sqlite3": "^4.1.0", diff --git a/src/app.ts b/src/app.ts index fb9eef7..a3d4513 100644 --- a/src/app.ts +++ b/src/app.ts @@ -3,6 +3,8 @@ import * as cookieParser from "cookie-parser"; import * as cors from "cors"; import {Request, Response} from "express"; import * as express from "express"; +import {UploadedFile} from "express-fileupload"; +import * as fileUpload from "express-fileupload"; import * as graphqlHTTP from "express-graphql"; import * as session from "express-session"; import sharedsession = require("express-socket.io-session"); @@ -13,6 +15,7 @@ import * as http from "http"; import * as httpStatus from "http-status"; import * as path from "path"; import {Sequelize} from "sequelize-typescript"; +import * as sharp from "sharp"; import * as socketIo from "socket.io"; import * as socketIoRedis from "socket.io-redis"; import {resolver} from "./graphql/resolvers"; @@ -22,6 +25,7 @@ import routes from "./routes"; const SequelizeStore = require("connect-session-sequelize")(session.Store); const logger = globals.logger; +const dataDir = path.join(__dirname, "public/data"); class App { public app: express.Application; @@ -112,6 +116,29 @@ class App { schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))), }; })); + this.app.use("/upload", fileUpload()); + this.app.use("/upload", async (req, res) => { + const profilePic = req.files.profilePicture as UploadedFile; + let success = false; + let fileName = undefined; + if (profilePic && req.session.userId) { + const dir = path.join(dataDir, "profilePictures"); + await fsx.ensureDir(dir); + await sharp(profilePic.data) + .resize(512, 512) + .normalise() + .png() + .toFile(path.join(dir, req.session.userId + ".png")); + success = true; + fileName = `/data/profilePictures/${req.session.userId}.png`; + } else { + res.status(400); + } + res.json({ + fileName, + success, + }); + }); // allow access to cluster information this.app.use("/cluster-info", (req: Request, res: Response) => { res.json({ diff --git a/yarn.lock b/yarn.lock index b5a9623..ff3abbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,6 +92,13 @@ dependencies: "@types/express" "*" +"@types/express-fileupload@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/express-fileupload/-/express-fileupload-1.1.0.tgz#c2f0555be691b11f6cddaf8b23b0d41b3ef689c8" + integrity sha512-l7ElHOlTt3Dis1My3LvEwP4tDw/cYiM1sor5nQY8aRm8HPVwalG48lf7ym9k2/z/PwRGADABGgIhv0a6y7XzoA== + dependencies: + "@types/express" "*" + "@types/express-graphql@^0.8.0": version "0.8.2" resolved "https://registry.yarnpkg.com/@types/express-graphql/-/express-graphql-0.8.2.tgz#fef9f46ae6ef636f6b02fcedcc099e5e1551c59a" @@ -218,6 +225,13 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/sharp@^0.23.1": + version "0.23.1" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.23.1.tgz#1e02560371d6603adc121389512f0745028aa507" + integrity sha512-iBRM9RjRF9pkIkukk6imlxfaKMRuiRND8L0yYKl5PJu5uLvxuNzp5f0x8aoTG5VX85M8O//BwbttzFVZL1j/FQ== + dependencies: + "@types/node" "*" + "@types/socket.io-redis@^1.0.25": version "1.0.25" resolved "https://registry.yarnpkg.com/@types/socket.io-redis/-/socket.io-redis-1.0.25.tgz#ad525e19a1d745f01d8e3a92f9bb259e4028eeb4" @@ -714,6 +728,13 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== +bl@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" + integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + dependencies: + readable-stream "^3.0.1" + blob@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" @@ -804,6 +825,13 @@ builtin-modules@^1.1.1: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= +busboy@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.3.1.tgz#170899274c5bf38aae27d5c62b71268cd585fd1b" + integrity sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw== + dependencies: + dicer "0.3.0" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -938,7 +966,7 @@ chokidar@^2.0.0: optionalDependencies: fsevents "^1.2.7" -chownr@^1.1.1: +chownr@^1.1.1, chownr@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== @@ -1079,6 +1107,14 @@ color@3.0.x: color-convert "^1.9.1" color-string "^1.5.2" +color@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" + integrity sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + colornames@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" @@ -1348,6 +1384,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + deep-eql@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" @@ -1453,7 +1496,7 @@ detect-file@^1.0.0: resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-libc@^1.0.2: +detect-libc@^1.0.2, detect-libc@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= @@ -1467,6 +1510,13 @@ diagnostics@^1.1.1: enabled "1.0.x" kuler "1.0.x" +dicer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" + integrity sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA== + dependencies: + streamsearch "0.1.2" + diff@3.5.0, diff@^3.0.1: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -1540,7 +1590,7 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -end-of-stream@^1.0.0, end-of-stream@^1.1.0: +end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -1704,6 +1754,11 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" @@ -1711,6 +1766,13 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" +express-fileupload@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/express-fileupload/-/express-fileupload-1.1.6.tgz#0ac2659ad8c1128c92c8580fd6e15b8b15343db0" + integrity sha512-w24zPWT8DkoIxSVkbxYPo9hkTiLpCQQzNsLRTCnecBhfbYv+IkIC5uLw2MIUAxBZ+7UMmXPjGxlhzUXo4RcbZw== + dependencies: + busboy "^0.3.1" + express-graphql@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/express-graphql/-/express-graphql-0.9.0.tgz#00fd8552f866bac5c9a4612b2c4c82076107b3c2" @@ -2014,6 +2076,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -2030,6 +2097,13 @@ fs-minipass@^1.2.5: dependencies: minipass "^2.6.0" +fs-minipass@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.0.0.tgz#a6415edab02fae4b9e9230bc87ee2e4472003cd1" + integrity sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A== + dependencies: + minipass "^3.0.0" + fs-mkdirp-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" @@ -2119,6 +2193,11 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -3232,6 +3311,11 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mimic-response@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" + integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== + "minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -3262,6 +3346,13 @@ minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: safe-buffer "^5.1.2" yallist "^3.0.0" +minipass@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" + integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w== + dependencies: + yallist "^4.0.0" + minizlib@^1.2.1: version "1.3.3" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" @@ -3269,6 +3360,14 @@ minizlib@^1.2.1: dependencies: minipass "^2.9.0" +minizlib@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3" + integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -3345,7 +3444,7 @@ mute-stdout@^1.0.0: resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== -nan@^2.12.1, nan@^2.13.2: +nan@^2.12.1, nan@^2.13.2, nan@^2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -3367,6 +3466,11 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +napi-build-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508" + integrity sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA== + needle@^2.2.1: version "2.4.0" resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" @@ -3386,6 +3490,13 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= +node-abi@^2.7.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-2.13.0.tgz#e2f2ec444d0aca3ea1b3874b6de41d1665828f63" + integrity sha512-9HrZGFVTR5SOu3PZAnAY2hLO36aW1wmA+FDsVkr85BTST32TLCA1H/AEcatVRAsWLyXS3bqUDYCAjq5/QGuSTA== + dependencies: + semver "^5.4.1" + node-environment-flags@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" @@ -3467,6 +3578,11 @@ node-sass@^4.8.3: stdout-stream "^1.4.0" "true-case-path" "^1.0.2" +noop-logger@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" + integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= + "nopt@2 || 3": version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -3529,7 +3645,7 @@ npm-packlist@^1.1.6: ignore-walk "^3.0.1" npm-bundled "^1.0.1" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== @@ -3958,6 +4074,27 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +prebuild-install@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.3.tgz#ef4052baac60d465f5ba6bf003c9c1de79b9da8e" + integrity sha512-GV+nsUXuPW2p8Zy7SarF/2W/oiK8bFQgJcncoJ0d7kRpekEA0ftChjfEaF9/Y+QJEc/wFR7RAEa8lYByuUIe2g== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.0" + mkdirp "^0.5.1" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" @@ -4106,6 +4243,14 @@ pump@^2.0.0: end-of-stream "^1.1.0" once "^1.3.1" +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pumpify@^1.3.5: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" @@ -4192,7 +4337,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -"readable-stream@2 || 3", readable-stream@^3.1.1: +"readable-stream@2 || 3", readable-stream@^3.0.1, readable-stream@^3.1.1: version "3.4.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== @@ -4486,7 +4631,7 @@ semver-greatest-satisfied-range@^1.1.0: dependencies: sver-compat "^1.5.0" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.7.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.7.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4588,6 +4733,21 @@ setprototypeof@1.1.1: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== +sharp@^0.23.4: + version "0.23.4" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.23.4.tgz#ca36067cb6ff7067fa6c77b01651cb9a890f8eb3" + integrity sha512-fJMagt6cT0UDy9XCsgyLi0eiwWWhQRxbwGmqQT6sY8Av4s0SVsT/deg8fobBQCTDU5iXRgz0rAeXoE2LBZ8g+Q== + dependencies: + color "^3.1.2" + detect-libc "^1.0.3" + nan "^2.14.0" + npmlog "^4.1.2" + prebuild-install "^5.3.3" + semver "^6.3.0" + simple-get "^3.1.0" + tar "^5.0.5" + tunnel-agent "^0.6.0" + shimmer@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" @@ -4598,6 +4758,20 @@ signal-exit@^3.0.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= +simple-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" + integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + +simple-get@^3.0.3, simple-get@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -4856,6 +5030,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= +streamsearch@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -4979,6 +5158,27 @@ sver-compat@^1.5.0: es6-iterator "^2.0.1" es6-symbol "^3.1.1" +tar-fs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== + dependencies: + chownr "^1.1.1" + mkdirp "^0.5.1" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" + integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== + dependencies: + bl "^3.0.0" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" @@ -5001,6 +5201,18 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.3" +tar@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/tar/-/tar-5.0.5.tgz#03fcdb7105bc8ea3ce6c86642b9c942495b04f93" + integrity sha512-MNIgJddrV2TkuwChwcSNds/5E9VijOiw7kAc1y5hTNJoLDSuIyid2QtLYiCYNnICebpuvjhPQZsXwUL0O3l7OQ== + dependencies: + chownr "^1.1.3" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.0" + mkdirp "^0.5.0" + yallist "^4.0.0" + terser@^3.7.6: version "3.17.0" resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2" @@ -5477,6 +5689,11 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= +which-pm-runs@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" + integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= + which@1, which@1.3.1, which@^1.2.14, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -5620,6 +5837,11 @@ yallist@^3.0.0, yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yargs-parser@13.1.1, yargs-parser@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" From 9011cb39cfae5df077cdb043036ea7b61ec60b05 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 9 Jan 2020 09:53:19 +0100 Subject: [PATCH 12/15] Added publicPath to config file - added publicPath config property to configure the path to the public files --- CHANGELOG.md | 1 + package.json | 4 +- src/app.ts | 21 +- src/default-config.yaml | 3 +- src/lib/interfaces/IConfig.ts | 5 + yarn.lock | 429 +++++++++++++++++++++++++++++++++- 6 files changed, 445 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e470a77..db4556f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Admin field that for admin users - ability for admins to delete posts - ability to upload file at `/upload` with the name profilePicture +- publicPath to config file to configure the directory for public files ### Removed diff --git a/package.json b/package.json index 6d5721c..10ab79b 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/cookie-parser": "^1.4.2", "@types/cors": "^2.8.6", "@types/express": "^4.17.1", + "@types/express-fileupload": "^1.1.0", "@types/express-graphql": "^0.8.0", "@types/express-session": "^1.15.14", "@types/express-socket.io-session": "^1.3.2", @@ -41,9 +42,8 @@ "@types/sharp": "^0.23.1", "@types/socket.io": "^2.1.2", "@types/socket.io-redis": "^1.0.25", - "@types/validator": "^10.11.3", - "@types/express-fileupload": "^1.1.0", "@types/uuid": "^3.4.6", + "@types/validator": "^10.11.3", "chai": "^4.2.0", "delete": "^1.1.0", "gulp": "^4.0.2", diff --git a/src/app.ts b/src/app.ts index a3d4513..8036b3c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -25,12 +25,12 @@ import routes from "./routes"; const SequelizeStore = require("connect-session-sequelize")(session.Store); const logger = globals.logger; -const dataDir = path.join(__dirname, "public/data"); class App { public app: express.Application; public io: socketIo.Server; public server: http.Server; + public readonly publicPath: string; public readonly id?: number; public readonly sequelize: Sequelize; @@ -40,6 +40,10 @@ class App { this.server = new http.Server(this.app); this.io = socketIo(this.server); this.sequelize = new Sequelize(globals.config.database.connectionUri); + this.publicPath = globals.config.frontend.publicPath; + if (!path.isAbsolute(this.publicPath)) { + this.publicPath = path.normalize(path.join(__dirname, this.publicPath)); + } } /** @@ -66,7 +70,12 @@ class App { this.sequelize.options.logging = (msg) => logger.silly(msg); logger.info("Setting up socket.io"); await routes.ioListeners(this.io); - this.io.adapter(socketIoRedis()); + try { + this.io.adapter(socketIoRedis()); + } catch (err) { + logger.error(err.message); + logger.debug(err.stack); + } this.io.use(sharedsession(appSession, {autoSave: true})); logger.info("Configuring express app."); @@ -77,7 +86,7 @@ class App { this.app.use(compression()); this.app.use(express.json()); this.app.use(express.urlencoded({extended: false})); - this.app.use(express.static(path.join(__dirname, "public"))); + this.app.use(express.static(this.publicPath)); this.app.use(cookieParser()); this.app.use(appSession); // enable cross origin requests if enabled in the config @@ -120,9 +129,9 @@ class App { this.app.use("/upload", async (req, res) => { const profilePic = req.files.profilePicture as UploadedFile; let success = false; - let fileName = undefined; + let fileName; if (profilePic && req.session.userId) { - const dir = path.join(dataDir, "profilePictures"); + const dir = path.join(this.publicPath, "data/profilePictures"); await fsx.ensureDir(dir); await sharp(profilePic.data) .resize(512, 512) @@ -148,7 +157,7 @@ class App { // redirect all request to the angular file this.app.use((req: any, res: Response) => { if (globals.config.frontend.angularIndex) { - const angularIndex = path.join(__dirname, globals.config.frontend.angularIndex); + const angularIndex = path.join(this.publicPath, globals.config.frontend.angularIndex); if (fsx.existsSync(path.join(angularIndex))) { res.sendFile(angularIndex); } else { diff --git a/src/default-config.yaml b/src/default-config.yaml index 86e0505..6794925 100644 --- a/src/default-config.yaml +++ b/src/default-config.yaml @@ -22,4 +22,5 @@ logging: level: info frontend: - angularIndex: + angularIndex: index.html + publicPath: ./public diff --git a/src/lib/interfaces/IConfig.ts b/src/lib/interfaces/IConfig.ts index 3d14530..1367920 100644 --- a/src/lib/interfaces/IConfig.ts +++ b/src/lib/interfaces/IConfig.ts @@ -63,5 +63,10 @@ interface IConfig { * Points to the index.html which is loaded as a fallback for angular to work */ angularIndex?: string; + + /** + * The path of the public folder + */ + publicPath?: string; }; } diff --git a/yarn.lock b/yarn.lock index ff3abbf..324ea4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,6 +18,14 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@dsherret/to-absolute-glob@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1f6475dc8bd974cea07a2daf3864b317b1dd332c" + integrity sha1-H2R13IvZdM6gei2vOGSzF7HdMyw= + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" + "@types/babel-types@*", "@types/babel-types@^7.0.0": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.7.tgz#667eb1640e8039436028055737d2b9986ee336e3" @@ -293,6 +301,16 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= +ajv@^5.1.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + ajv@^6.5.5: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" @@ -474,6 +492,11 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= + array-each@^1.0.0, array-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" @@ -523,6 +546,18 @@ array-sort@^1.0.0: get-value "^2.0.6" kind-of "^5.0.2" +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= + array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" @@ -533,6 +568,11 @@ arraybuffer.slice@~0.0.7: resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== +arrify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= + asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -619,6 +659,11 @@ aws-sign2@~0.7.0: resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= +aws4@^1.6.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c" + integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A== + aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" @@ -768,6 +813,20 @@ body-parser@1.19.0: raw-body "2.4.0" type-is "~1.6.17" +boom@4.x.x: + version "4.3.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" + integrity sha1-T4owBctKfjiJ90kDD9JbluAdLjE= + dependencies: + hoek "4.x.x" + +boom@5.x.x: + version "5.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" + integrity sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw== + dependencies: + hoek "4.x.x" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -885,6 +944,11 @@ camelcase@^3.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0= + camelcase@^5.0.0: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -915,6 +979,15 @@ chai@^4.2.0: pathval "^1.1.0" type-detect "^4.0.5" +chalk@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" + integrity sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -1006,6 +1079,15 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" +cliui@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" + integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + wrap-ansi "^2.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -1047,6 +1129,16 @@ cls-bluebird@^2.1.0: is-bluebird "^1.0.2" shimmer "^1.1.0" +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + +code-block-writer@^6.6.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/code-block-writer/-/code-block-writer-6.14.0.tgz#0245ee31aebcd21a82316ce2061d5fbe9550de6f" + integrity sha1-AkXuMa680hqCMWziBh1fvpVQ3m8= + code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1133,7 +1225,7 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -1324,6 +1416,22 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cryptiles@3.x.x: + version "3.1.4" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.4.tgz#769a68c95612b56faadfcebf57ac86479cbe8322" + integrity sha512-8I1sgZHfVwcSOY6mSGpVU3lw/GSIZvusg8dD2+OGehCJpOhQRLNcH0qb9upQnOH4XhgxxFJSg6E2kx95deb1Tw== + dependencies: + boom "5.x.x" + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -1741,6 +1849,19 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c= + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -1871,7 +1992,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@~3.0.1, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -1910,6 +2031,11 @@ fancy-log@^1.3.2: parse-node-version "^1.0.0" time-stamp "^1.0.0" +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= + fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -1975,6 +2101,13 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha1-RdG35QbHF93UgndaK3eSCjwMV6c= + dependencies: + locate-path "^2.0.0" + findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -2050,7 +2183,7 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@~2.3.2: +form-data@~2.3.1, form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== @@ -2181,6 +2314,11 @@ get-stdin@^4.0.1: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -2301,6 +2439,17 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw= + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + globule@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.1.tgz#5dffb1b191f22d20797a9369b49eab4e9839696d" @@ -2426,6 +2575,14 @@ har-schema@^2.0.0: resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0= + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + har-validator@~5.1.0: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" @@ -2518,11 +2675,26 @@ has@^1.0.1, has@^1.0.3: dependencies: function-bind "^1.1.1" +hawk@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" + integrity sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ== + dependencies: + boom "4.x.x" + cryptiles "3.x.x" + hoek "4.x.x" + sntp "2.x.x" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hoek@4.x.x: + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" + integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== + homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" @@ -2898,6 +3070,11 @@ is-windows@^1.0.1, is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= + isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -2968,6 +3145,11 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -3120,6 +3302,14 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -3133,6 +3323,11 @@ lodash.clonedeep@^4.3.2: resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash@4.17.5: + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" + integrity sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw== + lodash@^4.0.0, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -3249,6 +3444,13 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y= + dependencies: + mimic-fn "^1.0.0" + meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" @@ -3299,6 +3501,11 @@ mime-db@1.42.0, "mime-db@>= 1.40.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ== +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== + mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.25" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437" @@ -3306,17 +3513,29 @@ mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: dependencies: mime-db "1.42.0" +mime-types@~2.1.17: + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== + dependencies: + mime-db "1.43.0" + mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + mimic-response@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -3439,6 +3658,16 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +multimatch@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" + integrity sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis= + dependencies: + array-differ "^1.0.0" + array-union "^1.0.1" + arrify "^1.0.0" + minimatch "^3.0.0" + mute-stdout@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" @@ -3645,6 +3874,13 @@ npm-packlist@^1.1.6: ignore-walk "^3.0.1" npm-bundled "^1.0.1" +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= + dependencies: + path-key "^2.0.0" + "npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -3660,12 +3896,17 @@ number-is-nan@^1.0.0: resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= +oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -3786,6 +4027,13 @@ one-time@0.0.4: resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= +opn@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c" + integrity sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g== + dependencies: + is-wsl "^1.1.0" + optimist@~0.6.0: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -3813,6 +4061,15 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA== + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + os-tmpdir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3826,6 +4083,18 @@ osenv@0, osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + p-limit@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" @@ -3833,6 +4102,13 @@ p-limit@^2.0.0: dependencies: p-try "^2.0.0" +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= + dependencies: + p-limit "^1.1.0" + p-locate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" @@ -3840,6 +4116,11 @@ p-locate@^3.0.0: dependencies: p-limit "^2.0.0" +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= + p-try@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" @@ -3922,6 +4203,11 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" @@ -4275,7 +4561,7 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@~6.5.2: +qs@~6.5.1, qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== @@ -4480,6 +4766,34 @@ replace-homedir@^1.0.0: is-absolute "^1.0.0" remove-trailing-separator "^1.1.0" +request@2.85.0: + version "2.85.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" + integrity sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" @@ -4584,7 +4898,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@5.2.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== @@ -4748,6 +5062,18 @@ sharp@^0.23.4: tar "^5.0.5" tunnel-agent "^0.6.0" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + shimmer@^1.1.0: version "1.2.1" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" @@ -4809,6 +5135,13 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +sntp@2.x.x: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" + integrity sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg== + dependencies: + hoek "4.x.x" + socket.io-adapter@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b" @@ -5044,7 +5377,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2": +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -5091,6 +5424,11 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +stringstream@~0.0.5: + version "0.0.6" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" + integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -5119,6 +5457,11 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -5325,6 +5668,13 @@ toposort-class@^1.0.1: resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988" integrity sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg= +tough-cookie@~2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== + dependencies: + punycode "^1.4.1" + tough-cookie@~2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" @@ -5364,6 +5714,18 @@ ts-lint@^4.5.1: resolve "^1.1.7" tsutils "^1.1.0" +ts-simple-ast@9.5.0: + version "9.5.0" + resolved "https://registry.yarnpkg.com/ts-simple-ast/-/ts-simple-ast-9.5.0.tgz#18f755e64434cd968dddf78723454bbcb16e070f" + integrity sha1-GPdV5kQ0zZaN3feHI0VLvLFuBw8= + dependencies: + "@dsherret/to-absolute-glob" "^2.0.2" + code-block-writer "^6.6.0" + globby "^6.1.0" + multimatch "^2.1.0" + object-assign "^4.1.1" + typescript "2.7.1" + tsc@^1.20150623.0: version "1.20150623.0" resolved "https://registry.yarnpkg.com/tsc/-/tsc-1.20150623.0.tgz#4ebc3c774e169148cbc768a7342533f082c7a6e5" @@ -5393,6 +5755,20 @@ tslint@^5.19.0: tslib "^1.8.0" tsutils "^2.29.0" +tsuml@^0.0.1-alpha.8: + version "0.0.1-alpha.8" + resolved "https://registry.yarnpkg.com/tsuml/-/tsuml-0.0.1-alpha.8.tgz#6909d3b31da3abd71febecb87c32b5dced4e99e5" + integrity sha512-CyfoMUQ55bhHcv18ecvU6P0Yl9EzRQB4Cpa0xkogGOQno5wjB/XW+xW/vHatUPD/Z7q2eiQbnvWxMXYenSJj/w== + dependencies: + chalk "2.3.2" + glob "7.1.2" + lodash "4.17.5" + opn "5.3.0" + request "2.85.0" + ts-simple-ast "9.5.0" + typescript "2.7.2" + yargs "11.0.0" + tsutils@^1.1.0: version "1.9.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" @@ -5445,6 +5821,16 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.1.tgz#bb3682c2c791ac90e7c6210b26478a8da085c359" + integrity sha512-bqB1yS6o9TNA9ZC/MJxM0FZzPnZdtHj0xWK/IZ5khzVqdpGul/R/EIiHRgFXlwTD7PSIaYVnGKq1QgMCu2mnqw== + +typescript@2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" + integrity sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw== + typescript@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" @@ -5575,7 +5961,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.3.2, uuid@^3.3.3: +uuid@^3.1.0, uuid@^3.3.2, uuid@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== @@ -5857,6 +6243,13 @@ yargs-parser@^5.0.0: dependencies: camelcase "^3.0.0" +yargs-parser@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" + integrity sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc= + dependencies: + camelcase "^4.1.0" + yargs-unparser@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" @@ -5866,6 +6259,24 @@ yargs-unparser@1.6.0: lodash "^4.17.15" yargs "^13.3.0" +yargs@11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b" + integrity sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw== + dependencies: + cliui "^4.0.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^9.0.2" + yargs@13.3.0, yargs@^13.3.0: version "13.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" From 124fde08d54b49d52d683e5256daf01a91877f28 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 9 Jan 2020 10:19:11 +0100 Subject: [PATCH 13/15] Added profilePicture property to User - added profilePicture which saves the url to the users profile picture --- CHANGELOG.md | 1 + src/app.ts | 4 ++++ src/lib/models/User.ts | 3 +++ 3 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index db4556f..e0cc684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ability for admins to delete posts - ability to upload file at `/upload` with the name profilePicture - publicPath to config file to configure the directory for public files +- profilePicture property to User model which is an url to the users profile picture ### Removed diff --git a/src/app.ts b/src/app.ts index 8036b3c..8c64094 100644 --- a/src/app.ts +++ b/src/app.ts @@ -21,6 +21,7 @@ import * as socketIoRedis from "socket.io-redis"; import {resolver} from "./graphql/resolvers"; import dataaccess from "./lib/dataAccess"; import globals from "./lib/globals"; +import {User} from "./lib/models"; import routes from "./routes"; const SequelizeStore = require("connect-session-sequelize")(session.Store); @@ -140,6 +141,9 @@ class App { .toFile(path.join(dir, req.session.userId + ".png")); success = true; fileName = `/data/profilePictures/${req.session.userId}.png`; + const user = await User.findByPk(req.session.userId); + user.profilePicture = fileName; + await user.save(); } else { res.status(400); } diff --git a/src/lib/models/User.ts b/src/lib/models/User.ts index 5901054..8db31e9 100644 --- a/src/lib/models/User.ts +++ b/src/lib/models/User.ts @@ -65,6 +65,9 @@ export class User extends Model { @Column({defaultValue: false, allowNull: false}) public isAdmin: boolean; + @Column({type: sqz.STRING(512)}) + public profilePicture: string; + @BelongsToMany(() => User, () => Friendship, "userId") public rFriends: User[]; From 4f6caf461ccc3fc3e37f3e3be8f03e9bf9f5e74c Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 9 Jan 2020 11:16:38 +0100 Subject: [PATCH 14/15] Added activities - added Activity model - added Activity association to Post - added getActivities field to queries - added createActivity to mutations --- CHANGELOG.md | 3 ++ src/graphql/resolvers.ts | 37 ++++++++++++++++++++---- src/graphql/schema.graphql | 32 +++++++++++++++++---- src/lib/dataAccess.ts | 38 +++++++++++++++++++------ src/lib/errors/ActivityNotFoundError.ts | 7 +++++ src/lib/models/Activity.ts | 18 ++++++++++++ src/lib/models/Post.ts | 17 ++++++++++- src/lib/models/User.ts | 13 ++++----- src/lib/models/index.ts | 1 + 9 files changed, 139 insertions(+), 27 deletions(-) create mode 100644 src/lib/errors/ActivityNotFoundError.ts create mode 100644 src/lib/models/Activity.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e0cc684..4f99a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ability to upload file at `/upload` with the name profilePicture - publicPath to config file to configure the directory for public files - profilePicture property to User model which is an url to the users profile picture +- activities to posts +- getActivities field to receive all activities +- activities table ### Removed diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts index a03814a..a08c806 100644 --- a/src/graphql/resolvers.ts +++ b/src/graphql/resolvers.ts @@ -5,7 +5,6 @@ import {Op} from "sequelize"; import dataaccess from "../lib/dataAccess"; import {NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; import {InvalidLoginError} from "../lib/errors/InvalidLoginError"; -import {UserNotFoundError} from "../lib/errors/UserNotFoundError"; import globals from "../lib/globals"; import {InternalEvents} from "../lib/InternalEvents"; import * as models from "../lib/models"; @@ -227,15 +226,20 @@ export function resolver(req: any, res: any): any { return new GraphQLError("No postId or type given."); } }, - async createPost({content}: { content: string }) { + 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 { - const post = await dataaccess.createPost(content, req.session.userId); - globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post); - return post; + 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); @@ -482,5 +486,28 @@ export function resolver(req: any, res: any): any { 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}'`); + } + } else { + res.status(status.FORBIDDEN); + return new GraphQLError("You are not an admin."); + } + } else { + res.status(status.UNAUTHORIZED); + return new NotLoggedInGqlError(); + } + }, }; } diff --git a/src/graphql/schema.graphql b/src/graphql/schema.graphql index 657d049..edfeabc 100644 --- a/src/graphql/schema.graphql +++ b/src/graphql/schema.graphql @@ -26,6 +26,9 @@ type Query { "returns the post filtered by the sort type with pagination." getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post] + "returns all activities" + getActivities: [Activity] + "Returns an access token for the user that can be used in requests. To user the token in requests, it has to be set in the HTTP header 'Authorization' with the format Bearer ." getToken(email: String!, passwordHash: String!): Token! } @@ -67,8 +70,8 @@ type Mutation { "send a message in a Chatroom" sendMessage(chatId: ID!, content: String!): ChatMessage - "create the post" - createPost(content: String!): Post! + "create a post that can belong to an activity" + createPost(content: String!, activityId: ID): Post! "delete the post for a given post id" deletePost(postId: ID!): Boolean! @@ -99,6 +102,9 @@ type Mutation { "Leaves a event." leaveEvent(eventId: ID!): Event + + "Creates an activity. Can only be used by admins." + createActivity(name: String!, description: String!, points: Int!): Activity } interface UserData { @@ -114,9 +120,6 @@ interface UserData { "Id of the User" id: ID! - "DEPRECATED! the total number of posts the user posted" - numberOfPosts: Int! - "the number of posts the user has created" postCount: Int! @@ -290,6 +293,9 @@ type Post { "if the post can be deleted by the specified user" deletable(userId: ID!): Boolean + + "the activity that belongs to the post" + activity: Activity } "represents a request of any type" @@ -407,6 +413,22 @@ type SearchResult { events: [Event!]! } +"An activity that grants points" +type Activity { + + "the id of the activity" + id: ID! + + "the name of the activity" + name: String! + + "the description of the activity" + description: String! + + "the number of points the activity grants" + points: Int! +} + "represents the type of vote performed on a post" enum VoteType { UPVOTE diff --git a/src/lib/dataAccess.ts b/src/lib/dataAccess.ts index 9e19163..25ff897 100644 --- a/src/lib/dataAccess.ts +++ b/src/lib/dataAccess.ts @@ -1,9 +1,12 @@ import * as crypto from "crypto"; +import {GraphQLError} from "graphql"; import * as sqz from "sequelize"; import {Sequelize} from "sequelize-typescript"; +import {ActivityNotFoundError} from "./errors/ActivityNotFoundError"; import {ChatNotFoundError} from "./errors/ChatNotFoundError"; import {DuplicatedRequestError} from "./errors/DuplicatedRequestError"; import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError"; +import {PostNotFoundGqlError} from "./errors/graphqlErrors"; import {GroupAlreadyExistsError} from "./errors/GroupAlreadyExistsError"; import {GroupNotFoundError} from "./errors/GroupNotFoundError"; import {InvalidLoginError} from "./errors/InvalidLoginError"; @@ -11,6 +14,7 @@ import {NoActionSpecifiedError} from "./errors/NoActionSpecifiedError"; import {UserNotFoundError} from "./errors/UserNotFoundError"; import globals from "./globals"; import {InternalEvents} from "./InternalEvents"; +import {Activity} from "./models"; import * as models from "./models"; /** @@ -54,6 +58,7 @@ namespace dataaccess { models.GroupMember, models.EventParticipant, models.Event, + models.Activity, ]); } catch (err) { globals.logger.error(err.message); @@ -165,21 +170,38 @@ namespace dataaccess { * Creates a post * @param content * @param authorId - * @param type + * @param activityId */ - export async function createPost(content: string, authorId: number, type?: string): Promise { - type = type || "MISC"; - const post = await models.Post.create({content, authorId}); - globals.internalEmitter.emit(InternalEvents.POSTCREATE, post); - return post; + export async function createPost(content: string, authorId: number, activityId?: number): Promise { + const activity = await models.Activity.findByPk(activityId); + if (!activityId || activity) { + const post = await models.Post.create({content, authorId, activityId}); + globals.internalEmitter.emit(InternalEvents.POSTCREATE, post); + if (activity) { + const user = await models.User.findByPk(authorId); + user.rankpoints += activity.points; + await user.save(); + } + return post; + } else { + throw new ActivityNotFoundError(activityId); + } } /** * Deletes a post * @param postId */ - export async function deletePost(postId: number): Promise { - await (await models.Post.findByPk(postId)).destroy(); + 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(); + const author = await post.author(); + author.rankpoints -= activity.points; + await author.save(); + } catch (err) { + return new PostNotFoundGqlError(postId); + } return true; } diff --git a/src/lib/errors/ActivityNotFoundError.ts b/src/lib/errors/ActivityNotFoundError.ts new file mode 100644 index 0000000..839afa6 --- /dev/null +++ b/src/lib/errors/ActivityNotFoundError.ts @@ -0,0 +1,7 @@ +import {BaseError} from "./BaseError"; + +export class ActivityNotFoundError extends BaseError { + constructor(id: number) { + super(`The activity with the id ${id} could not be found.`); + } +} diff --git a/src/lib/models/Activity.ts b/src/lib/models/Activity.ts new file mode 100644 index 0000000..8b56ad2 --- /dev/null +++ b/src/lib/models/Activity.ts @@ -0,0 +1,18 @@ +import * as sqz from "sequelize"; +import {Column, ForeignKey, Model, NotNull, Table, Unique} from "sequelize-typescript"; + +@Table({underscored: true}) +export class Activity extends Model { + + @Unique + @NotNull + @Column({type: sqz.STRING(128), allowNull: false, unique: true}) + public name: string; + + @NotNull + @Column({type: sqz.TEXT, allowNull: false}) + public description: string; + + @Column + public points: number; +} diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts index e9fef2b..39f2ebc 100644 --- a/src/lib/models/Post.ts +++ b/src/lib/models/Post.ts @@ -1,6 +1,7 @@ import * as sqz from "sequelize"; import {BelongsTo, BelongsToMany, Column, CreatedAt, ForeignKey, Model, NotNull, Table} from "sequelize-typescript"; import markdown from "../markdown"; +import {Activity} from "./Activity"; import {PostVote, VoteType} from "./PostVote"; import {User} from "./User"; @@ -15,9 +16,16 @@ export class Post extends Model { @Column({allowNull: false}) public authorId: number; + @ForeignKey(() => Activity) + @Column({allowNull: true}) + public activityId: number; + @BelongsTo(() => User, "authorId") public rAuthor: User; + @BelongsTo(() => Activity, "activityId") + public rActivity?: Activity; + @BelongsToMany(() => User, () => PostVote) public rVotes: Array; @@ -31,6 +39,13 @@ export class Post extends Model { return await this.$get("rAuthor") as User; } + /** + * Returns the activity of the post. + */ + public async activity(): Promise { + return await this.$get("rActivity") as Activity; + } + /** * Returns the votes on a post */ @@ -89,7 +104,7 @@ export class Post extends Model { } /** - * Returns the type of vote that was performend on the post by the user specified by the user id. + * Returns the type of vote that was performed on the post by the user specified by the user id. * @param userId */ public async userVote({userId}: {userId: number}): Promise { diff --git a/src/lib/models/User.ts b/src/lib/models/User.ts index 8db31e9..3ec3dc1 100644 --- a/src/lib/models/User.ts +++ b/src/lib/models/User.ts @@ -207,20 +207,17 @@ export class User extends Model { return await this.$get("rReceivedRequests") as Request[]; } + /** + * a list of posts the user has created + * @param first + * @param offset + */ public async posts({first, offset}: { first: number, offset: number }): Promise { const limit = first ?? 10; offset = offset ?? 0; return await this.$get("rPosts", {limit, offset}) as Post[]; } - /** - * @deprecated - * use {@link postCount} instead - */ - public async numberOfPosts(): Promise { - return this.postCount(); - } - /** * number of posts the user created */ diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts index 2a669ba..823dfd7 100644 --- a/src/lib/models/index.ts +++ b/src/lib/models/index.ts @@ -11,3 +11,4 @@ export {GroupAdmin} from "./GroupAdmin"; export {GroupMember} from "./GroupMember"; export {Event} from "./Event"; export {EventParticipant} from "./EventParticipant"; +export {Activity} from "./Activity"; From 72a7f3222cd0594ed094c1f106e29e8f3459c614 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Thu, 9 Jan 2020 11:16:45 +0100 Subject: [PATCH 15/15] Updated Changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f99a7e..fd8da81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Jenkinsfile - Mocha Tests - worker initialization error handling -- bearer token authentification for testing purposes +- bearer token authentication for testing purposes - Added `deletable' field on post - Admin field that for admin users - ability for admins to delete posts @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - profilePicture property to User model which is an url to the users profile picture - activities to posts - getActivities field to receive all activities +- createActivity mutation - activities table ### Removed