Merge branch 'julius-dev' of Software_Engineering_I/greenvironment-server into develop

pull/2/head
Trivernis 5 years ago committed by Gitea
commit b3e89b0c51

22
.gitignore vendored

@ -1,11 +1,11 @@
.nyc_output/ .nyc_output/
coverage/ coverage/
node_modules/ node_modules/
npm-debug.log npm-debug.log
test/*.log test/*.log
dist dist
.idea .idea
config.yaml config.yaml
sqz-force sqz-force
greenvironment.db greenvironment.db
logs logs

@ -1,19 +1,19 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [Unreleased]
### Added ### Added
- Graphql Schema - Graphql Schema
- default-config file and generation of config file on startup - default-config file and generation of config file on startup
- DTOs - DTOs
- Home Route - Home Route
- session management - session management
- Sequelize models and integration - Sequelize models and integration
- Sequelize-typescript integration - Sequelize-typescript integration
- error pages - error pages
- pagination for most list types - pagination for most list types

@ -1,11 +1,11 @@
FROM node:current-alpine FROM node:current-alpine
COPY . /home/node/green COPY . /home/node/green
WORKDIR /home/node/green WORKDIR /home/node/green
RUN npm install -g gulp RUN npm install -g gulp
RUN npm install --save-dev RUN npm install --save-dev
RUN npm rebuild node-sass RUN npm rebuild node-sass
RUN gulp RUN gulp
COPY . . COPY . .
EXPOSE 8080 EXPOSE 8080
CMD ["npm" , "run"] CMD ["npm" , "run"]

@ -1,11 +1,11 @@
# greenvironment-server # greenvironment-server
Server of the greenvironment social network. Server of the greenvironment social network.
## Install ## Install
You need to install a nodejs runtime to run the greenvironment server. You need to install a nodejs runtime to run the greenvironment server.
Then you need to install all requirements. To do so, open a terminal in the Then you need to install all requirements. To do so, open a terminal in the
greenvironment project folder and execute "npm i". You can build the project by greenvironment project folder and execute "npm i". You can build the project by
executing "gulp" in the terminal. To run the server you need executing "gulp" in the terminal. To run the server you need
to execute "node ./dist". to execute "node ./dist".

@ -1,11 +1,11 @@
version: "3" version: "3"
services: services:
greenvironment: greenvironment:
build: . build: .
user: "root" user: "root"
working_dir: /home/node/green working_dir: /home/node/green
environment: environment:
- NODE_ENV=production - NODE_ENV=production
ports: ports:
- "8080:8080" - "8080:8080"
command: "npm start" command: "npm start"

@ -1,47 +1,47 @@
const {src, dest, watch, series, task} = require('gulp'); const {src, dest, watch, series, task} = require('gulp');
const sass = require('gulp-sass'); const sass = require('gulp-sass');
const ts = require('gulp-typescript'); const ts = require('gulp-typescript');
const minify = require('gulp-minify'); const minify = require('gulp-minify');
const del = require('delete'); const del = require('delete');
const gulp = require('gulp'); const gulp = require('gulp');
function clearDist(cb) { function clearDist(cb) {
del('dist/*', cb); del('dist/*', cb);
} }
function compileTypescript() { function compileTypescript() {
let tsProject = ts.createProject('tsconfig.json'); let tsProject = ts.createProject('tsconfig.json');
let tsResult = tsProject.src().pipe(tsProject()); let tsResult = tsProject.src().pipe(tsProject());
return tsResult return tsResult
//.pipe(minify()) //.pipe(minify())
.pipe(dest('dist')); .pipe(dest('dist'));
} }
function minifyJs() { function minifyJs() {
return src('src/public/javascripts/**/*.js') return src('src/public/javascripts/**/*.js')
.pipe(minify({ .pipe(minify({
ext: { ext: {
src: '-debug.js', src: '-debug.js',
min: '.js' min: '.js'
} }
})) }))
.pipe(dest('dist/public/javascripts')); .pipe(dest('dist/public/javascripts'));
} }
function compileSass() { function compileSass() {
return src('src/public/stylesheets/sass/**/style.sass') return src('src/public/stylesheets/sass/**/style.sass')
.pipe(sass().on('error', sass.logError)) .pipe(sass().on('error', sass.logError))
.pipe(dest('dist/public/stylesheets')); .pipe(dest('dist/public/stylesheets'));
} }
function moveRemaining() { function moveRemaining() {
return src(['src/**/*', '!src/**/*.ts', '!src/**/*.sass', '!src/**/*.js']) return src(['src/**/*', '!src/**/*.ts', '!src/**/*.sass', '!src/**/*.js'])
.pipe(dest('dist')); .pipe(dest('dist'));
} }
task('default', series(clearDist, compileTypescript, minifyJs, compileSass, moveRemaining)); task('default', series(clearDist, compileTypescript, minifyJs, compileSass, moveRemaining));
task('watch', () => { task('watch', () => {
watch('src/public/stylesheets/sass/**/*.sass', compileSass); watch('src/public/stylesheets/sass/**/*.sass', compileSass);
watch('**/*.js', minifyJs); watch('**/*.js', minifyJs);
watch('**/*.ts', compileTypescript); watch('**/*.ts', compileTypescript);
}); });

15800
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,79 +1,79 @@
{ {
"name": "greenvironment-server", "name": "greenvironment-server",
"version": "0.1.0", "version": "0.1.0",
"description": "Server for greenvironment network", "description": "Server for greenvironment network",
"main": "./dist/index.js", "main": "./dist/index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build": "gulp", "build": "gulp",
"start": "node ./dist/index.js" "start": "node ./dist/index.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.trivernis.net/Software_Engineering_I/greenvironment-server.git" "url": "https://git.trivernis.net/Software_Engineering_I/greenvironment-server.git"
}, },
"keywords": [ "keywords": [
"server", "server",
"nodejs", "nodejs",
"express" "express"
], ],
"author": "SoftEngI", "author": "SoftEngI",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/bluebird": "^3.5.27", "@types/bluebird": "^3.5.27",
"@types/compression": "^1.0.1", "@types/compression": "^1.0.1",
"@types/connect-pg-simple": "^4.2.0", "@types/connect-pg-simple": "^4.2.0",
"@types/cookie-parser": "^1.4.2", "@types/cookie-parser": "^1.4.2",
"@types/cors": "^2.8.6", "@types/cors": "^2.8.6",
"@types/express": "^4.17.1", "@types/express": "^4.17.1",
"@types/express-graphql": "^0.8.0", "@types/express-graphql": "^0.8.0",
"@types/express-session": "^1.15.14", "@types/express-session": "^1.15.14",
"@types/express-socket.io-session": "^1.3.2", "@types/express-socket.io-session": "^1.3.2",
"@types/fs-extra": "^8.0.0", "@types/fs-extra": "^8.0.0",
"@types/graphql": "^14.2.3", "@types/graphql": "^14.2.3",
"@types/http-status": "^0.2.30", "@types/http-status": "^0.2.30",
"@types/js-yaml": "^3.12.1", "@types/js-yaml": "^3.12.1",
"@types/markdown-it": "0.0.9", "@types/markdown-it": "0.0.9",
"@types/node": "^12.7.12", "@types/node": "^12.7.12",
"@types/pg": "^7.11.0", "@types/pg": "^7.11.0",
"@types/sequelize": "^4.28.5", "@types/sequelize": "^4.28.5",
"@types/socket.io": "^2.1.2", "@types/socket.io": "^2.1.2",
"@types/validator": "^10.11.3", "@types/validator": "^10.11.3",
"@types/winston": "^2.4.4", "@types/winston": "^2.4.4",
"delete": "^1.1.0", "delete": "^1.1.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-minify": "^3.1.0", "gulp-minify": "^3.1.0",
"gulp-sass": "^4.0.2", "gulp-sass": "^4.0.2",
"gulp-typescript": "^5.0.1", "gulp-typescript": "^5.0.1",
"ts-lint": "^4.5.1", "ts-lint": "^4.5.1",
"tsc": "^1.20150623.0", "tsc": "^1.20150623.0",
"tslint": "^5.19.0", "tslint": "^5.19.0",
"typescript": "^3.5.3" "typescript": "^3.5.3"
}, },
"dependencies": { "dependencies": {
"compression": "^1.7.4", "compression": "^1.7.4",
"connect-session-sequelize": "^6.0.0", "connect-session-sequelize": "^6.0.0",
"cookie-parser": "^1.4.4", "cookie-parser": "^1.4.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.17.1", "express": "^4.17.1",
"express-graphql": "^0.9.0", "express-graphql": "^0.9.0",
"express-session": "^1.16.2", "express-session": "^1.16.2",
"express-socket.io-session": "^1.3.5", "express-socket.io-session": "^1.3.5",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"graphql": "^14.4.2", "graphql": "^14.4.2",
"graphql-import": "^0.7.1", "graphql-import": "^0.7.1",
"http-status": "^1.3.2", "http-status": "^1.3.2",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"markdown-it": "^10.0.0", "markdown-it": "^10.0.0",
"markdown-it-emoji": "^1.4.0", "markdown-it-emoji": "^1.4.0",
"pg": "^7.12.1", "pg": "^7.12.1",
"pug": "^2.0.4", "pug": "^2.0.4",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"sequelize": "^5.19.6", "sequelize": "^5.19.6",
"sequelize-typescript": "^1.0.0", "sequelize-typescript": "^1.0.0",
"socket.io": "^2.2.0", "socket.io": "^2.2.0",
"sqlite3": "^4.1.0", "sqlite3": "^4.1.0",
"winston": "^3.2.1", "winston": "^3.2.1",
"winston-daily-rotate-file": "^4.2.1" "winston-daily-rotate-file": "^4.2.1"
} }
} }

@ -1,114 +1,118 @@
import * as compression from "compression"; import * as compression from "compression";
import * as cookieParser from "cookie-parser"; import * as cookieParser from "cookie-parser";
import * as cors from "cors"; import * as cors from "cors";
import {Request, Response} from "express"; import {Request, Response} from "express";
import * as express from "express"; import * as express from "express";
import * as graphqlHTTP from "express-graphql"; import * as graphqlHTTP from "express-graphql";
import * as session from "express-session"; import * as session from "express-session";
import sharedsession = require("express-socket.io-session"); import sharedsession = require("express-socket.io-session");
import * as fsx from "fs-extra"; import * as fsx from "fs-extra";
import {buildSchema} from "graphql"; import {buildSchema} from "graphql";
import {importSchema} from "graphql-import"; import {importSchema} from "graphql-import";
import * as http from "http"; import * as http from "http";
import * as httpStatus from "http-status"; import * as httpStatus from "http-status";
import * as path from "path"; import * as path from "path";
import {Sequelize} from "sequelize-typescript"; import {Sequelize} from "sequelize-typescript";
import * as socketIo from "socket.io"; import * as socketIo from "socket.io";
import {resolver} from "./graphql/resolvers"; import {resolver} from "./graphql/resolvers";
import dataaccess from "./lib/dataaccess"; import dataaccess from "./lib/dataaccess";
import globals from "./lib/globals"; import globals from "./lib/globals";
import routes from "./routes"; import routes from "./routes";
const SequelizeStore = require("connect-session-sequelize")(session.Store); const SequelizeStore = require("connect-session-sequelize")(session.Store);
const logger = globals.logger; const logger = globals.logger;
class App { class App {
public app: express.Application; public app: express.Application;
public io: socketIo.Server; public io: socketIo.Server;
public server: http.Server; public server: http.Server;
public readonly sequelize: Sequelize; public readonly sequelize: Sequelize;
constructor() { constructor() {
this.app = express(); this.app = express();
this.server = new http.Server(this.app); this.server = new http.Server(this.app);
this.io = socketIo(this.server); this.io = socketIo(this.server);
this.sequelize = new Sequelize(globals.config.database.connectionUri ); this.sequelize = new Sequelize(globals.config.database.connectionUri );
} }
/** /**
* initializes everything that needs to be initialized asynchronous. * initializes everything that needs to be initialized asynchronous.
*/ */
public async init() { public async init() {
await dataaccess.init(this.sequelize); await dataaccess.init(this.sequelize);
const appSession = session({ const appSession = session({
cookie: { cookie: {
maxAge: Number(globals.config.session.cookieMaxAge) || 604800000, maxAge: Number(globals.config.session.cookieMaxAge) || 604800000,
secure: "auto", secure: "auto",
}, },
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
secret: globals.config.session.secret, secret: globals.config.session.secret,
store: new SequelizeStore({db: this.sequelize}), store: new SequelizeStore({db: this.sequelize}),
}); });
const force = fsx.existsSync("sqz-force"); const force = fsx.existsSync("sqz-force");
logger.info(`Sequelize Table force: ${force}`); logger.info(`Sequelize Table force: ${force}`);
await this.sequelize.sync({force, logging: (msg) => logger.silly(msg)}); await this.sequelize.sync({force, logging: (msg) => logger.silly(msg)});
await routes.ioListeners(this.io); await routes.ioListeners(this.io);
this.io.use(sharedsession(appSession, {autoSave: true})); this.io.use(sharedsession(appSession, {autoSave: true}));
this.app.set("views", path.join(__dirname, "views")); this.app.set("views", path.join(__dirname, "views"));
this.app.set("view engine", "pug"); this.app.set("view engine", "pug");
this.app.set("trust proxy", 1); this.app.set("trust proxy", 1);
this.app.use(compression()); this.app.use(compression());
this.app.use(express.json()); this.app.use(express.json());
this.app.use(express.urlencoded({extended: false})); this.app.use(express.urlencoded({extended: false}));
this.app.use(express.static(path.join(__dirname, "public"))); this.app.use(express.static(path.join(__dirname, "public")));
this.app.use(cookieParser()); this.app.use(cookieParser());
this.app.use(appSession); this.app.use(appSession);
if (globals.config.server.cors) { if (globals.config.server.cors) {
this.app.use(cors()); this.app.use(cors());
} }
this.app.use((req, res, next) => { this.app.use((req, res, next) => {
logger.verbose(`${req.method} ${req.url}`); logger.verbose(`${req.method} ${req.url}`);
next(); next();
}); });
this.app.use(routes.router); this.app.use(routes.router);
this.app.use("/graphql", graphqlHTTP((request, response) => { this.app.use("/graphql", graphqlHTTP((request, response) => {
return { return {
// @ts-ignore all // @ts-ignore all
context: {session: request.session}, context: {session: request.session},
graphiql: true, graphiql: true,
rootValue: resolver(request, response), rootValue: resolver(request, response),
schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))), schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))),
}; };
})); }));
this.app.use((req: any, res: Response) => { this.app.use((req: any, res: Response) => {
res.status(httpStatus.NOT_FOUND); if (globals.config.frontend.angularIndex) {
res.render("errors/404.pug", {url: req.url}); res.sendFile(path.join(__dirname, globals.config.frontend.angularIndex));
}); } else {
this.app.use((err, req: Request, res: Response) => { res.status(httpStatus.NOT_FOUND);
res.status(httpStatus.INTERNAL_SERVER_ERROR); res.render("errors/404.pug", {url: req.url});
res.render("errors/500.pug"); }
}); });
} this.app.use((err, req: Request, res: Response) => {
res.status(httpStatus.INTERNAL_SERVER_ERROR);
/** res.render("errors/500.pug");
* Starts the web server. });
*/ }
public start() {
if (globals.config.server.port) { /**
logger.info(`Starting server...`); * Starts the web server.
this.app.listen(globals.config.server.port); */
logger.info(`Server running on port ${globals.config.server.port}`); public start() {
} else { if (globals.config.server.port) {
logger.error("No port specified in the config." + logger.info(`Starting server...`);
"Please configure a port in the config.yaml."); this.app.listen(globals.config.server.port);
} logger.info(`Server running on port ${globals.config.server.port}`);
} } else {
} logger.error("No port specified in the config." +
"Please configure a port in the config.yaml.");
export default App; }
}
}
export default App;

