Merge branch 'develop' of Software_Engineering_I/greenvironment-server into master

pull/5/head
Trivernis 5 years ago committed by Gitea
commit a4ce781e41

@ -1,20 +1,20 @@
# 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).
## [0.9] - 2019-10-29 ## [0.9] - 2019-10-29
### 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
- angular integration by redirecting to `index.html` on not found - angular integration by redirecting to `index.html` on not found

@ -9,3 +9,4 @@ 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".
Additionally the server needs a working redis server to connect to.

7900
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -30,7 +30,6 @@
"@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/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",
@ -38,8 +37,8 @@
"@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/socket.io-redis": "^1.0.25",
"@types/validator": "^10.11.3", "@types/validator": "^10.11.3",
"@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",
@ -48,7 +47,7 @@
"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.7.2"
}, },
"dependencies": { "dependencies": {
"compression": "^1.7.4", "compression": "^1.7.4",
@ -72,6 +71,7 @@
"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",
"socket.io-redis": "^5.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"

@ -14,8 +14,9 @@ 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 * as socketIoRedis from "socket.io-redis";
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";
@ -26,9 +27,11 @@ 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 id?: number;
public readonly sequelize: Sequelize; public readonly sequelize: Sequelize;
constructor() { constructor(id?: number) {
this.id = id;
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);
@ -38,12 +41,13 @@ class App {
/** /**
* initializes everything that needs to be initialized asynchronous. * initializes everything that needs to be initialized asynchronous.
*/ */
public async init() { public async init(): Promise<void> {
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,
// @ts-ignore
secure: "auto", secure: "auto",
}, },
resave: false, resave: false,
@ -53,12 +57,15 @@ class App {
}); });
const force = fsx.existsSync("sqz-force"); const force = fsx.existsSync("sqz-force");
logger.info(`Sequelize Table force: ${force}`); logger.info(`Syncinc database. Sequelize Table force: ${force}.`);
await this.sequelize.sync({force, logging: (msg) => logger.silly(msg)}); await this.sequelize.sync({force, logging: (msg) => logger.silly(msg)});
this.sequelize.options.logging = (msg) => logger.silly(msg);
logger.info("Setting up socket.io");
await routes.ioListeners(this.io); await routes.ioListeners(this.io);
this.io.adapter(socketIoRedis());
this.io.use(sharedsession(appSession, {autoSave: true})); this.io.use(sharedsession(appSession, {autoSave: true}));
logger.info("Configuring express app.");
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);
@ -69,14 +76,17 @@ class App {
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) { // enable cross origin requests if enabled in the config
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}`);
process.send({cmd: "notifyRequest"});
next(); next();
}); });
this.app.use(routes.router); this.app.use(routes.router);
// listen for graphql requrest
this.app.use("/graphql", graphqlHTTP((request, response) => { this.app.use("/graphql", graphqlHTTP((request, response) => {
return { return {
// @ts-ignore all // @ts-ignore all
@ -86,25 +96,40 @@ class App {
schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))), schema: buildSchema(importSchema(path.join(__dirname, "./graphql/schema.graphql"))),
}; };
})); }));
// allow access to cluster information
this.app.use("/cluster-info", (req: Request, res: Response) => {
res.json({
id: this.id,
});
});
// redirect all request to the angular file
this.app.use((req: any, res: Response) => { this.app.use((req: any, res: Response) => {
if (globals.config.frontend.angularIndex) { if (globals.config.frontend.angularIndex) {
res.sendFile(path.join(__dirname, globals.config.frontend.angularIndex)); const angularIndex = path.join(__dirname, globals.config.frontend.angularIndex);
if (fsx.existsSync(path.join(angularIndex))) {
res.sendFile(angularIndex);
} else {
res.status(httpStatus.NOT_FOUND);
res.render("errors/404.pug", {url: req.url});
}
} else { } else {
res.status(httpStatus.NOT_FOUND); res.status(httpStatus.NOT_FOUND);
res.render("errors/404.pug", {url: req.url}); res.render("errors/404.pug", {url: req.url});
} }
}); });
// show an error page for internal errors
this.app.use((err, req: Request, res: Response) => { this.app.use((err, req: Request, res: Response) => {
res.status(httpStatus.INTERNAL_SERVER_ERROR); res.status(httpStatus.INTERNAL_SERVER_ERROR);
res.render("errors/500.pug"); res.render("errors/500.pug");
}); });
logger.info("Server configured.");
} }
/** /**
* Starts the web server. * Starts the web server.
*/ */
public start() { public start(): void {
if (globals.config.server.port) { if (globals.config.server?.port) {
logger.info(`Starting server...`); logger.info(`Starting server...`);
this.app.listen(globals.config.server.port); this.app.listen(globals.config.server.port);
logger.info(`Server running on port ${globals.config.server.port}`); logger.info(`Server running on port ${globals.config.server.port}`);

@ -1,11 +1,12 @@
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";
import * as yaml from "js-yaml";
/** /**
* Returns the resolvers for the graphql api. * Returns the resolvers for the graphql api.
@ -121,6 +122,22 @@ export function resolver(req: any, res: any): any {
return new GraphQLError("No username, email or password given."); return new GraphQLError("No username, email or password given.");
} }
}, },
async setUserSettings({settings}: {settings: string}) {
if (req.session.userId) {
const user = await models.User.findByPk(req.session.userId);
try {
user.frontendSettings = yaml.safeLoad(settings);
await user.save();
return user.settings;
} catch (err) {
res.status(400);
return new GraphQLError("Invalid settings json.");
}
} else {
res.status(status.UNAUTHORIZED);
return new NotLoggedInGqlError();
}
},
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) {

@ -37,6 +37,9 @@ type Mutation {
"Registers the user." "Registers the user."
register(username: String, email: String, passwordHash: String): Profile register(username: String, email: String, passwordHash: String): Profile
"Sets the user settings to the specified settings string. The settings parameter should be a valid yaml."
setUserSettings(settings: String!): String!
"Logout of the user." "Logout of the user."
logout: Boolean logout: Boolean
@ -247,6 +250,9 @@ type Profile implements UserData {
"the levels of the user depending on the points" "the levels of the user depending on the points"
level: Int! level: Int!
"the custom settings for the frontend"
settings: String!
} }
"represents a single user post" "represents a single user post"
@ -274,7 +280,7 @@ type Post {
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(userId: ID!): VoteType
} }
"represents a request of any type" "represents a request of any type"

@ -1,11 +1,96 @@
// tslint:disable:no-console
import * as cluster from "cluster";
import App from "./app"; import App from "./app";
const numCPUs = require("os").cpus().length;
/** interface IResourceUsage {
* async main function wrapper. mem: {rss: number, heapTotal: number, heapUsed: number, external: number};
*/ cpu: {user: number, system: number};
(async () => { }
const app = new App();
await app.init();
app.start();
})();
interface IClusterData {
reqCount: number;
workerCount: () => number;
workerRes: {[key: string]: IResourceUsage};
}
if (cluster.isMaster) {
console.log(`[CLUSTER-M] Master ${process.pid} is running`);
const clusterData: IClusterData = {
reqCount: 0,
workerCount: () => Object.keys(cluster.workers).length,
// @ts-ignore
workerRes: {},
};
setInterval(() => {
clusterData.workerRes.M = {
cpu: process.cpuUsage(),
mem: process.memoryUsage(),
};
}, 1000);
const log = (msg: string) => {
process.stdout.write(" ".padEnd(100) + "\r");
process.stdout.write(msg);
process.stdout.write(
`W: ${clusterData.workerCount()},R: ${clusterData.reqCount},M: ${(() => {
let usageString = "";
for (const [key, value] of Object.entries(clusterData.workerRes)) {
usageString += `${
Math.round((value as IResourceUsage).mem.heapUsed / 100000) / 10}MB,`.padEnd(8);
}
return usageString;
})()}`.padEnd(99) + "\r");
};
cluster.settings.silent = true;
cluster.on("exit", (worker, code, signal) => {
log(`[CLUSTER-M] Worker ${worker.process.pid} died!\n`);
delete clusterData.workerRes[worker.id];
log("[CLUSTER-M] Starting new worker\n");
cluster.fork();
});
cluster.on("online", (worker) => {
worker.process.stdout.on("data", (data) => {
log(`[CLUSTER-${worker.id}] ${data}`);
});
});
cluster.on("message", (worker, message) => {
switch (message.cmd) {
case "notifyRequest":
clusterData.reqCount++;
log("");
break;
case "notifyResources":
// @ts-ignore
clusterData.workerRes[worker.id] = message.data;
log("");
break;
default:
break;
}
});
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
/**
* async main function wrapper.
*/
(async () => {
setInterval(() => {
process.send({cmd: "notifyResources", data: {
cpu: process.cpuUsage(),
mem: process.memoryUsage(),
}});
}, 1000);
const app = new App(cluster.worker.id);
await app.init();
app.start();
})();
console.log(`[CLUSTER] Worker ${process.pid} started`);
}

@ -35,7 +35,7 @@ namespace dataaccess {
/** /**
* 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): Promise<void> {
sequelize = seq; sequelize = seq;
try { try {
await sequelize.addModels([ await sequelize.addModels([
@ -131,7 +131,7 @@ namespace dataaccess {
* @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): Promise<models.Post[]> {
if (sort === SortType.NEW) { if (sort === SortType.NEW) {
return models.Post.findAll({ return models.Post.findAll({
include: [{association: "rVotes"}], include: [{association: "rVotes"}],
@ -140,6 +140,7 @@ namespace dataaccess {
order: [["createdAt", "DESC"]], order: [["createdAt", "DESC"]],
}); });
} else { } else {
// more performant way to get the votes with plain sql
return await sequelize.query( return await sequelize.query(
`SELECT * FROM ( `SELECT * FROM (
SELECT *, SELECT *,

@ -1,4 +1,4 @@
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 {

@ -1,12 +1,13 @@
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 path from "path";
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 = path.join(__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))) {
@ -21,9 +22,9 @@ if (!(fsx.pathExistsSync(configPath))) {
* 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: IConfig = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8"));
// @ts-ignore // @ts-ignore
export const logger = winston.createLogger({ export const logger: winston.Logger = winston.createLogger({
transports: [ transports: [
new winston.transports.Console({ new winston.transports.Console({
format: winston.format.combine( format: winston.format.combine(
@ -33,7 +34,7 @@ namespace globals {
return `${timestamp} ${level}: ${message}`; return `${timestamp} ${level}: ${message}`;
}), }),
), ),
level: config.logging.level, level: config.logging?.level ?? "info",
}), }),
// @ts-ignore // @ts-ignore
new (winston.transports.DailyRotateFile)({ new (winston.transports.DailyRotateFile)({
@ -46,13 +47,13 @@ namespace globals {
}), }),
), ),
json: false, json: false,
level: config.logging.level, level: config.logging?.level ?? "info",
maxFiles: "7d", maxFiles: "7d",
zippedArchive: true, zippedArchive: true,
}), }),
], ],
}); });
export const internalEmitter = new EventEmitter(); export const internalEmitter: EventEmitter = new EventEmitter();
} }
export default globals; export default globals;

@ -0,0 +1,67 @@
/**
* An interface for the configuration file
*/
interface IConfig {
/**
* Database connection info
*/
database: {
/**
* A connection uri for the database. <type>://<user>:<password>@<ip/domain>/<database>
*/
connectionUri: string;
};
/**
* Configuration for the http server
*/
server?: {
/**
* The port to listen on
*/
port?: number;
/**
* If cross origin requests should be enabled
*/
cors?: false;
};
/**
* The session configuration
*/
session: {
/**
* A secure secret to be used for sessions
*/
secret: string;
/**
* The maximum cookie age before the session gets deleted
*/
cookieMaxAge: number;
};
/**
* Configuration for markdown parsing
*/
markdown?: {
/**
* The plugins to use for parsing
*/
plugins: string[];
};
/**
* Logging configuration
*/
logging?: {
/**
* The loglevel that is used for the console and logfiles
*/
level?: ("silly" | "debug" | "verbose" | "info" | "warn" | "error");
};
/**
* The frontend configuration
*/
frontend?: {
/**
* Points to the index.html which is loaded as a fallback for angular to work
*/
angularIndex?: string;
};
}

@ -20,7 +20,7 @@ namespace markdown {
* 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): string {
return md.renderInline(markdownString); return md.renderInline(markdownString);
} }
@ -28,7 +28,7 @@ namespace markdown {
* Renders the markdown string. * Renders the markdown string.
* @param markdownString * @param markdownString
*/ */
export function render(markdownString: string) { export function render(markdownString: string): string {
return md.render(markdownString); return md.render(markdownString);
} }
} }

@ -29,8 +29,8 @@ export class Event extends Model<Event> {
} }
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[];
} }
} }

@ -41,14 +41,14 @@ export class Group extends Model<Group> {
} }
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[];
} }
@ -57,8 +57,8 @@ export class Group extends Model<Group> {
} }
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,5 +1,5 @@
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";
@ -44,15 +44,20 @@ export class Post extends Model<Post> {
return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.DOWNVOTE).length; return (await this.votes()).filter((v) => v.PostVote.voteType === VoteType.DOWNVOTE).length;
} }
/**
* Toggles the vote of the user.
* @param userId
* @param type
*/
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) {
@ -67,4 +72,13 @@ export class Post extends Model<Post> {
return vote.PostVote.voteType; return vote.PostVote.voteType;
} }
/**
* Returns the type of vote that was performend on the post by the user specified by the user id.
* @param userId
*/
public async userVote({userId}: {userId: number}): Promise<VoteType> {
const votes = await this.$get("rVotes", {where: {id: userId}}) as Array<User & {PostVote: PostVote}>;
return votes[0]?.PostVote?.voteType;
}
} }

@ -49,6 +49,10 @@ export class User extends Model<User> {
@Column({defaultValue: 0, allowNull: false}) @Column({defaultValue: 0, allowNull: false})
public rankpoints: number; public rankpoints: number;
@NotNull
@Column({defaultValue: {}, allowNull: false, type: sqz.JSON})
public frontendSettings: any;
@BelongsToMany(() => User, () => Friendship, "userId") @BelongsToMany(() => User, () => Friendship, "userId")
public rFriends: User[]; public rFriends: User[];
@ -119,14 +123,21 @@ export class User extends Model<User> {
return Math.ceil(this.rankpoints / 100); return Math.ceil(this.rankpoints / 100);
} }
/**
* returns the settings of the user as a jston string
*/
public get settings(): string {
return JSON.stringify(this.getDataValue("frontendSettings"));
}
/** /**
* 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[];
} }
@ -143,8 +154,8 @@ export class User extends Model<User> {
* @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[];
} }
@ -170,8 +181,8 @@ export class User extends Model<User> {
} }
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[];
} }
@ -210,8 +221,8 @@ export class User extends Model<User> {
* @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[];
} }

@ -1,6 +1,6 @@
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";

@ -2,13 +2,16 @@
"compileOnSave": true, "compileOnSave": true,
"compilerOptions": { "compilerOptions": {
"noImplicitAny": true, "noImplicitAny": true,
"noImplicitThis": 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": false,
"forceConsistentCasingInFileNames": true,
"strictFunctionTypes": true,
"moduleResolution": "node", "moduleResolution": "node",
"module": "commonjs", "module": "commonjs",
"experimentalDecorators": true, "experimentalDecorators": true,

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save