@ -1,22 +1,25 @@
# database connection info # database connection info
database: database:
connectionUri: "sqlite://greenvironment.db" connectionUri: "sqlite://greenvironment.db"
# http server configuration # http server configuration
server: server:
port: 8080 port: 8080
cors: false cors: false
# configuration of sessions # configuration of sessions
session: session:
secret: REPLACE WITH SAFE RANDOM GENERATED SECRET secret: REPLACE WITH SAFE RANDOM GENERATED SECRET
cookieMaxAge: 604800000 # 7 days cookieMaxAge: 604800000 # 7 days
# configuration of the markdown parser # configuration of the markdown parser
markdown: markdown:
plugins: plugins:
- 'markdown-it-emoji' - 'markdown-it-emoji'
# configuration for logging # configuration for logging
logging: logging:
level: info level: info
frontend:
angularIndex:

@ -1,371 +1,371 @@
import {AggregateError} from "bluebird"; import {AggregateError} from "bluebird";
import {GraphQLError} from "graphql"; import {GraphQLError} from "graphql";
import * as status from "http-status"; import * as status from "http-status";
import dataaccess from "../lib/dataaccess"; import dataaccess from "../lib/dataaccess";
import {NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors"; import {NotLoggedInGqlError, PostNotFoundGqlError} from "../lib/errors/graphqlErrors";
import globals from "../lib/globals"; import globals from "../lib/globals";
import {InternalEvents} from "../lib/InternalEvents"; import {InternalEvents} from "../lib/InternalEvents";
import * as models from "../lib/models"; import * as models from "../lib/models";
import {is} from "../lib/regex"; import {is} from "../lib/regex";
/** /**
* Returns the resolvers for the graphql api. * Returns the resolvers for the graphql api.
* @param req - the request object * @param req - the request object
* @param res - the response object * @param res - the response object
*/ */
export function resolver(req: any, res: any): any { export function resolver(req: any, res: any): any {
return { return {
async getSelf() { async getSelf() {
if (req.session.userId) { if (req.session.userId) {
return models.User.findByPk(req.session.userId); return models.User.findByPk(req.session.userId);
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async getUser({userId, handle}: { userId: number, handle: string }) { async getUser({userId, handle}: { userId: number, handle: string }) {
if (handle) { if (handle) {
return await dataaccess.getUserByHandle(handle); return await dataaccess.getUserByHandle(handle);
} else if (userId) { } else if (userId) {
return models.User.findByPk(userId); return models.User.findByPk(userId);
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No userId or handle provided."); return new GraphQLError("No userId or handle provided.");
} }
}, },
async getPost({postId}: { postId: number }) { async getPost({postId}: { postId: number }) {
if (postId) { if (postId) {
return await dataaccess.getPost(postId); return await dataaccess.getPost(postId);
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No postId given."); return new GraphQLError("No postId given.");
} }
}, },
async getChat({chatId}: { chatId: number }) { async getChat({chatId}: { chatId: number }) {
if (chatId) { if (chatId) {
return models.ChatRoom.findByPk(chatId); return models.ChatRoom.findByPk(chatId);
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No chatId given."); return new GraphQLError("No chatId given.");
} }
}, },
async getGroup({groupId}: {groupId: number}) { async getGroup({groupId}: {groupId: number}) {
if (groupId) { if (groupId) {
return models.Group.findByPk(groupId); return models.Group.findByPk(groupId);
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No group id given."); return new GraphQLError("No group id given.");
} }
}, },
async getRequest({requestId}: {requestId: number}) { async getRequest({requestId}: {requestId: number}) {
if (requestId) { if (requestId) {
return models.Request.findByPk(requestId); return models.Request.findByPk(requestId);
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No requestId given."); return new GraphQLError("No requestId given.");
} }
}, },
acceptCookies() { acceptCookies() {
req.session.cookiesAccepted = true; req.session.cookiesAccepted = true;
return true; return true;
}, },
async login({email, passwordHash}: { email: string, passwordHash: string }) { async login({email, passwordHash}: { email: string, passwordHash: string }) {
if (email && passwordHash) { if (email && passwordHash) {
try { try {
const user = await dataaccess.getUserByLogin(email, passwordHash); const user = await dataaccess.getUserByLogin(email, passwordHash);
req.session.userId = user.id; req.session.userId = user.id;
return user; return user;
} catch (err) { } catch (err) {
globals.logger.warn(err.message); globals.logger.warn(err.message);
globals.logger.debug(err.stack); globals.logger.debug(err.stack);
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return err.graphqlError || err.message; return err.graphqlError || err.message;
} }
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No email or password given."); return new GraphQLError("No email or password given.");
} }
}, },
logout() { logout() {
if (req.session.user) { if (req.session.user) {
delete req.session.user; delete req.session.user;
return true; return true;
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) { async register({username, email, passwordHash}: { username: string, email: string, passwordHash: string }) {
if (username && email && passwordHash) { if (username && email && passwordHash) {
if (!is.email(email)) { if (!is.email(email)) {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError(`'${email}' is not a valid email address!`); return new GraphQLError(`'${email}' is not a valid email address!`);
} }
try { try {
const user = await dataaccess.registerUser(username, email, passwordHash); const user = await dataaccess.registerUser(username, email, passwordHash);
req.session.userId = user.id; req.session.userId = user.id;
return user; return user;
} catch (err) { } catch (err) {
globals.logger.warn(err.message); globals.logger.warn(err.message);
globals.logger.debug(err.stack); globals.logger.debug(err.stack);
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return err.graphqlError || err.message; return err.graphqlError || err.message;
} }
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No username, email or password given."); return new GraphQLError("No username, email or password given.");
} }
}, },
async vote({postId, type}: { postId: number, type: dataaccess.VoteType }) { async vote({postId, type}: { postId: number, type: dataaccess.VoteType }) {
if (postId && type) { if (postId && type) {
if (req.session.userId) { if (req.session.userId) {
const post = await models.Post.findByPk(postId); const post = await models.Post.findByPk(postId);
if (post) { if (post) {
return await post.vote(req.session.userId, type); return await post.vote(req.session.userId, type);
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new PostNotFoundGqlError(postId); return new PostNotFoundGqlError(postId);
} }
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No postId or type given."); return new GraphQLError("No postId or type given.");
} }
}, },
async createPost({content}: { content: string }) { async createPost({content}: { content: string }) {
if (content) { if (content) {
if (req.session.userId) { if (req.session.userId) {
const post = await dataaccess.createPost(content, req.session.userId); const post = await dataaccess.createPost(content, req.session.userId);
globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post); globals.internalEmitter.emit(InternalEvents.GQLPOSTCREATE, post);
return post; return post;
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("Can't create empty post."); return new GraphQLError("Can't create empty post.");
} }
}, },
async deletePost({postId}: { postId: number }) { async deletePost({postId}: { postId: number }) {
if (postId) { if (postId) {
const post = await models.Post.findByPk(postId, {include: [models.User]}); const post = await models.Post.findByPk(postId, {include: [models.User]});
if (post.rAuthor.id === req.session.userId) { if (post.rAuthor.id === req.session.userId) {
return await dataaccess.deletePost(post.id); return await dataaccess.deletePost(post.id);
} else { } else {
res.status(status.FORBIDDEN); res.status(status.FORBIDDEN);
return new GraphQLError("User is not author of the post."); return new GraphQLError("User is not author of the post.");
} }
} else { } else {
return new GraphQLError("No postId given."); return new GraphQLError("No postId given.");
} }
}, },
async createChat({members}: { members: number[] }) { async createChat({members}: { members: number[] }) {
if (req.session.userId) { if (req.session.userId) {
const chatMembers = [req.session.userId]; const chatMembers = [req.session.userId];
if (members) { if (members) {
chatMembers.push(...members); chatMembers.push(...members);
} }
return await dataaccess.createChat(...chatMembers); return await dataaccess.createChat(...chatMembers);
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async sendMessage({chatId, content}: { chatId: number, content: string }) { async sendMessage({chatId, content}: { chatId: number, content: string }) {
if (!req.session.userId) { if (!req.session.userId) {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
if (chatId && content) { if (chatId && content) {
try { try {
const message = await dataaccess.sendChatMessage(req.session.userId, chatId, content); const message = await dataaccess.sendChatMessage(req.session.userId, chatId, content);
globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message); globals.internalEmitter.emit(InternalEvents.GQLCHATMESSAGE, message);
return message; return message;
} catch (err) { } catch (err) {
globals.logger.warn(err.message); globals.logger.warn(err.message);
globals.logger.debug(err.stack); globals.logger.debug(err.stack);
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return err.graphqlError || err.message; return err.graphqlError || err.message;
} }
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No chatId or content given."); return new GraphQLError("No chatId or content given.");
} }
}, },
async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }) { async sendRequest({receiver, type}: { receiver: number, type: dataaccess.RequestType }) {
if (!req.session.userId) { if (!req.session.userId) {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
if (receiver && type) { if (receiver && type) {
return await dataaccess.createRequest(req.session.userId, receiver, type); return await dataaccess.createRequest(req.session.userId, receiver, type);
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No receiver or type given."); return new GraphQLError("No receiver or type given.");
} }
}, },
async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { async denyRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) {
if (!req.session.userId) { if (!req.session.userId) {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
if (sender && type) { if (sender && type) {
const user = await models.User.findByPk(req.session.userId); const user = await models.User.findByPk(req.session.userId);
await user.denyRequest(sender, type); await user.denyRequest(sender, type);
return true; return true;
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No sender or type given."); return new GraphQLError("No sender or type given.");
} }
}, },
async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) { async acceptRequest({sender, type}: { sender: number, type: dataaccess.RequestType }) {
if (!req.session.userId) { if (!req.session.userId) {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
if (sender && type) { if (sender && type) {
try { try {
const user = await models.User.findByPk(req.session.userId); const user = await models.User.findByPk(req.session.userId);
await user.acceptRequest(sender, type); await user.acceptRequest(sender, type);
return true; return true;
} catch (err) { } catch (err) {
globals.logger.warn(err.message); globals.logger.warn(err.message);
globals.logger.debug(err.stack); globals.logger.debug(err.stack);
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return err.graphqlError || err.message; return err.graphqlError || err.message;
} }
} else { } else {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return new GraphQLError("No sender or type given."); return new GraphQLError("No sender or type given.");
} }
}, },
async removeFriend({friendId}: {friendId: number}) { async removeFriend({friendId}: {friendId: number}) {
if (req.session.userId) { if (req.session.userId) {
const self = await models.User.findByPk(req.session.userId); const self = await models.User.findByPk(req.session.userId);
return await self.removeFriend(friendId); return await self.removeFriend(friendId);
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async getPosts({first, offset, sort}: {first: number, offset: number, sort: dataaccess.SortType}) { async getPosts({first, offset, sort}: {first: number, offset: number, sort: dataaccess.SortType}) {
return await dataaccess.getPosts(first, offset, sort); return await dataaccess.getPosts(first, offset, sort);
}, },
async createGroup({name, members}: {name: string, members: number[]}) { async createGroup({name, members}: {name: string, members: number[]}) {
if (req.session.userId) { if (req.session.userId) {
return await dataaccess.createGroup(name, req.session.userId, members); return await dataaccess.createGroup(name, req.session.userId, members);
} else { } else {
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async joinGroup({id}: {id: number}) { async joinGroup({id}: {id: number}) {
if (req.session.userId) { if (req.session.userId) {
try { try {
return await dataaccess return await dataaccess
.changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.ADD); .changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.ADD);
} catch (err) { } catch (err) {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return err.graphqlError; return err.graphqlError;
} }
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async leaveGroup({id}: {id: number}) { async leaveGroup({id}: {id: number}) {
if (req.session.userId) { if (req.session.userId) {
try { try {
return await dataaccess return await dataaccess
.changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.REMOVE); .changeGroupMembership(id, req.session.userId, dataaccess.MembershipChangeAction.REMOVE);
} catch (err) { } catch (err) {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return err.graphqlError; return err.graphqlError;
} }
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async addGroupAdmin({groupId, userId}: {groupId: number, userId: number}) { async addGroupAdmin({groupId, userId}: {groupId: number, userId: number}) {
if (req.session.userId) { if (req.session.userId) {
const group = await models.Group.findByPk(groupId); const group = await models.Group.findByPk(groupId);
const self = await models.User.findByPk(req.session.userId); const self = await models.User.findByPk(req.session.userId);
if (group && !(await group.$has("rAdmins", self)) && (await group.creator()) !== self.id) { if (group && !(await group.$has("rAdmins", self)) && (await group.creator()) !== self.id) {
res.status(status.FORBIDDEN); res.status(status.FORBIDDEN);
return new GraphQLError("You are not a group admin!"); return new GraphQLError("You are not a group admin!");
} }
try { try {
return await dataaccess return await dataaccess
.changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.OP); .changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.OP);
} catch (err) { } catch (err) {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return err.graphqlError; return err.graphqlError;
} }
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async removeGroupAdmin({groupId, userId}: {groupId: number, userId: number}) { async removeGroupAdmin({groupId, userId}: {groupId: number, userId: number}) {
if (req.session.userId) { if (req.session.userId) {
const group = await models.Group.findByPk(groupId); const group = await models.Group.findByPk(groupId);
const isCreator = Number(group.creatorId) === Number(req.session.userId); const isCreator = Number(group.creatorId) === Number(req.session.userId);
const userIsCreator = Number(group.creatorId) === Number(userId) ; const userIsCreator = Number(group.creatorId) === Number(userId) ;
if (group && !isCreator && Number(userId) !== Number(req.session.userId)) { if (group && !isCreator && Number(userId) !== Number(req.session.userId)) {
res.status(status.FORBIDDEN); res.status(status.FORBIDDEN);
return new GraphQLError("You are not the group creator!"); return new GraphQLError("You are not the group creator!");
} else if (userIsCreator) { } else if (userIsCreator) {
res.status(status.FORBIDDEN); res.status(status.FORBIDDEN);
return new GraphQLError("You are not allowed to remove a creator as an admin."); return new GraphQLError("You are not allowed to remove a creator as an admin.");
} }
try { try {
return await dataaccess return await dataaccess
.changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.DEOP); .changeGroupMembership(groupId, userId, dataaccess.MembershipChangeAction.DEOP);
} catch (err) { } catch (err) {
res.status(status.BAD_REQUEST); res.status(status.BAD_REQUEST);
return err.graphqlError; return err.graphqlError;
} }
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async createEvent({name, dueDate, groupId}: {name: string, dueDate: string, groupId: number}) { async createEvent({name, dueDate, groupId}: {name: string, dueDate: string, groupId: number}) {
if (req.session.userId) { if (req.session.userId) {
const date = new Date(dueDate); const date = new Date(dueDate);
const group = await models.Group.findByPk(groupId); const group = await models.Group.findByPk(groupId);
return group.$create<models.Event>("rEvent", {name, dueDate: date}); return group.$create<models.Event>("rEvent", {name, dueDate: date});
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async joinEvent({eventId}: {eventId: number}) { async joinEvent({eventId}: {eventId: number}) {
if (req.session.userId) { if (req.session.userId) {
const event = await models.Event.findByPk(eventId); const event = await models.Event.findByPk(eventId);
const self = await models.User.findByPk(req.session.userId); const self = await models.User.findByPk(req.session.userId);
await event.$add("rParticipants", self); await event.$add("rParticipants", self);
return event; return event;
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
async leaveEvent({eventId}: {eventId: number}) { async leaveEvent({eventId}: {eventId: number}) {
if (req.session.userId) { if (req.session.userId) {
const event = await models.Event.findByPk(eventId); const event = await models.Event.findByPk(eventId);
const self = await models.User.findByPk(req.session.userId); const self = await models.User.findByPk(req.session.userId);
await event.$remove("rParticipants", self); await event.$remove("rParticipants", self);
return event; return event;
} else { } else {
res.status(status.UNAUTHORIZED); res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError(); return new NotLoggedInGqlError();
} }
}, },
}; };
} }

@ -1,390 +1,390 @@
type Query { type Query {
"returns the user object for a given user id or a handle (only one required)" "returns the user object for a given user id or a handle (only one required)"
getUser(userId: ID, handle: String): User getUser(userId: ID, handle: String): User
"returns the logged in user" "returns the logged in user"
getSelf: Profile getSelf: Profile
"returns the post object for a post id" "returns the post object for a post id"
getPost(postId: ID!): Post getPost(postId: ID!): Post
"returns the chat object for a chat id" "returns the chat object for a chat id"
getChat(chatId: ID!): ChatRoom getChat(chatId: ID!): ChatRoom
"return shte group object for its id" "return shte group object for its id"
getGroup(groupId: ID!): Group getGroup(groupId: ID!): Group
"returns the request object for its id" "returns the request object for its id"
getRequest(requestId: ID!): Request getRequest(requestId: ID!): Request
"find a post by the posted date or content" "find a post by the posted date or content"
findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post] findPost(first: Int, offset: Int, text: String!, postedDate: String): [Post]
"find a user by user name or handle" "find a user by user name or handle"
findUser(first: Int, offset: Int, name: String, handle: String): [User] findUser(first: Int, offset: Int, name: String, handle: String): [User]
"returns the post filtered by the sort type with pagination." "returns the post filtered by the sort type with pagination."
getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post] getPosts(first: Int=20, offset: Int=0, sort: SortType = NEW): [Post]
} }
type Mutation { type Mutation {
"Accepts the usage of cookies." "Accepts the usage of cookies."
acceptCookies: Boolean acceptCookies: Boolean
"Login of the user. The passwordHash should be a sha512 hash of the password." "Login of the user. The passwordHash should be a sha512 hash of the password."
login(email: String, passwordHash: String): Profile login(email: String, passwordHash: String): Profile
"Registers the user." "Registers the user."
register(username: String, email: String, passwordHash: String): Profile register(username: String, email: String, passwordHash: String): Profile
"Logout of the user." "Logout of the user."
logout: Boolean logout: Boolean
"Upvote/downvote a Post" "Upvote/downvote a Post"
vote(postId: ID!, type: VoteType!): VoteType vote(postId: ID!, type: VoteType!): VoteType
"Report the post" "Report the post"
report(postId: ID!): Boolean report(postId: ID!): Boolean
"send a request" "send a request"
sendRequest(receiver: ID!, type: RequestType): Request sendRequest(receiver: ID!, type: RequestType): Request
"lets you accept a request for a given request id" "lets you accept a request for a given request id"
acceptRequest(sender: ID!, type: RequestType): Boolean acceptRequest(sender: ID!, type: RequestType): Boolean
"lets you deny a request for a given request id" "lets you deny a request for a given request id"
denyRequest(requestId: ID!): Boolean denyRequest(requestId: ID!): Boolean
"removes a friend" "removes a friend"
removeFriend(friendId: ID!): Boolean removeFriend(friendId: ID!): Boolean
"send a message in a Chatroom" "send a message in a Chatroom"
sendMessage(chatId: ID!, content: String!): ChatMessage sendMessage(chatId: ID!, content: String!): ChatMessage
"create the post" "create the post"
createPost(content: String!): Post! createPost(content: String!): Post!
"delete the post for a given post id" "delete the post for a given post id"
deletePost(postId: ID!): Boolean! deletePost(postId: ID!): Boolean!
"Creates a chat between the user (and optional an other user)" "Creates a chat between the user (and optional an other user)"
createChat(members: [ID!]): ChatRoom! createChat(members: [ID!]): ChatRoom!
"Creates a new group with a given name and additional members" "Creates a new group with a given name and additional members"
createGroup(name: String!, members: [ID!]): Group! createGroup(name: String!, members: [ID!]): Group!
"Joins a group with the given id" "Joins a group with the given id"
joinGroup(id: ID!): Group joinGroup(id: ID!): Group
"leaves the group with the given id" "leaves the group with the given id"
leaveGroup(id: ID!): Group leaveGroup(id: ID!): Group
"adds an admin to the group" "adds an admin to the group"
addGroupAdmin(groupId: ID!, userId: ID!): Group addGroupAdmin(groupId: ID!, userId: ID!): Group
"removes an admin from the group" "removes an admin from the group"
removeGroupAdmin(groupId: ID!, userId: ID!): Group removeGroupAdmin(groupId: ID!, userId: ID!): Group
"Creates a new event with a epoch due date on a group." "Creates a new event with a epoch due date on a group."
createEvent(name: String, dueDate: String, groupId: ID!): Event createEvent(name: String, dueDate: String, groupId: ID!): Event
"Joins a event." "Joins a event."
joinEvent(eventId: ID!): Event joinEvent(eventId: ID!): Event
"Leaves a event." "Leaves a event."
leaveEvent(eventId: ID!): Event leaveEvent(eventId: ID!): Event
} }
interface UserData { interface UserData {
"url for the Profile picture of the User" "url for the Profile picture of the User"
profilePicture: String profilePicture: String
"name of the User" "name of the User"
name: String! name: String!
"unique identifier name from the User" "unique identifier name from the User"
handle: String! handle: String!
"Id of the User" "Id of the User"
id: ID! id: ID!
"DEPRECATED! the total number of posts the user posted" "DEPRECATED! the total number of posts the user posted"
numberOfPosts: Int! numberOfPosts: Int!
"the number of posts the user has created" "the number of posts the user has created"
postCount: Int! postCount: Int!
"returns a given number of posts of a user" "returns a given number of posts of a user"
posts(first: Int=10, offset: Int=0): [Post] posts(first: Int=10, offset: Int=0): [Post]
"creation date of the user account" "creation date of the user account"
joinedAt: String! joinedAt: String!
"all friends of the user" "all friends of the user"
friends(first: Int=10, offset: Int=0): [User] friends(first: Int=10, offset: Int=0): [User]
"The number of friends the user has" "The number of friends the user has"
friendCount: Int! friendCount: Int!
"The groups the user has joined" "The groups the user has joined"
groups(first: Int=10, offset: Int=0): [Group] groups(first: Int=10, offset: Int=0): [Group]
"The numbef of groups the user has joined" "The numbef of groups the user has joined"
groupCount: Int! groupCount: Int!
"the points of the user" "the points of the user"
points: Int! points: Int!
"the levels of the user depending on the points" "the levels of the user depending on the points"
level: Int! level: Int!
} }
"represents a single user account" "represents a single user account"
type User implements UserData{ type User implements UserData{
"url for the Profile picture of the User" "url for the Profile picture of the User"
profilePicture: String profilePicture: String
"name of the User" "name of the User"
name: String! name: String!
"unique identifier name from the User" "unique identifier name from the User"
handle: String! handle: String!
"Id of the User" "Id of the User"
id: ID! id: ID!
"the total number of posts the user posted" "the total number of posts the user posted"
numberOfPosts: Int! numberOfPosts: Int!
"returns a given number of posts of a user" "returns a given number of posts of a user"
posts(first: Int=10, offset: Int): [Post] posts(first: Int=10, offset: Int): [Post]
"the number of posts the user has created" "the number of posts the user has created"
postCount: Int! postCount: Int!
"creation date of the user account" "creation date of the user account"
joinedAt: String! joinedAt: String!
"all friends of the user" "all friends of the user"
friends(first: Int=10, offset: Int=0): [User] friends(first: Int=10, offset: Int=0): [User]
"The number of friends the user has" "The number of friends the user has"
friendCount: Int! friendCount: Int!
"the points of the user" "the points of the user"
points: Int! points: Int!
"the groups the user has joined" "the groups the user has joined"
groups(first: Int=10, offset: Int=0): [Group] groups(first: Int=10, offset: Int=0): [Group]
"The numbef of groups the user has joined" "The numbef of groups the user has joined"
groupCount: Int! groupCount: Int!
"the levels of the user depending on the points" "the levels of the user depending on the points"
level: Int! level: Int!
} }
type Profile implements UserData { type Profile implements UserData {
"url for the Profile picture of the User" "url for the Profile picture of the User"
profilePicture: String profilePicture: String
"name of the User" "name of the User"
name: String! name: String!
"the email of the user" "the email of the user"
email: String! email: String!
"returns the chatrooms the user joined." "returns the chatrooms the user joined."
chats(first: Int=10, offset: Int): [ChatRoom] chats(first: Int=10, offset: Int): [ChatRoom]
"the count of the users chats" "the count of the users chats"
chatCount: Int! chatCount: Int!
"unique identifier name from the User" "unique identifier name from the User"
handle: String! handle: String!
"Id of the User" "Id of the User"
id: ID! id: ID!
"the total number of posts the user posted" "the total number of posts the user posted"
numberOfPosts: Int! numberOfPosts: Int!
"the number of posts the user has created" "the number of posts the user has created"
postCount: Int! postCount: Int!
"returns a given number of posts of a user" "returns a given number of posts of a user"
posts(first: Int=10, offset: Int): [Post!]! posts(first: Int=10, offset: Int): [Post!]!
"creation date of the user account" "creation date of the user account"
joinedAt: String! joinedAt: String!
"all friends of the user" "all friends of the user"
friends(first: Int=10, offset: Int=0): [User!]! friends(first: Int=10, offset: Int=0): [User!]!
"The number of friends the user has" "The number of friends the user has"
friendCount: Int! friendCount: Int!
"all sent request for groupChats/friends/events" "all sent request for groupChats/friends/events"
sentRequests: [Request!]! sentRequests: [Request!]!
"all received request for groupChats/friends/events" "all received request for groupChats/friends/events"
receivedRequests: [Request!]! receivedRequests: [Request!]!
"all groups the user is an admin of" "all groups the user is an admin of"
administratedGroups: [Group!]! administratedGroups: [Group!]!
"all groups the user has created" "all groups the user has created"
createdGroups: [Group!]! createdGroups: [Group!]!
"all groups the user has joined" "all groups the user has joined"
groups(first: Int=10, offset: Int=0): [Group!]! groups(first: Int=10, offset: Int=0): [Group!]!
"The numbef of groups the user has joined" "The numbef of groups the user has joined"
groupCount: Int! groupCount: Int!
"the points of the user" "the points of the user"
points: Int! points: Int!
"the levels of the user depending on the points" "the levels of the user depending on the points"
level: Int! level: Int!
} }
"represents a single user post" "represents a single user post"
type Post { type Post {
"The id of the post." "The id of the post."
id: ID! id: ID!
"the text of the post" "the text of the post"
content: String content: String
"the content of the post rendered by markdown-it" "the content of the post rendered by markdown-it"
htmlContent: String htmlContent: String
"upvotes of the Post" "upvotes of the Post"
upvotes: Int! upvotes: Int!
"downvotes of the Post" "downvotes of the Post"
downvotes: Int! downvotes: Int!
"the user that is the author of the Post" "the user that is the author of the Post"
author: User! author: User!
"date the post was created" "date the post was created"
createdAt: String! createdAt: String!
"the type of vote the user performed on the post" "the type of vote the user performed on the post"
userVote: VoteType userVote: VoteType
} }
"represents a request of any type" "represents a request of any type"
type Request { type Request {
"Id of the request." "Id of the request."
id: ID! id: ID!
"Id of the user who sended the request" "Id of the user who sended the request"
sender: User! sender: User!
"Id of the user who received the request" "Id of the user who received the request"
receiver: User! receiver: User!
"type of the request" "type of the request"
type: RequestType! type: RequestType!
} }
"represents a chatroom" "represents a chatroom"
type ChatRoom { type ChatRoom {
"the socket.io namespace for the chatroom" "the socket.io namespace for the chatroom"
namespace: String namespace: String
"the members of the chatroom" "the members of the chatroom"
members(first: Int=10, offset: Int=0): [User!] members(first: Int=10, offset: Int=0): [User!]
"return a specfic range of messages posted in the chat" "return a specfic range of messages posted in the chat"
messages(first: Int = 10, offset: Int, containing: String): [ChatMessage]! messages(first: Int = 10, offset: Int, containing: String): [ChatMessage]!
"id of the chat" "id of the chat"
id: ID! id: ID!
} }
type ChatMessage { type ChatMessage {
"Id of the chat message" "Id of the chat message"
id: ID! id: ID!
"The author of the chat message." "The author of the chat message."
author: User! author: User!
"The chatroom the message was posted in" "The chatroom the message was posted in"
chat: ChatRoom! chat: ChatRoom!
"The timestamp when the message was posted (epoch)." "The timestamp when the message was posted (epoch)."
createdAt: String! createdAt: String!
"The content of the message." "The content of the message."
content: String! content: String!
"The content of the message rendered by markdown-it." "The content of the message rendered by markdown-it."
htmlContent: String htmlContent: String
} }
type Group { type Group {
"ID of the group" "ID of the group"
id: ID! id: ID!
"name of the group" "name of the group"
name: String! name: String!
"the creator of the group" "the creator of the group"
creator: User creator: User
"all admins of the group" "all admins of the group"
admins(first: Int=10, offset: Int=0): [User]! admins(first: Int=10, offset: Int=0): [User]!
"the members of the group with pagination" "the members of the group with pagination"
members(first: Int = 10, offset: Int = 0): [User]! members(first: Int = 10, offset: Int = 0): [User]!
"the groups chat" "the groups chat"
chat: ChatRoom chat: ChatRoom
"the events of the group" "the events of the group"
events(first: Int=10, offset: Int=0): [Event!]! events(first: Int=10, offset: Int=0): [Event!]!
} }
type Event { type Event {
"ID of the event" "ID of the event"
id: ID! id: ID!
"Name of the event" "Name of the event"
name: String! name: String!
"The date of the event." "The date of the event."
dueDate: String! dueDate: String!
"The group the event belongs to." "The group the event belongs to."
group: Group! group: Group!
"The participants of the event." "The participants of the event."
participants(first: Int=10, offset: Int=0): [User!]! participants(first: Int=10, offset: Int=0): [User!]!
} }
"represents the type of vote performed on a post" "represents the type of vote performed on a post"
enum VoteType { enum VoteType {
UPVOTE UPVOTE
DOWNVOTE DOWNVOTE
} }
""" """
represents the type of request that the user has received represents the type of request that the user has received
Currently on Friend Requests are implemented. Currently on Friend Requests are implemented.
""" """
enum RequestType { enum RequestType {
FRIENDREQUEST FRIENDREQUEST
GROUPINVITE GROUPINVITE
EVENTINVITE EVENTINVITE
} }
enum SortType { enum SortType {
TOP TOP
NEW NEW
} }

@ -1,11 +1,11 @@
import App from "./app"; import App from "./app";
/** /**
* async main function wrapper. * async main function wrapper.
*/ */
(async () => { (async () => {
const app = new App(); const app = new App();
await app.init(); await app.init();
app.start(); app.start();
})(); })();

@ -1,8 +1,8 @@
export enum InternalEvents { export enum InternalEvents {
CHATCREATE = "chatCreate", CHATCREATE = "chatCreate",
CHATMESSAGE = "chatMessage", CHATMESSAGE = "chatMessage",
GQLCHATMESSAGE = "graphqlChatMessage", GQLCHATMESSAGE = "graphqlChatMessage",
REQUESTCREATE = "requestCreate", REQUESTCREATE = "requestCreate",
POSTCREATE = "postCreate", POSTCREATE = "postCreate",
GQLPOSTCREATE = "graphqlPostCreate", GQLPOSTCREATE = "graphqlPostCreate",
} }

@ -1,27 +1,27 @@
/** /**
* @author Trivernis * @author Trivernis
* @remarks * @remarks
* *
* Taken from {@link https://github.com/Trivernis/whooshy} * Taken from {@link https://github.com/Trivernis/whooshy}
*/ */
import {Router} from "express"; import {Router} from "express";
import {Namespace, Server} from "socket.io"; import {Namespace, Server} from "socket.io";
/** /**
* Abstract Route class to be implemented by each route. * Abstract Route class to be implemented by each route.
* This class contains the socket-io Server, router and resolver * This class contains the socket-io Server, router and resolver
* for each route. * for each route.
*/ */
abstract class Route { abstract class Route {
public router?: Router; public router?: Router;
protected io?: Server; protected io?: Server;
protected ions?: Namespace; protected ions?: Namespace;
public abstract async init(...params: any): Promise<any>; public abstract async init(...params: any): Promise<any>;
public abstract async destroy(...params: any): Promise<any>; public abstract async destroy(...params: any): Promise<any>;
} }
export default Route; export default Route;

@ -1,312 +1,312 @@
import * as crypto from "crypto"; import * as crypto from "crypto";
import * as status from "http-status"; import * as status from "http-status";
import {Sequelize} from "sequelize-typescript"; import {Sequelize} from "sequelize-typescript";
import {ChatNotFoundError} from "./errors/ChatNotFoundError"; import {ChatNotFoundError} from "./errors/ChatNotFoundError";
import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError"; import {EmailAlreadyRegisteredError} from "./errors/EmailAlreadyRegisteredError";
import {GroupNotFoundGqlError, NotLoggedInGqlError} from "./errors/graphqlErrors"; import {GroupNotFoundGqlError, NotLoggedInGqlError} from "./errors/graphqlErrors";
import {GroupNotFoundError} from "./errors/GroupNotFoundError"; import {GroupNotFoundError} from "./errors/GroupNotFoundError";
import {InvalidLoginError} from "./errors/InvalidLoginError"; import {InvalidLoginError} from "./errors/InvalidLoginError";
import {NoActionSpecifiedError} from "./errors/NoActionSpecifiedError"; import {NoActionSpecifiedError} from "./errors/NoActionSpecifiedError";
import {UserNotFoundError} from "./errors/UserNotFoundError"; import {UserNotFoundError} from "./errors/UserNotFoundError";
import globals from "./globals"; import globals from "./globals";
import {InternalEvents} from "./InternalEvents"; import {InternalEvents} from "./InternalEvents";
import * as models from "./models"; import * as models from "./models";
/** /**
* Generates a new handle from the username and a base64 string of the current time. * Generates a new handle from the username and a base64 string of the current time.
* @param username * @param username
*/ */
function generateHandle(username: string) { function generateHandle(username: string) {
return `${username}.${Buffer.from(Date.now().toString()).toString("base64")}`; return `${username}.${Buffer.from(Date.now().toString()).toString("base64")}`;
} }
/** /**
* Namespace with functions to fetch initial data for wrapping. * Namespace with functions to fetch initial data for wrapping.
*/ */
namespace dataaccess { namespace dataaccess {
let sequelize: Sequelize; let sequelize: Sequelize;
/** /**
* Initializes everything that needs to be initialized asynchronous. * Initializes everything that needs to be initialized asynchronous.
*/ */
export async function init(seq: Sequelize) { export async function init(seq: Sequelize) {
sequelize = seq; sequelize = seq;
try { try {
await sequelize.addModels([ await sequelize.addModels([
models.ChatMember, models.ChatMember,
models.ChatMessage, models.ChatMessage,
models.ChatRoom, models.ChatRoom,
models.Friendship, models.Friendship,
models.Post, models.Post,
models.PostVote, models.PostVote,
models.Request, models.Request,
models.User, models.User,
models.Group, models.Group,
models.GroupAdmin, models.GroupAdmin,
models.GroupMember, models.GroupMember,
models.EventParticipant, models.EventParticipant,
models.Event, models.Event,
]); ]);
} catch (err) { } catch (err) {
globals.logger.error(err.message); globals.logger.error(err.message);
globals.logger.debug(err.stack); globals.logger.debug(err.stack);
} }
} }
/** /**
* Returns the user by handle. * Returns the user by handle.
* @param userHandle * @param userHandle
*/ */
export async function getUserByHandle(userHandle: string): Promise<models.User> { export async function getUserByHandle(userHandle: string): Promise<models.User> {
const user = await models.User.findOne({where: {handle: userHandle}}); const user = await models.User.findOne({where: {handle: userHandle}});
if (user) { if (user) {
return user; return user;
} else { } else {
throw new UserNotFoundError(userHandle); throw new UserNotFoundError(userHandle);
} }
} }
/** /**
* Returns the user by email and password * Returns the user by email and password
* @param email * @param email
* @param password * @param password
*/ */
export async function getUserByLogin(email: string, password: string): Promise<models.User> { export async function getUserByLogin(email: string, password: string): Promise<models.User> {
const hash = crypto.createHash("sha512"); const hash = crypto.createHash("sha512");
hash.update(password); hash.update(password);
password = hash.digest("hex"); password = hash.digest("hex");
const user = await models.User.findOne({where: {email}}); const user = await models.User.findOne({where: {email}});
if (user) { if (user) {
if (user.password === password) { if (user.password === password) {
return user; return user;
} else { } else {
throw new InvalidLoginError(email); throw new InvalidLoginError(email);
} }
} else { } else {
throw new UserNotFoundError(email); throw new UserNotFoundError(email);
} }
} }
/** /**
* Registers a user with a username and password returning a user * Registers a user with a username and password returning a user
* @param username * @param username
* @param email * @param email
* @param password * @param password
*/ */
export async function registerUser(username: string, email: string, password: string): Promise<models.User> { export async function registerUser(username: string, email: string, password: string): Promise<models.User> {
const hash = crypto.createHash("sha512"); const hash = crypto.createHash("sha512");
hash.update(password); hash.update(password);
password = hash.digest("hex"); password = hash.digest("hex");
const existResult = !!(await models.User.findOne({where: {username, email, password}})); const existResult = !!(await models.User.findOne({where: {username, email, password}}));
const handle = generateHandle(username); const handle = generateHandle(username);
if (!existResult) { if (!existResult) {
return models.User.create({username, email, password, handle}); return models.User.create({username, email, password, handle});
} else { } else {
throw new EmailAlreadyRegisteredError(email); throw new EmailAlreadyRegisteredError(email);
} }
} }
/** /**
* Returns a post for a given postId.s * Returns a post for a given postId.s
* @param postId * @param postId
*/ */
export async function getPost(postId: number): Promise<models.Post> { export async function getPost(postId: number): Promise<models.Post> {
const post = await models.Post.findByPk(postId); const post = await models.Post.findByPk(postId);
if (post) { if (post) {
return post; return post;
} else { } else {
return null; return null;
} }
} }
/** /**
* Returns all posts sorted by new or top with pagination. * Returns all posts sorted by new or top with pagination.
* @param first * @param first
* @param offset * @param offset
* @param sort * @param sort
*/ */
export async function getPosts(first: number, offset: number, sort: SortType) { export async function getPosts(first: number, offset: number, sort: SortType) {
if (sort === SortType.NEW) { if (sort === SortType.NEW) {
return models.Post.findAll({ return models.Post.findAll({
include: [{association: "rVotes"}], include: [{association: "rVotes"}],
limit: first, limit: first,
offset, offset,
order: [["createdAt", "DESC"]], order: [["createdAt", "DESC"]],
}); });
} else { } else {
return await sequelize.query( return await sequelize.query(
`SELECT * FROM ( `SELECT * FROM (
SELECT *, SELECT *,
(SELECT count(*) FROM post_votes WHERE vote_type = 'UPVOTE' AND post_id = posts.id) AS upvotes , (SELECT count(*) FROM post_votes WHERE vote_type = 'UPVOTE' AND post_id = posts.id) AS upvotes ,
(SELECT count(*) FROM post_votes WHERE vote_type = 'DOWNVOTE' AND post_id = posts.id) AS downvotes (SELECT count(*) FROM post_votes WHERE vote_type = 'DOWNVOTE' AND post_id = posts.id) AS downvotes
FROM posts) AS a ORDER BY (a.upvotes - a.downvotes) DESC LIMIT ? OFFSET ?`, FROM posts) AS a ORDER BY (a.upvotes - a.downvotes) DESC LIMIT ? OFFSET ?`,
{replacements: [first, offset], mapToModel: true, model: models.Post}) as models.Post[]; {replacements: [first, offset], mapToModel: true, model: models.Post}) as models.Post[];
} }
} }
/** /**
* Creates a post * Creates a post
* @param content * @param content
* @param authorId * @param authorId
* @param type * @param type
*/ */
export async function createPost(content: string, authorId: number, type?: string): Promise<models.Post> { export async function createPost(content: string, authorId: number, type?: string): Promise<models.Post> {
type = type || "MISC"; type = type || "MISC";
const post = await models.Post.create({content, authorId}); const post = await models.Post.create({content, authorId});
globals.internalEmitter.emit(InternalEvents.POSTCREATE, post); globals.internalEmitter.emit(InternalEvents.POSTCREATE, post);
return post; return post;
} }
/** /**
* Deletes a post * Deletes a post
* @param postId * @param postId
*/ */
export async function deletePost(postId: number): Promise<boolean> { export async function deletePost(postId: number): Promise<boolean> {
await (await models.Post.findByPk(postId)).destroy(); await (await models.Post.findByPk(postId)).destroy();
return true; return true;
} }
/** /**
* Creates a chatroom containing two users * Creates a chatroom containing two users
* @param members * @param members
*/ */
export async function createChat(...members: number[]): Promise<models.ChatRoom> { export async function createChat(...members: number[]): Promise<models.ChatRoom> {
return sequelize.transaction(async (t) => { return sequelize.transaction(async (t) => {
const chat = await models.ChatRoom.create({}, {transaction: t, include: [models.User]}); const chat = await models.ChatRoom.create({}, {transaction: t, include: [models.User]});
for (const member of members) { for (const member of members) {
const user = await models.User.findByPk(member); const user = await models.User.findByPk(member);
await chat.$add("rMember", user, {transaction: t}); await chat.$add("rMember", user, {transaction: t});
} }
await chat.save({transaction: t}); await chat.save({transaction: t});
globals.internalEmitter.emit(InternalEvents.CHATCREATE, chat); globals.internalEmitter.emit(InternalEvents.CHATCREATE, chat);
return chat; return chat;
}); });
} }
/** /**
* Sends a message into a chat. * Sends a message into a chat.
* @param authorId * @param authorId
* @param chatId * @param chatId
* @param content * @param content
*/ */
export async function sendChatMessage(authorId: number, chatId: number, content: string) { export async function sendChatMessage(authorId: number, chatId: number, content: string) {
const chat = await models.ChatRoom.findByPk(chatId); const chat = await models.ChatRoom.findByPk(chatId);
if (chat) { if (chat) {
const message = await chat.$create("rMessage", {content, authorId}) as models.ChatMessage; const message = await chat.$create("rMessage", {content, authorId}) as models.ChatMessage;
globals.internalEmitter.emit(InternalEvents.CHATMESSAGE, message); globals.internalEmitter.emit(InternalEvents.CHATMESSAGE, message);
return message; return message;
} else { } else {
throw new ChatNotFoundError(chatId); throw new ChatNotFoundError(chatId);
} }
} }
/** /**
* Returns all rChats. * Returns all rChats.
*/ */
export async function getAllChats(): Promise<models.ChatRoom[]> { export async function getAllChats(): Promise<models.ChatRoom[]> {
return models.ChatRoom.findAll(); return models.ChatRoom.findAll();
} }
/** /**
* Sends a request to a user. * Sends a request to a user.
* @param sender * @param sender
* @param receiver * @param receiver
* @param requestType * @param requestType
*/ */
export async function createRequest(sender: number, receiver: number, requestType?: RequestType) { export async function createRequest(sender: number, receiver: number, requestType?: RequestType) {
requestType = requestType || RequestType.FRIENDREQUEST; requestType = requestType || RequestType.FRIENDREQUEST;
const request = await models.Request.create({senderId: sender, receiverId: receiver, requestType}); const request = await models.Request.create({senderId: sender, receiverId: receiver, requestType});
globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, request); globals.internalEmitter.emit(InternalEvents.REQUESTCREATE, request);
return request; return request;
} }
/** /**
* Create a new group. * Create a new group.
* @param name * @param name
* @param creator * @param creator
* @param members * @param members
*/ */
export async function createGroup(name: string, creator: number, members: number[]): Promise<models.Group> { export async function createGroup(name: string, creator: number, members: number[]): Promise<models.Group> {
members = members || []; members = members || [];
return sequelize.transaction(async (t) => { return sequelize.transaction(async (t) => {
members.push(creator); members.push(creator);
const groupChat = await createChat(...members); const groupChat = await createChat(...members);
const group = await models.Group.create({name, creatorId: creator, chatId: groupChat.id}, {transaction: t}); const group = await models.Group.create({name, creatorId: creator, chatId: groupChat.id}, {transaction: t});
const creatorUser = await models.User.findByPk(creator, {transaction: t}); const creatorUser = await models.User.findByPk(creator, {transaction: t});
await group.$add("rAdmins", creatorUser, {transaction: t}); await group.$add("rAdmins", creatorUser, {transaction: t});
for (const member of members) { for (const member of members) {
const user = await models.User.findByPk(member, {transaction: t}); const user = await models.User.findByPk(member, {transaction: t});
await group.$add("rMembers", user, {transaction: t}); await group.$add("rMembers", user, {transaction: t});
} }
return group; return group;
}); });
} }
/** /**
* Changes the membership of a user * Changes the membership of a user
* @param groupId * @param groupId
* @param userId * @param userId
* @param action * @param action
*/ */
export async function changeGroupMembership(groupId: number, userId: number, action: MembershipChangeAction): export async function changeGroupMembership(groupId: number, userId: number, action: MembershipChangeAction):
Promise<models.Group> { Promise<models.Group> {
const group = await models.Group.findByPk(groupId); const group = await models.Group.findByPk(groupId);
if (group) { if (group) {
const user = await models.User.findByPk(userId); const user = await models.User.findByPk(userId);
if (user) { if (user) {
if (action === MembershipChangeAction.ADD) { if (action === MembershipChangeAction.ADD) {
await group.$add("rMembers", user); await group.$add("rMembers", user);
} else if (action === MembershipChangeAction.REMOVE) { } else if (action === MembershipChangeAction.REMOVE) {
await group.$remove("rMembers", user); await group.$remove("rMembers", user);
} else if (action === MembershipChangeAction.OP) { } else if (action === MembershipChangeAction.OP) {
await group.$add("rAdmins", user); await group.$add("rAdmins", user);
} else if (action === MembershipChangeAction.DEOP) { } else if (action === MembershipChangeAction.DEOP) {
await group.$remove("rAdmins", user); await group.$remove("rAdmins", user);
} else { } else {
throw new NoActionSpecifiedError(MembershipChangeAction); throw new NoActionSpecifiedError(MembershipChangeAction);
} }
return group; return group;
} else { } else {
throw new UserNotFoundError(userId); throw new UserNotFoundError(userId);
} }
} else { } else {
throw new GroupNotFoundError(groupId); throw new GroupNotFoundError(groupId);
} }
} }
/** /**
* Enum representing the types of votes that can be performed on a post. * Enum representing the types of votes that can be performed on a post.
*/ */
export enum VoteType { export enum VoteType {
UPVOTE = "UPVOTE", UPVOTE = "UPVOTE",
DOWNVOTE = "DOWNVOTE", DOWNVOTE = "DOWNVOTE",
} }
/** /**
* Enum representing the types of request that can be created. * Enum representing the types of request that can be created.
*/ */
export enum RequestType { export enum RequestType {
FRIENDREQUEST = "FRIENDREQUEST", FRIENDREQUEST = "FRIENDREQUEST",
GROUPINVITE = "GROUPINVITE", GROUPINVITE = "GROUPINVITE",
EVENTINVITE = "EVENTINVITE", EVENTINVITE = "EVENTINVITE",
} }
/** /**
* Enum representing the types of sorting in the feed. * Enum representing the types of sorting in the feed.
*/ */
export enum SortType { export enum SortType {
TOP = "TOP", TOP = "TOP",
NEW = "NEW", NEW = "NEW",
} }
export enum MembershipChangeAction { export enum MembershipChangeAction {
ADD, ADD,
REMOVE, REMOVE,
OP, OP,
DEOP, DEOP,
} }
} }
export default dataaccess; export default dataaccess;

@ -1,13 +1,13 @@
import {GraphQLError} from "graphql"; import {GraphQLError} from "graphql";
/** /**
* Base error class. * Base error class.
*/ */
export class BaseError extends Error { export class BaseError extends Error {
public readonly graphqlError: GraphQLError; public readonly graphqlError: GraphQLError;
constructor(message?: string, friendlyMessage?: string) { constructor(message?: string, friendlyMessage?: string) {
super(message); super(message);
this.graphqlError = new GraphQLError(friendlyMessage || message); this.graphqlError = new GraphQLError(friendlyMessage || message);
} }
} }

@ -1,7 +1,7 @@
import {BaseError} from "./BaseError"; import {BaseError} from "./BaseError";
export class ChatNotFoundError extends BaseError { export class ChatNotFoundError extends BaseError {
constructor(chatId: number) { constructor(chatId: number) {
super(`Chat with id ${chatId} not found.`); super(`Chat with id ${chatId} not found.`);
} }
} }

@ -1,8 +1,8 @@
import {BaseError} from "./BaseError"; import {BaseError} from "./BaseError";
export class EmailAlreadyRegisteredError extends BaseError { export class EmailAlreadyRegisteredError extends BaseError {
constructor(email: string) { constructor(email: string) {
super(`A user for '${email}' does already exist.`); super(`A user for '${email}' does already exist.`);
} }
} }

@ -1,8 +1,8 @@
import {BaseError} from "./BaseError"; import {BaseError} from "./BaseError";
export class GroupNotFoundError extends BaseError { export class GroupNotFoundError extends BaseError {
constructor(groupId: number) { constructor(groupId: number) {
super(`Group ${groupId} not found!`); super(`Group ${groupId} not found!`);
} }
} }

@ -1,7 +1,7 @@
import {BaseError} from "./BaseError"; import {BaseError} from "./BaseError";
export class InvalidLoginError extends BaseError { export class InvalidLoginError extends BaseError {
constructor(email: (string)) { constructor(email: (string)) {
super(`Invalid login data for ${email}.`); super(`Invalid login data for ${email}.`);
} }
} }

@ -1,11 +1,11 @@
import {BaseError} from "./BaseError"; import {BaseError} from "./BaseError";
export class NoActionSpecifiedError extends BaseError { export class NoActionSpecifiedError extends BaseError {
constructor(actions?: any) { constructor(actions?: any) {
if (actions) { if (actions) {
super(`No action of '${Object.keys(actions).join(", ")}'`); super(`No action of '${Object.keys(actions).join(", ")}'`);
} else { } else {
super("No action specified!"); super("No action specified!");
} }
} }
} }

@ -1,9 +1,9 @@
import dataaccess from "../dataaccess"; import dataaccess from "../dataaccess";
import {BaseError} from "./BaseError"; import {BaseError} from "./BaseError";
export class RequestNotFoundError extends BaseError { export class RequestNotFoundError extends BaseError {
constructor(sender: number, receiver: number, type: dataaccess.RequestType) { constructor(sender: number, receiver: number, type: dataaccess.RequestType) {
super(`Request with sender '${sender}' and receiver '${receiver}' of type '${type}' not found.`); super(`Request with sender '${sender}' and receiver '${receiver}' of type '${type}' not found.`);
} }
} }

@ -1,7 +1,7 @@
import {BaseError} from "./BaseError"; import {BaseError} from "./BaseError";
export class UserNotFoundError extends BaseError { export class UserNotFoundError extends BaseError {
constructor(username: (string|number)) { constructor(username: (string|number)) {
super(`User ${username} not found!`); super(`User ${username} not found!`);
} }
} }

@ -1,19 +1,19 @@
import {GraphQLError} from "graphql"; import {GraphQLError} from "graphql";
export class NotLoggedInGqlError extends GraphQLError { export class NotLoggedInGqlError extends GraphQLError {
constructor() { constructor() {
super("Not logged in"); super("Not logged in");
} }
} }
export class PostNotFoundGqlError extends GraphQLError { export class PostNotFoundGqlError extends GraphQLError {
constructor(postId: number) { constructor(postId: number) {
super(`Post '${postId}' not found!`); super(`Post '${postId}' not found!`);
} }
} }
export class GroupNotFoundGqlError extends GraphQLError { export class GroupNotFoundGqlError extends GraphQLError {
constructor(groupId: number) { constructor(groupId: number) {
super(`Group '${groupId}' not found!`); super(`Group '${groupId}' not found!`);
} }
} }

@ -1,63 +1,63 @@
/** /**
* @author Trivernis * @author Trivernis
* @remarks * @remarks
* *
* Partly taken from {@link https://github.com/Trivernis/whooshy} * Partly taken from {@link https://github.com/Trivernis/whooshy}
*/ */
import {EventEmitter} from "events"; import {EventEmitter} from "events";
import * as fsx from "fs-extra"; import * as fsx from "fs-extra";
import * as yaml from "js-yaml"; import * as yaml from "js-yaml";
import * as winston from "winston"; import * as winston from "winston";
require('winston-daily-rotate-file'); require('winston-daily-rotate-file');
const configPath = "config.yaml"; const configPath = "config.yaml";
const defaultConfig = __dirname + "/../default-config.yaml"; const defaultConfig = __dirname + "/../default-config.yaml";
// ensure that the config exists by copying the default config. // ensure that the config exists by copying the default config.
if (!(fsx.pathExistsSync(configPath))) { if (!(fsx.pathExistsSync(configPath))) {
fsx.copySync(defaultConfig, configPath); fsx.copySync(defaultConfig, configPath);
} else { } else {
const conf = yaml.safeLoad(fsx.readFileSync(configPath, "utf-8")); const conf = yaml.safeLoad(fsx.readFileSync(configPath, "utf-8"));
const defConf = yaml.safeLoad(fsx.readFileSync(defaultConfig, "utf-8")); const defConf = yaml.safeLoad(fsx.readFileSync(defaultConfig, "utf-8"));
fsx.writeFileSync(configPath, yaml.safeDump(Object.assign(defConf, conf))); fsx.writeFileSync(configPath, yaml.safeDump(Object.assign(defConf, conf)));
} }
/** /**
* Defines global variables to be used. * Defines global variables to be used.
*/ */
namespace globals { namespace globals {
export const config = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8")); export const config = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8"));
// @ts-ignore // @ts-ignore
export const logger = winston.createLogger({ export const logger = winston.createLogger({
transports: [ transports: [
new winston.transports.Console({ new winston.transports.Console({
format: winston.format.combine( format: winston.format.combine(
winston.format.timestamp(), winston.format.timestamp(),
winston.format.colorize(), winston.format.colorize(),
winston.format.printf(({level, message, timestamp}) => { winston.format.printf(({level, message, timestamp}) => {
return `${timestamp} ${level}: ${message}`; return `${timestamp} ${level}: ${message}`;
}), }),
), ),
level: config.logging.level, level: config.logging.level,
}), }),
// @ts-ignore // @ts-ignore
new (winston.transports.DailyRotateFile)({ new (winston.transports.DailyRotateFile)({
dirname: "logs", dirname: "logs",
filename: "gv-%DATE%.log", filename: "gv-%DATE%.log",
format: winston.format.combine( format: winston.format.combine(
winston.format.timestamp(), winston.format.timestamp(),
winston.format.printf(({level, message, timestamp}) => { winston.format.printf(({level, message, timestamp}) => {
return `${timestamp} ${level}: ${message}`; return `${timestamp} ${level}: ${message}`;
}), }),
), ),
json: false, json: false,
maxFiles: "7d", maxFiles: "7d",
zippedArchive: true, zippedArchive: true,
}), }),
], ],
}); });
export const internalEmitter = new EventEmitter(); export const internalEmitter = new EventEmitter();
} }
export default globals; export default globals;

@ -1,36 +1,36 @@
import * as MarkdownIt from "markdown-it/lib"; import * as MarkdownIt from "markdown-it/lib";
import globals from "./globals"; import globals from "./globals";
namespace markdown { namespace markdown {
const md = new MarkdownIt(); const md = new MarkdownIt();
for (const pluginName of globals.config.markdown.plugins) { for (const pluginName of globals.config.markdown.plugins) {
try { try {
const plugin = require(pluginName); const plugin = require(pluginName);
if (plugin) { if (plugin) {
md.use(plugin); md.use(plugin);
} }
} catch (err) { } catch (err) {
globals.logger.warn(`Markdown-it plugin '${pluginName}' not found!`); globals.logger.warn(`Markdown-it plugin '${pluginName}' not found!`);
} }
} }
/** /**
* Renders the markdown string inline (without blocks). * Renders the markdown string inline (without blocks).
* @param markdownString * @param markdownString
*/ */
export function renderInline(markdownString: string) { export function renderInline(markdownString: string) {
return md.renderInline(markdownString); return md.renderInline(markdownString);
} }
/** /**
* Renders the markdown string. * Renders the markdown string.
* @param markdownString * @param markdownString
*/ */
export function render(markdownString: string) { export function render(markdownString: string) {
return md.render(markdownString); return md.render(markdownString);
} }
} }
export default markdown; export default markdown;

@ -1,16 +1,16 @@
import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript";
import {ChatRoom} from "./ChatRoom"; import {ChatRoom} from "./ChatRoom";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class ChatMember extends Model<ChatMember> { export class ChatMember extends Model<ChatMember> {
@ForeignKey(() => User) @ForeignKey(() => User)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public userId: number; public userId: number;
@ForeignKey(() => ChatRoom) @ForeignKey(() => ChatRoom)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public chatId: number; public chatId: number;
} }

@ -1,44 +1,44 @@
import * as sqz from "sequelize"; import * as sqz from "sequelize";
import {BelongsTo, Column, CreatedAt, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; import {BelongsTo, Column, CreatedAt, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript";
import markdown from "../markdown"; import markdown from "../markdown";
import {ChatRoom} from "./ChatRoom"; import {ChatRoom} from "./ChatRoom";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class ChatMessage extends Model<ChatMessage> { export class ChatMessage extends Model<ChatMessage> {
@NotNull @NotNull
@Column({type: sqz.STRING(512), allowNull: false}) @Column({type: sqz.STRING(512), allowNull: false})
public content: string; public content: string;
@ForeignKey(() => ChatRoom) @ForeignKey(() => ChatRoom)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public chatId: number; public chatId: number;
@ForeignKey(() => User) @ForeignKey(() => User)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public authorId: number; public authorId: number;
@BelongsTo(() => ChatRoom, "chatId") @BelongsTo(() => ChatRoom, "chatId")
public rChat: ChatRoom; public rChat: ChatRoom;
@BelongsTo(() => User, "authorId") @BelongsTo(() => User, "authorId")
public rAuthor: User; public rAuthor: User;
@CreatedAt @CreatedAt
public createdAt: Date; public createdAt: Date;
public async chat(): Promise<ChatRoom> { public async chat(): Promise<ChatRoom> {
return await this.$get("rChat") as ChatRoom; return await this.$get("rChat") as ChatRoom;
} }
public async author(): Promise<User> { public async author(): Promise<User> {
return await this.$get("rAuthor") as User; return await this.$get("rAuthor") as User;
} }
public get htmlContent(): string { public get htmlContent(): string {
return markdown.renderInline(this.getDataValue("content")); return markdown.renderInline(this.getDataValue("content"));
} }
} }

@ -1,28 +1,28 @@
import {BelongsToMany, CreatedAt, HasMany, Model, Table,} from "sequelize-typescript"; import {BelongsToMany, CreatedAt, HasMany, Model, Table,} from "sequelize-typescript";
import {ChatMember} from "./ChatMember"; import {ChatMember} from "./ChatMember";
import {ChatMessage} from "./ChatMessage"; import {ChatMessage} from "./ChatMessage";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class ChatRoom extends Model<ChatRoom> { export class ChatRoom extends Model<ChatRoom> {
@BelongsToMany(() => User, () => ChatMember) @BelongsToMany(() => User, () => ChatMember)
public rMembers: User[]; public rMembers: User[];
@HasMany(() => ChatMessage, "chatId") @HasMany(() => ChatMessage, "chatId")
public rMessages: ChatMessage[]; public rMessages: ChatMessage[];
@CreatedAt @CreatedAt
public readonly createdAt!: Date; public readonly createdAt!: Date;
public async members(): Promise<User[]> { public async members(): Promise<User[]> {
return await this.$get("rMembers") as User[]; return await this.$get("rMembers") as User[];
} }
public async messages(): Promise<ChatMessage[]> { public async messages(): Promise<ChatMessage[]> {
return await this.$get("rMessages") as ChatMessage[]; return await this.$get("rMessages") as ChatMessage[];
} }
public get namespace(): string { public get namespace(): string {
return "/chats/" + this.getDataValue("id"); return "/chats/" + this.getDataValue("id");
} }
} }

@ -1,36 +1,36 @@
import {BelongsTo, BelongsToMany, Column, ForeignKey, Model, NotNull, Table} from "sequelize-typescript"; import {BelongsTo, BelongsToMany, Column, ForeignKey, Model, NotNull, Table} from "sequelize-typescript";
import {EventParticipant} from "./EventParticipant"; import {EventParticipant} from "./EventParticipant";
import {Group} from "./Group"; import {Group} from "./Group";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class Event extends Model<Event> { export class Event extends Model<Event> {
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public name: string; public name: string;
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public dueDate: Date; public dueDate: Date;
@NotNull @NotNull
@ForeignKey(() => Group) @ForeignKey(() => Group)
@Column({allowNull: false}) @Column({allowNull: false})
public groupId: number; public groupId: number;
@BelongsTo(() => Group, "groupId") @BelongsTo(() => Group, "groupId")
public rGroup: Group; public rGroup: Group;
@BelongsToMany(() => User, () => EventParticipant) @BelongsToMany(() => User, () => EventParticipant)
public rParticipants: User[]; public rParticipants: User[];
public async group(): Promise<Group> { public async group(): Promise<Group> {
return await this.$get("rGroup") as Group; return await this.$get("rGroup") as Group;
} }
public async participants({first, offset}: {first: number, offset: number}): Promise<User[]> { public async participants({first, offset}: {first: number, offset: number}): Promise<User[]> {
const limit = first || 10; const limit = first || 10;
offset = offset || 0; offset = offset || 0;
return await this.$get("rParticipants", {limit, offset}) as User[]; return await this.$get("rParticipants", {limit, offset}) as User[];
} }
} }

@ -1,16 +1,16 @@
import {BelongsTo, BelongsToMany, Column, ForeignKey, Model, NotNull, Table} from "sequelize-typescript"; import {BelongsTo, BelongsToMany, Column, ForeignKey, Model, NotNull, Table} from "sequelize-typescript";
import {Event} from "./Event"; import {Event} from "./Event";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class EventParticipant extends Model<EventParticipant> { export class EventParticipant extends Model<EventParticipant> {
@NotNull @NotNull
@ForeignKey(() => User) @ForeignKey(() => User)
@Column({allowNull: false}) @Column({allowNull: false})
public userId: number; public userId: number;
@NotNull @NotNull
@ForeignKey(() => Event) @ForeignKey(() => Event)
@Column({allowNull: false}) @Column({allowNull: false})
public eventId: number; public eventId: number;
} }

@ -1,18 +1,18 @@
import {Column, ForeignKey, Model, NotNull, PrimaryKey, Table} from "sequelize-typescript"; import {Column, ForeignKey, Model, NotNull, PrimaryKey, Table} from "sequelize-typescript";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class Friendship extends Model<Friendship> { export class Friendship extends Model<Friendship> {
@ForeignKey(() => User) @ForeignKey(() => User)
@PrimaryKey @PrimaryKey
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public userId: number; public userId: number;
@ForeignKey(() => User) @ForeignKey(() => User)
@PrimaryKey @PrimaryKey
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public friendId: number; public friendId: number;
} }

@ -1,64 +1,64 @@
import {BelongsTo, BelongsToMany, Column, ForeignKey, HasMany, Model, NotNull, Table} from "sequelize-typescript"; import {BelongsTo, BelongsToMany, Column, ForeignKey, HasMany, Model, NotNull, Table} from "sequelize-typescript";
import {ChatRoom} from "./ChatRoom"; import {ChatRoom} from "./ChatRoom";
import {Event} from "./Event"; import {Event} from "./Event";
import {GroupAdmin} from "./GroupAdmin"; import {GroupAdmin} from "./GroupAdmin";
import {GroupMember} from "./GroupMember"; import {GroupMember} from "./GroupMember";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class Group extends Model<Group> { export class Group extends Model<Group> {
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public name: string; public name: string;
@NotNull @NotNull
@ForeignKey(() => User) @ForeignKey(() => User)
@Column({allowNull: false}) @Column({allowNull: false})
public creatorId: number; public creatorId: number;
@NotNull @NotNull
@ForeignKey(() => ChatRoom) @ForeignKey(() => ChatRoom)
@Column({allowNull: false}) @Column({allowNull: false})
public chatId: number; public chatId: number;
@BelongsTo(() => User, "creatorId") @BelongsTo(() => User, "creatorId")
public rCreator: User; public rCreator: User;
@BelongsToMany(() => User, () => GroupAdmin) @BelongsToMany(() => User, () => GroupAdmin)
public rAdmins: User[]; public rAdmins: User[];
@BelongsToMany(() => User, () => GroupMember) @BelongsToMany(() => User, () => GroupMember)
public rMembers: User[]; public rMembers: User[];
@BelongsTo(() => ChatRoom) @BelongsTo(() => ChatRoom)
public rChat: ChatRoom; public rChat: ChatRoom;
@HasMany(() => Event, "groupId") @HasMany(() => Event, "groupId")
public rEvents: Event[]; public rEvents: Event[];
public async creator(): Promise<User> { public async creator(): Promise<User> {
return await this.$get("rCreator") as User; return await this.$get("rCreator") as User;
} }
public async admins({first, offset}: { first: number, offset: number }): Promise<User[]> { public async admins({first, offset}: { first: number, offset: number }): Promise<User[]> {
const limit = first || 10; const limit = first || 10;
offset = offset || 0; offset = offset || 0;
return await this.$get("rAdmins", {limit, offset}) as User[]; return await this.$get("rAdmins", {limit, offset}) as User[];
} }
public async members({first, offset}: { first: number, offset: number }): Promise<User[]> { public async members({first, offset}: { first: number, offset: number }): Promise<User[]> {
const limit = first || 10; const limit = first || 10;
offset = offset || 0; offset = offset || 0;
return await this.$get("rMembers", {limit, offset}) as User[]; return await this.$get("rMembers", {limit, offset}) as User[];
} }
public async chat(): Promise<ChatRoom> { public async chat(): Promise<ChatRoom> {
return await this.$get("rChat") as ChatRoom; return await this.$get("rChat") as ChatRoom;
} }
public async events({first, offset}: { first: number, offset: number }): Promise<Event[]> { public async events({first, offset}: { first: number, offset: number }): Promise<Event[]> {
const limit = first || 10; const limit = first || 10;
offset = offset || 0; offset = offset || 0;
return await this.$get("rEvents", {limit, offset}) as Event[]; return await this.$get("rEvents", {limit, offset}) as Event[];
} }
} }

@ -1,16 +1,16 @@
import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript";
import {Group} from "./Group"; import {Group} from "./Group";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class GroupAdmin extends Model<GroupAdmin> { export class GroupAdmin extends Model<GroupAdmin> {
@NotNull @NotNull
@ForeignKey(() => User) @ForeignKey(() => User)
@Column({allowNull: false}) @Column({allowNull: false})
public userId: number; public userId: number;
@NotNull @NotNull
@ForeignKey(() => Group) @ForeignKey(() => Group)
@Column({allowNull: false}) @Column({allowNull: false})
public groupId: number; public groupId: number;
} }

@ -1,16 +1,16 @@
import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript";
import {Group} from "./Group"; import {Group} from "./Group";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class GroupMember extends Model<GroupMember> { export class GroupMember extends Model<GroupMember> {
@NotNull @NotNull
@ForeignKey(() => User) @ForeignKey(() => User)
@Column({allowNull: false}) @Column({allowNull: false})
public userId: number; public userId: number;
@NotNull @NotNull
@ForeignKey(() => Group) @ForeignKey(() => Group)
@Column({allowNull: false}) @Column({allowNull: false})
public groupId: number; public groupId: number;
} }

@ -1,70 +1,70 @@
import * as sqz from "sequelize"; import * as sqz from "sequelize";
import {BelongsTo, BelongsToMany, Column, CreatedAt, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; import {BelongsTo, BelongsToMany, Column, CreatedAt, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript";
import markdown from "../markdown"; import markdown from "../markdown";
import {PostVote, VoteType} from "./PostVote"; import {PostVote, VoteType} from "./PostVote";
import {User} from "./User"; import {User} from "./User";
@Table({underscored: true}) @Table({underscored: true})
export class Post extends Model<Post> { export class Post extends Model<Post> {
@NotNull @NotNull
@Column({type: sqz.STRING(2048), allowNull: false}) @Column({type: sqz.STRING(2048), allowNull: false})
public content: string; public content: string;
@ForeignKey(() => User) @ForeignKey(() => User)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public authorId: number; public authorId: number;
@BelongsTo(() => User, "authorId") @BelongsTo(() => User, "authorId")
public rAuthor: User; public rAuthor: User;
@BelongsToMany(() => User, () => PostVote) @BelongsToMany(() => User, () => PostVote)
public rVotes: Array<User & {PostVote: PostVote}>; public rVotes: Array<User & {PostVote: PostVote}>;
@CreatedAt @CreatedAt
public readonly createdAt!: Date; public readonly createdAt!: Date;
public async author(): Promise<User> { public async author(): Promise<User> {
return await this.$get("rAuthor") as User; return await this.$get("rAuthor") as User;
} }
public async votes(): Promise<Array<User & {PostVote: PostVote}>> { public async votes(): Promise<Array<User & {PostVote: PostVote}>> {
return await this.$get("rVotes") as Array<User & {PostVote: PostVote}>; return await this.$get("rVotes") as Array<User & {PostVote: PostVote}>;
} }
public get htmlContent() { public get htmlContent() {
return markdown.render(this.getDataValue("content")); return markdown.render(this.getDataValue("content"));
} }
public async upvotes() { public async upvotes() {
return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.UPVOTE).length; return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.UPVOTE).length;
} }
public async downvotes() { public async downvotes() {
return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.DOWNVOTE).length; return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.DOWNVOTE).length;
} }
public async vote(userId: number, type: VoteType): Promise<VoteType> { public async vote(userId: number, type: VoteType): Promise<VoteType> {
type = type || VoteType.UPVOTE; type = type || VoteType.UPVOTE;
let votes = await this.$get("rVotes", {where: {id: userId}}) as Array<User & {PostVote: PostVote}>; let votes = await this.$get("rVotes", {where: {id: userId}}) as Array<User & {PostVote: PostVote}>;
let vote = votes[0] || null; let vote = votes[0] || null;
let created = false; let created = false;
if (!vote) { if (!vote) {
await this.$add("rVote", userId); await this.$add("rVote", userId);
votes = await this.$get("rVotes", {where: {id: userId}}) as Array<User & {PostVote: PostVote}>; votes = await this.$get("rVotes", {where: {id: userId}}) as Array<User & {PostVote: PostVote}>;
vote = votes[0] || null; vote = votes[0] || null;
created = true; created = true;
} }
if (vote) { if (vote) {
if (vote.PostVote.voteType === type && !created) { if (vote.PostVote.voteType === type && !created) {
await vote.PostVote.destroy(); await vote.PostVote.destroy();
return null; return null;
} else { } else {
vote.PostVote.voteType = type; vote.PostVote.voteType = type;
await vote.PostVote.save(); await vote.PostVote.save();
} }
} }
return vote.PostVote.voteType; return vote.PostVote.voteType;
} }
} }

@ -1,26 +1,26 @@
import * as sqz from "sequelize"; import * as sqz from "sequelize";
import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; import {Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript";
import {Post} from "./Post"; import {Post} from "./Post";
import {User} from "./User"; import {User} from "./User";
export enum VoteType { export enum VoteType {
UPVOTE = "UPVOTE", UPVOTE = "UPVOTE",
DOWNVOTE = "DOWNVOTE", DOWNVOTE = "DOWNVOTE",
} }
@Table({underscored: true}) @Table({underscored: true})
export class PostVote extends Model<PostVote> { export class PostVote extends Model<PostVote> {
@NotNull @NotNull
@Column({type: sqz.ENUM, values: ["UPVOTE", "DOWNVOTE"], defaultValue: "UPVOTE", allowNull: false}) @Column({type: sqz.ENUM, values: ["UPVOTE", "DOWNVOTE"], defaultValue: "UPVOTE", allowNull: false})
public voteType: VoteType; public voteType: VoteType;
@ForeignKey(() => User) @ForeignKey(() => User)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public userId: number; public userId: number;
@ForeignKey(() => Post) @ForeignKey(() => Post)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public postId: number; public postId: number;
} }

@ -1,41 +1,41 @@
import * as sqz from "sequelize"; import * as sqz from "sequelize";
import {BelongsTo, Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript"; import {BelongsTo, Column, ForeignKey, Model, NotNull, Table,} from "sequelize-typescript";
import {User} from "./User"; import {User} from "./User";
export enum RequestType { export enum RequestType {
FRIENDREQUEST = "FRIENDREQUEST", FRIENDREQUEST = "FRIENDREQUEST",
GROUPINVITE = "GROUPINVITE", GROUPINVITE = "GROUPINVITE",
EVENTINVITE = "EVENTINVITE", EVENTINVITE = "EVENTINVITE",
} }
@Table({underscored: true}) @Table({underscored: true})
export class Request extends Model<Request> { export class Request extends Model<Request> {
@NotNull @NotNull
@Column({type: sqz.ENUM, values: ["FRIENDREQUEST", "GROUPINVITE", "EVENTINVITE"], @Column({type: sqz.ENUM, values: ["FRIENDREQUEST", "GROUPINVITE", "EVENTINVITE"],
defaultValue: "FRIENDREQUEST", allowNull: false}) defaultValue: "FRIENDREQUEST", allowNull: false})
public requestType: RequestType; public requestType: RequestType;
@ForeignKey(() => User) @ForeignKey(() => User)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public senderId: number; public senderId: number;
@BelongsTo(() => User, "senderId") @BelongsTo(() => User, "senderId")
public rSender: User; public rSender: User;
@ForeignKey(() => User) @ForeignKey(() => User)
@NotNull @NotNull
@Column({allowNull: false}) @Column({allowNull: false})
public receiverId: number; public receiverId: number;
@BelongsTo(() => User, "receiverId") @BelongsTo(() => User, "receiverId")
public rReceiver: User; public rReceiver: User;
public async receiver(): Promise<User> { public async receiver(): Promise<User> {
return await this.$get("rReceiver") as User; return await this.$get("rReceiver") as User;
} }
public async sender(): Promise<User> { public async sender(): Promise<User> {
return await this.$get("rSender") as User; return await this.$get("rSender") as User;
} }
} }

@ -1,289 +1,289 @@
import * as sqz from "sequelize"; import * as sqz from "sequelize";
import { import {
BelongsTo, BelongsTo,
BelongsToMany, BelongsToMany,
Column, Column,
CreatedAt, CreatedAt,
HasMany, HasMany,
Model, Model,
NotNull, NotNull,
Table, Table,
Unique, Unique,
UpdatedAt, UpdatedAt,
} from "sequelize-typescript"; } from "sequelize-typescript";
import {RequestNotFoundError} from "../errors/RequestNotFoundError"; import {RequestNotFoundError} from "../errors/RequestNotFoundError";
import {UserNotFoundError} from "../errors/UserNotFoundError"; import {UserNotFoundError} from "../errors/UserNotFoundError";
import {ChatMember} from "./ChatMember"; import {ChatMember} from "./ChatMember";
import {ChatMessage} from "./ChatMessage"; import {ChatMessage} from "./ChatMessage";
import {ChatRoom} from "./ChatRoom"; import {ChatRoom} from "./ChatRoom";
import {Event} from "./Event"; import {Event} from "./Event";
import {EventParticipant} from "./EventParticipant"; import {EventParticipant} from "./EventParticipant";
import {Friendship} from "./Friendship"; import {Friendship} from "./Friendship";
import {Group} from "./Group"; import {Group} from "./Group";
import {GroupAdmin} from "./GroupAdmin"; import {GroupAdmin} from "./GroupAdmin";
import {GroupMember} from "./GroupMember"; import {GroupMember} from "./GroupMember";
import {Post} from "./Post"; import {Post} from "./Post";
import {PostVote} from "./PostVote"; import {PostVote} from "./PostVote";
import {Request, RequestType} from "./Request"; import {Request, RequestType} from "./Request";
@Table({underscored: true}) @Table({underscored: true})
export class User extends Model<User> { export class User extends Model<User> {
@NotNull @NotNull
@Column({type: sqz.STRING(128), allowNull: false}) @Column({type: sqz.STRING(128), allowNull: false})
public username: string; public username: string;
@NotNull @NotNull
@Unique @Unique
@Column({type: sqz.STRING(128), allowNull: false, unique: true}) @Column({type: sqz.STRING(128), allowNull: false, unique: true})
public handle: string; public handle: string;
@Unique @Unique
@NotNull @NotNull
@Column({type: sqz.STRING(128), allowNull: false, unique: true}) @Column({type: sqz.STRING(128), allowNull: false, unique: true})
public email: string; public email: string;
@NotNull @NotNull
@Column({type: sqz.STRING(128), allowNull: false}) @Column({type: sqz.STRING(128), allowNull: false})
public password: string; public password: string;
@NotNull @NotNull
@Column({defaultValue: 0, allowNull: false}) @Column({defaultValue: 0, allowNull: false})
public rankpoints: number; public rankpoints: number;
@BelongsToMany(() => User, () => Friendship, "userId") @BelongsToMany(() => User, () => Friendship, "userId")
public rFriends: User[]; public rFriends: User[];
@BelongsToMany(() => User, () => Friendship, "friendId") @BelongsToMany(() => User, () => Friendship, "friendId")
public rFriendOf: User[]; public rFriendOf: User[];
@BelongsToMany(() => Post, () => PostVote) @BelongsToMany(() => Post, () => PostVote)
public votes: Array<Post & {PostVote: PostVote}>; public votes: Array<Post & {PostVote: PostVote}>;
@BelongsToMany(() => ChatRoom, () => ChatMember) @BelongsToMany(() => ChatRoom, () => ChatMember)
public rChats: ChatRoom[]; public rChats: ChatRoom[];
@BelongsToMany(() => Group, () => GroupAdmin) @BelongsToMany(() => Group, () => GroupAdmin)
public rAdministratedGroups: Group[]; public rAdministratedGroups: Group[];
@BelongsToMany(() => Event, () => EventParticipant) @BelongsToMany(() => Event, () => EventParticipant)
public rEvents: Event[]; public rEvents: Event[];
@BelongsToMany(() => Group, () => GroupMember) @BelongsToMany(() => Group, () => GroupMember)
public rGroups: Group[]; public rGroups: Group[];
@HasMany(() => Post, "authorId") @HasMany(() => Post, "authorId")
public rPosts: Post[]; public rPosts: Post[];
@HasMany(() => Request, "senderId") @HasMany(() => Request, "senderId")
public rSentRequests: Request[]; public rSentRequests: Request[];
@HasMany(() => Request, "receiverId") @HasMany(() => Request, "receiverId")
public rReceivedRequests: Request[]; public rReceivedRequests: Request[];
@HasMany(() => ChatMessage, "authorId") @HasMany(() => ChatMessage, "authorId")
public messages: ChatMessage[]; public messages: ChatMessage[];
@HasMany(() => Group, "creatorId") @HasMany(() => Group, "creatorId")
public rCreatedGroups: Group[]; public rCreatedGroups: Group[];
@CreatedAt @CreatedAt
public readonly createdAt!: Date; public readonly createdAt!: Date;
@UpdatedAt @UpdatedAt
public readonly updatedAt!: Date; public readonly updatedAt!: Date;
/** /**
* The name of the user * The name of the user
*/ */
public get name(): string { public get name(): string {
return this.getDataValue("username"); return this.getDataValue("username");
} }
/** /**
* The date the user joined the network * The date the user joined the network
*/ */
public get joinedAt(): Date { public get joinedAt(): Date {
return this.getDataValue("createdAt"); return this.getDataValue("createdAt");
} }
/** /**
* The points of the user * The points of the user
*/ */
public get points(): number { public get points(): number {
return this.rankpoints; return this.rankpoints;
} }
/** /**
* The level of the user which is the points divided by 100 * The level of the user which is the points divided by 100
*/ */
public get level(): number { public get level(): number {
return Math.ceil(this.rankpoints / 100); return Math.ceil(this.rankpoints / 100);
} }
/** /**
* All friends of the user * All friends of the user
* @param first * @param first
* @param offset * @param offset
*/ */
public async friends({first, offset}: {first: number, offset: number}): Promise<User[]> { public async friends({first, offset}: {first: number, offset: number}): Promise<User[]> {
const limit = first || 10; const limit = first || 10;
offset = offset || 0; offset = offset || 0;
return await this.$get("rFriendOf", {limit, offset}) as User[]; return await this.$get("rFriendOf", {limit, offset}) as User[];
} }
/** /**
* The total number of the users friends. * The total number of the users friends.
*/ */
public async friendCount(): Promise<number> { public async friendCount(): Promise<number> {
return this.$count("rFriends"); return this.$count("rFriends");
} }
/** /**
* The chats the user has joined * The chats the user has joined
* @param first * @param first
* @param offset * @param offset
*/ */
public async chats({first, offset}: {first: number, offset: number}): Promise<ChatRoom[]> { public async chats({first, offset}: {first: number, offset: number}): Promise<ChatRoom[]> {
const limit = first || 10; const limit = first || 10;
offset = offset || 0; offset = offset || 0;
return await this.$get("rChats", {limit, offset}) as ChatRoom[]; return await this.$get("rChats", {limit, offset}) as ChatRoom[];
} }
/** /**
* the number of chats the user has * the number of chats the user has
*/ */
public async chatCount(): Promise<number> { public async chatCount(): Promise<number> {
return this.$count("rChats"); return this.$count("rChats");
} }
/** /**
* All active requests the user ha ssent * All active requests the user ha ssent
*/ */
public async sentRequests(): Promise<Request[]> { public async sentRequests(): Promise<Request[]> {
return await this.$get("rSentRequests") as Request[]; return await this.$get("rSentRequests") as Request[];
} }
/** /**
* All requests the user has received * All requests the user has received
*/ */
public async receivedRequests(): Promise<Request[]> { public async receivedRequests(): Promise<Request[]> {
return await this.$get("rReceivedRequests") as Request[]; return await this.$get("rReceivedRequests") as Request[];
} }
public async posts({first, offset}: {first: number, offset: number}): Promise<Post[]> { public async posts({first, offset}: {first: number, offset: number}): Promise<Post[]> {
const limit = first || 10; const limit = first || 10;
offset = offset || 0; offset = offset || 0;
return await this.$get("rPosts", {limit, offset}) as Post[]; return await this.$get("rPosts", {limit, offset}) as Post[];
} }
/** /**
* @deprecated * @deprecated
* use {@link postCount} instead * use {@link postCount} instead
*/ */
public async numberOfPosts(): Promise<number> { public async numberOfPosts(): Promise<number> {
return this.postCount(); return this.postCount();
} }
/** /**
* number of posts the user created * number of posts the user created
*/ */
public async postCount(): Promise<number> { public async postCount(): Promise<number> {
return this.$count("rPosts"); return this.$count("rPosts");
} }
/** /**
* Groups the user is the admin of * Groups the user is the admin of
*/ */
public async administratedGroups(): Promise<Group[]> { public async administratedGroups(): Promise<Group[]> {
return await this.$get("rAdministratedGroups") as Group[]; return await this.$get("rAdministratedGroups") as Group[];
} }
/** /**
* Groups the user has created * Groups the user has created
*/ */
public async createdGroups(): Promise<Group[]> { public async createdGroups(): Promise<Group[]> {
return await this.$get("rCreatedGroups") as Group[]; return await this.$get("rCreatedGroups") as Group[];
} }
/** /**
* Groups the user is a member of * Groups the user is a member of
* @param first * @param first
* @param offset * @param offset
*/ */
public async groups({first, offset}: {first: number, offset: number}): Promise<Group[]> { public async groups({first, offset}: {first: number, offset: number}): Promise<Group[]> {
const limit = first || 10; const limit = first || 10;
offset = offset || 0; offset = offset || 0;
return await this.$get("rGroups", {limit, offset}) as Group[]; return await this.$get("rGroups", {limit, offset}) as Group[];
} }
/** /**
* The number of groups the user has joined * The number of groups the user has joined
*/ */
public async groupCount(): Promise<number> { public async groupCount(): Promise<number> {
return this.$count("rGroups"); return this.$count("rGroups");
} }
/** /**
* Events the user has joined * Events the user has joined
*/ */
public async events(): Promise<Event[]> { public async events(): Promise<Event[]> {
return await this.$get("rEvents") as Event[]; return await this.$get("rEvents") as Event[];
} }
/** /**
* The number of events the user is participating in. * The number of events the user is participating in.
*/ */
public async eventCount(): Promise<number> { public async eventCount(): Promise<number> {
return this.$count("rEvents"); return this.$count("rEvents");
} }
/** /**
* Denys a request the user has received * Denys a request the user has received
* @param sender * @param sender
* @param type * @param type
*/ */
public async denyRequest(sender: number, type: RequestType) { public async denyRequest(sender: number, type: RequestType) {
const request = await this.$get("rReceivedRequests", const request = await this.$get("rReceivedRequests",
{where: {senderId: sender, requestType: type}}) as Request[]; {where: {senderId: sender, requestType: type}}) as Request[];
if (request[0]) { if (request[0]) {
await request[0].destroy(); await request[0].destroy();
} }
} }
/** /**
* Accepts a request the user has received * Accepts a request the user has received
* @param sender * @param sender
* @param type * @param type
*/ */
public async acceptRequest(sender: number, type: RequestType) { public async acceptRequest(sender: number, type: RequestType) {
const requests = await this.$get("rReceivedRequests", const requests = await this.$get("rReceivedRequests",
{where: {senderId: sender, requestType: type}}) as Request[]; {where: {senderId: sender, requestType: type}}) as Request[];
if (requests.length > 0) { if (requests.length > 0) {
const request = requests[0]; const request = requests[0];
if (request.requestType === RequestType.FRIENDREQUEST) { if (request.requestType === RequestType.FRIENDREQUEST) {
await Friendship.bulkCreate([ await Friendship.bulkCreate([
{userId: this.id, friendId: sender}, {userId: this.id, friendId: sender},
{userId: sender, friendId: this.id}, {userId: sender, friendId: this.id},
], {ignoreDuplicates: true}); ], {ignoreDuplicates: true});
await request.destroy(); await request.destroy();
} }
} else { } else {
throw new RequestNotFoundError(sender, this.id, type); throw new RequestNotFoundError(sender, this.id, type);
} }
} }
/** /**
* Removes a user from the users friends * Removes a user from the users friends
* @param friendId * @param friendId
*/ */
public async removeFriend(friendId: number) { public async removeFriend(friendId: number) {
const friend = await User.findByPk(friendId); const friend = await User.findByPk(friendId);
if (friend) { if (friend) {
await this.$remove("rFriends", friend); await this.$remove("rFriends", friend);
await this.$remove("rFriendOf", friend); await this.$remove("rFriendOf", friend);
return true; return true;
} else { } else {
throw new UserNotFoundError(friendId); throw new UserNotFoundError(friendId);
} }
} }
} }

@ -1,13 +1,13 @@
export {ChatMember} from "./ChatMember"; export {ChatMember} from "./ChatMember";
export {ChatMessage} from "./ChatMessage"; export {ChatMessage} from "./ChatMessage";
export {ChatRoom} from "./ChatRoom"; export {ChatRoom} from "./ChatRoom";
export {Friendship} from "./Friendship"; export {Friendship} from "./Friendship";
export {Post} from "./Post"; export {Post} from "./Post";
export {PostVote} from "./PostVote"; export {PostVote} from "./PostVote";
export {Request} from "./Request"; export {Request} from "./Request";
export {User} from "./User"; export {User} from "./User";
export {Group} from "./Group"; export {Group} from "./Group";
export {GroupAdmin} from "./GroupAdmin"; export {GroupAdmin} from "./GroupAdmin";
export {GroupMember} from "./GroupMember"; export {GroupMember} from "./GroupMember";
export {Event} from "./Event"; export {Event} from "./Event";
export {EventParticipant} from "./EventParticipant"; export {EventParticipant} from "./EventParticipant";

@ -1,11 +1,11 @@
export namespace is { export namespace is {
const emailRegex = /\S+?@\S+?(\.\S+?)?\.\w{2,3}(.\w{2-3})?/g; const emailRegex = /\S+?@\S+?(\.\S+?)?\.\w{2,3}(.\w{2-3})?/g;
/** /**
* Tests if a string is a valid email. * Tests if a string is a valid email.
* @param testString * @param testString
*/ */
export function email(testString: string) { export function email(testString: string) {
return emailRegex.test(testString); return emailRegex.test(testString);
} }
} }

@ -1,10 +1,10 @@
body body
font-family: Arial, serif font-family: Arial, serif
#server-error #server-error
* *
margin-left: auto margin-left: auto
margin-right: auto margin-right: auto
text-align: center text-align: center
code code
font-size: 2em font-size: 2em

@ -1,98 +1,98 @@
import {Router} from "express"; import {Router} from "express";
import {Namespace, Server} from "socket.io"; import {Namespace, Server} from "socket.io";
import dataaccess from "../lib/dataaccess"; import dataaccess from "../lib/dataaccess";
import globals from "../lib/globals"; import globals from "../lib/globals";
import {InternalEvents} from "../lib/InternalEvents"; import {InternalEvents} from "../lib/InternalEvents";
import {ChatMessage, ChatRoom, Post, Request, User} from "../lib/models"; import {ChatMessage, ChatRoom, Post, Request, User} from "../lib/models";
import Route from "../lib/Route"; import Route from "../lib/Route";
/** /**
* list of chatroom socket namespaces. * list of chatroom socket namespaces.
*/ */
const chatRooms: Namespace[] = []; const chatRooms: Namespace[] = [];
/** /**
* Class for the home route. * Class for the home route.
*/ */
class HomeRoute extends Route { class HomeRoute extends Route {
/** /**
* Constructor, creates new router. * Constructor, creates new router.
*/ */
constructor() { constructor() {
super(); super();
this.router = Router(); this.router = Router();
} }
/** /**
* Asynchronous init for socket.io. * Asynchronous init for socket.io.
* @param io - the io instance * @param io - the io instance
*/ */
public async init(io: Server) { public async init(io: Server) {
this.io = io; this.io = io;
io.on("connection", (socket) => { io.on("connection", (socket) => {
socket.on("postCreate", async (content) => { socket.on("postCreate", async (content) => {
if (socket.handshake.session.userId) { if (socket.handshake.session.userId) {
const post = await dataaccess.createPost(content, socket.handshake.session.userId); const post = await dataaccess.createPost(content, socket.handshake.session.userId);
io.emit("post", Object.assign(post, {htmlContent: post.htmlContent})); io.emit("post", Object.assign(post, {htmlContent: post.htmlContent}));
} else { } else {
socket.emit("error", "Not logged in!"); socket.emit("error", "Not logged in!");
} }
}); });
globals.internalEmitter.on(InternalEvents.REQUESTCREATE, async (request: Request) => { globals.internalEmitter.on(InternalEvents.REQUESTCREATE, async (request: Request) => {
if ((await request.$get("sender") as User).id === socket.handshake.session.userId) { if ((await request.$get("sender") as User).id === socket.handshake.session.userId) {
socket.emit("request", request); socket.emit("request", request);
} }
}); });
globals.internalEmitter.on(InternalEvents.GQLPOSTCREATE, async (post: Post) => { globals.internalEmitter.on(InternalEvents.GQLPOSTCREATE, async (post: Post) => {
socket.emit("post", Object.assign(post, {htmlContent: post.htmlContent})); socket.emit("post", Object.assign(post, {htmlContent: post.htmlContent}));
}); });
}); });
const chats = await dataaccess.getAllChats(); const chats = await dataaccess.getAllChats();
for (const chat of chats) { for (const chat of chats) {
chatRooms[chat.id] = this.getChatSocketNamespace(chat.id); chatRooms[chat.id] = this.getChatSocketNamespace(chat.id);
} }
globals.internalEmitter.on(InternalEvents.CHATCREATE, (chat: ChatRoom) => { globals.internalEmitter.on(InternalEvents.CHATCREATE, (chat: ChatRoom) => {
chatRooms[chat.id] = this.getChatSocketNamespace(chat.id); chatRooms[chat.id] = this.getChatSocketNamespace(chat.id);
}); });
} }
/** /**
* Destroys the instance by dereferencing the router and resolver. * Destroys the instance by dereferencing the router and resolver.
*/ */
public async destroy(): Promise<void> { public async destroy(): Promise<void> {
this.router = null; this.router = null;
} }
/** /**
* Returns the namespace socket for a chat socket. * Returns the namespace socket for a chat socket.
* @param chatId * @param chatId
*/ */
private getChatSocketNamespace(chatId: number) { private getChatSocketNamespace(chatId: number) {
if (chatRooms[chatId]) { if (chatRooms[chatId]) {
return chatRooms[chatId]; return chatRooms[chatId];
} }
const chatNs = this.io.of(`/chat/${chatId}`); const chatNs = this.io.of(`/chat/${chatId}`);
chatNs.on("connection", (socket) => { chatNs.on("connection", (socket) => {
socket.on("chatMessage", async (content) => { socket.on("chatMessage", async (content) => {
if (socket.handshake.session.userId) { if (socket.handshake.session.userId) {
const userId = socket.handshake.session.userId; const userId = socket.handshake.session.userId;
const message = await dataaccess.sendChatMessage(userId, chatId, content); const message = await dataaccess.sendChatMessage(userId, chatId, content);
socket.broadcast.emit("chatMessage", Object.assign(message, {htmlContent: message.htmlContent})); socket.broadcast.emit("chatMessage", Object.assign(message, {htmlContent: message.htmlContent}));
socket.emit("chatMessageSent", Object.assign(message, {htmlContent: message.htmlContent})); socket.emit("chatMessageSent", Object.assign(message, {htmlContent: message.htmlContent}));
} else { } else {
socket.emit("error", "Not logged in!"); socket.emit("error", "Not logged in!");
} }
}); });
globals.internalEmitter.on(InternalEvents.GQLCHATMESSAGE, async (message: ChatMessage) => { globals.internalEmitter.on(InternalEvents.GQLCHATMESSAGE, async (message: ChatMessage) => {
if ((await message.$get("chat") as ChatRoom).id === chatId) { if ((await message.$get("chat") as ChatRoom).id === chatId) {
socket.emit("chatMessage", Object.assign(message, {htmlContent: message.htmlContent})); socket.emit("chatMessage", Object.assign(message, {htmlContent: message.htmlContent}));
} }
}); });
}); });
return chatNs; return chatNs;
} }
} }
export default HomeRoute; export default HomeRoute;

@ -1,34 +1,34 @@
/** /**
* @author Trivernis * @author Trivernis
* @remarks * @remarks
* *
* Taken from {@link https://github.com/Trivernis/whooshy} * Taken from {@link https://github.com/Trivernis/whooshy}
*/ */
import {Router} from "express"; import {Router} from "express";
import {Server} from "socket.io"; import {Server} from "socket.io";
import HomeRoute from "./home"; import HomeRoute from "./home";
const homeRoute = new HomeRoute(); const homeRoute = new HomeRoute();
/** /**
* Namespace to manage the routes of the server. * Namespace to manage the routes of the server.
* Allows easier assignments of graphql endpoints, socket.io connections and routers when * Allows easier assignments of graphql endpoints, socket.io connections and routers when
* used with {@link Route}. * used with {@link Route}.
*/ */
namespace routes { namespace routes {
export const router = Router(); export const router = Router();
router.use("/", homeRoute.router); router.use("/", homeRoute.router);
/** /**
* Assigns the io listeners or namespaces to the routes * Assigns the io listeners or namespaces to the routes
* @param io * @param io
*/ */
export const ioListeners = async (io: Server) => { export const ioListeners = async (io: Server) => {
await homeRoute.init(io); await homeRoute.init(io);
}; };
} }
export default routes; export default routes;

@ -1,12 +1,12 @@
html html
head head
link(href="/stylesheets/style.css" rel="stylesheet" type="text/css") link(href="/stylesheets/style.css" rel="stylesheet" type="text/css")
body body
div#server-error div#server-error
div div
h1 Page not found! h1 Page not found!
div div
code 404 code 404
div div
h1 The page "#{url}" was not found. h1 The page "#{url}" was not found.

@ -1,13 +1,13 @@
html html
head head
link(href="/stylesheets/style.css" rel="stylesheet" type="text/css") link(href="/stylesheets/style.css" rel="stylesheet" type="text/css")
body body
div#server-error div#server-error
div div
h1 Internal server error! h1 Internal server error!
div div
code 500 code 500
div div
h2 Oops the server couldn't handle that. h2 Oops the server couldn't handle that.
div div
p You might want to report this. p You might want to report this.

@ -1,24 +1,24 @@
{ {
"compileOnSave": true, "compileOnSave": true,
"compilerOptions": { "compilerOptions": {
"noImplicitAny": true, "noImplicitAny": true,
"removeComments": true, "removeComments": true,
"preserveConstEnums": true, "preserveConstEnums": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"outDir": "./dist", "outDir": "./dist",
"sourceMap": true, "sourceMap": true,
"target": "es2018", "target": "es2018",
"allowJs": true, "allowJs": true,
"moduleResolution": "node", "moduleResolution": "node",
"module": "commonjs", "module": "commonjs",
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true "emitDecoratorMetadata": true
}, },
"include": [ "include": [
"src/**/*" "src/**/*"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",
"**/*.spec.ts" "**/*.spec.ts"
] ]
} }

@ -1,32 +1,32 @@
{ {
"extends": "tslint:recommended", "extends": "tslint:recommended",
"rulesDirectory": [], "rulesDirectory": [],
"rules": { "rules": {
"max-line-length": { "max-line-length": {
"options": [120] "options": [120]
}, },
"new-parens": true, "new-parens": true,
"no-arg": true, "no-arg": true,
"no-bitwise": true, "no-bitwise": true,
"no-conditional-assignment": true, "no-conditional-assignment": true,
"no-consecutive-blank-lines": false, "no-consecutive-blank-lines": false,
"cyclomatic-complexity": true, "cyclomatic-complexity": true,
"brace-style": "1tbs", "brace-style": "1tbs",
"semicolon": true, "semicolon": true,
"indent": [true, "spaces", 4], "indent": [true, "spaces", 4],
"no-shadowed-variable": true, "no-shadowed-variable": true,
"no-console": { "no-console": {
"severity": "warning", "severity": "warning",
"options": ["debug", "info", "log", "time", "timeEnd", "trace"] "options": ["debug", "info", "log", "time", "timeEnd", "trace"]
}, },
"no-namespace": false, "no-namespace": false,
"no-internal-module": false, "no-internal-module": false,
"max-classes-per-file": false, "max-classes-per-file": false,
"no-var-requires": false "no-var-requires": false
}, },
"jsRules": { "jsRules": {
"max-line-length": { "max-line-length": {
"options": [120] "options": [120]
} }
} }
} }

Loading…
Cancel
Save