From e8eb85993e4c151bf8ee65ac869ef51741bd7c61 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 25 Jan 2020 11:43:51 +0100 Subject: [PATCH] Add level configuration and query - Add query getLevels to get all possible levels - Add mutation createLevel as a way for admins to create new levels - Remove column levelNumber and replace it with method that looks up the number of the level by counting --- src/lib/UploadManager.ts | 2 +- src/lib/errors/LevelAlreadyExistsError.ts | 10 ++++++ src/lib/models/Level.ts | 13 ++++---- src/lib/models/User.ts | 2 +- src/routes/graphql/MutationResolver.ts | 39 ++++++++++++++++++++++- src/routes/graphql/QueryResolver.ts | 24 ++++++++++++-- src/routes/graphql/schema.graphql | 14 +++++++- 7 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 src/lib/errors/LevelAlreadyExistsError.ts diff --git a/src/lib/UploadManager.ts b/src/lib/UploadManager.ts index 223ea89..00856c9 100644 --- a/src/lib/UploadManager.ts +++ b/src/lib/UploadManager.ts @@ -98,7 +98,7 @@ export class UploadManager { * @param extension */ public async processAndStoreVideo(data: Buffer, extension: string): Promise { - const fileBasename = UploadManager.getCrypticFileName() + extension; + const fileBasename = UploadManager.getCrypticFileName() + "." + extension; await fsx.ensureDir(this.dataDir); const filePath = path.join(this.dataDir, fileBasename); await fsx.writeFile(filePath, data); diff --git a/src/lib/errors/LevelAlreadyExistsError.ts b/src/lib/errors/LevelAlreadyExistsError.ts new file mode 100644 index 0000000..3ca5e2d --- /dev/null +++ b/src/lib/errors/LevelAlreadyExistsError.ts @@ -0,0 +1,10 @@ +import {BaseError} from "./BaseError"; + +/** + * An error that is thrown when the level already exists + */ +export class LevelAlreadyExistsError extends BaseError { + constructor(property: string) { + super(`A level with the property value '${property}' already exists`); + } +} diff --git a/src/lib/models/Level.ts b/src/lib/models/Level.ts index 51863e1..727b86c 100644 --- a/src/lib/models/Level.ts +++ b/src/lib/models/Level.ts @@ -16,18 +16,17 @@ export class Level extends Model { public name: string; /** - * The number of the level + * The required points for the level */ @NotNull @Unique @Column({allowNull: false, unique: true}) - public levelNumber: number; + public points: number; /** - * The required points for the level + * Returns the number of the level as the number of the database entry */ - @NotNull - @Unique - @Column({allowNull: false, unique: true}) - public points: number; + public async levelNumber(): Promise { + return Level.count({where: {points: {[sqz.Op.lte]: this.points}}}); + } } diff --git a/src/lib/models/User.ts b/src/lib/models/User.ts index 7d6c147..87b3f9c 100644 --- a/src/lib/models/User.ts +++ b/src/lib/models/User.ts @@ -44,7 +44,7 @@ export class User extends Model { */ @BeforeUpdate public static async assignLevel(instance: User) { - const level = await Level.findOne({where: {points: {[sqz.Op.lte]: instance.rankpoints}}, order: [["levelNumber", "desc"]]}) as Level; + const level = await Level.findOne({where: {points: {[sqz.Op.lte]: instance.rankpoints}}, order: [["points", "desc"]]}) as Level; if (level) { instance.$set("rLevel", level); } diff --git a/src/routes/graphql/MutationResolver.ts b/src/routes/graphql/MutationResolver.ts index 9933479..6e19237 100644 --- a/src/routes/graphql/MutationResolver.ts +++ b/src/routes/graphql/MutationResolver.ts @@ -1,11 +1,13 @@ import {GraphQLError} from "graphql"; import * as yaml from "js-yaml"; +import sequelize from "sequelize"; import isEmail from "validator/lib/isEmail"; import dataAccess from "../../lib/dataAccess"; import {BlacklistedError} from "../../lib/errors/BlacklistedError"; import {GroupNotFoundError} from "../../lib/errors/GroupNotFoundError"; import {HandleInUseError} from "../../lib/errors/HandleInUseError"; import {InvalidEmailError} from "../../lib/errors/InvalidEmailError"; +import {LevelAlreadyExistsError} from "../../lib/errors/LevelAlreadyExistsError"; import {NotAGroupAdminError} from "../../lib/errors/NotAGroupAdminError"; import {NotAnAdminError} from "../../lib/errors/NotAnAdminError"; import {NotTheGroupCreatorError} from "../../lib/errors/NotTheGroupCreatorError"; @@ -15,7 +17,18 @@ import {ReportReasonNameAlreadyExistsError} from "../../lib/errors/ReportReasonN import {ReportReasonNotFoundError} from "../../lib/errors/ReportReasonNotFoundError"; import globals from "../../lib/globals"; import {InternalEvents} from "../../lib/InternalEvents"; -import {Activity, BlacklistedPhrase, ChatMessage, ChatRoom, Event, Group, Post, Request, User} from "../../lib/models"; +import { + Activity, + BlacklistedPhrase, + ChatMessage, + ChatRoom, + Event, + Group, + Level, + Post, + Request, + User, +} from "../../lib/models"; import {Report} from "../../lib/models"; import {ReportReason} from "../../lib/models"; import {UploadManager} from "../../lib/UploadManager"; @@ -147,6 +160,8 @@ export class MutationResolver extends BaseResolver { await user.save(); return user.settings; } catch (err) { + globals.logger.warning(err.message); + globals.logger.debug(err.stack); throw new GraphQLError("Invalid settings json."); } } @@ -557,4 +572,26 @@ export class MutationResolver extends BaseResolver { } return ReportReason.create({name, description}); } + + /** + * Creates a new level + * @param name + * @param levelNumber + * @param requiredPoints + * @param request + */ + public async createLevel({name, requiredPoints}: {name: string, requiredPoints: number}, request: any): + Promise { + this.ensureLoggedIn(request); + const user = await User.findByPk(request.session.userId); + if (!user.isAdmin) { + throw new NotAnAdminError(); + } + const existingLevel = await Level.findOne({where: {[sequelize.Op.or]: [{name}, {points: requiredPoints}]}}); + if (existingLevel) { + throw new LevelAlreadyExistsError( + existingLevel.name === name ? "name" : "points"); + } + return Level.create({name, points: requiredPoints}); + } } diff --git a/src/routes/graphql/QueryResolver.ts b/src/routes/graphql/QueryResolver.ts index 7f0c1ed..3a7c374 100644 --- a/src/routes/graphql/QueryResolver.ts +++ b/src/routes/graphql/QueryResolver.ts @@ -7,7 +7,18 @@ import {GroupNotFoundError} from "../../lib/errors/GroupNotFoundError"; import {NotAnAdminError} from "../../lib/errors/NotAnAdminError"; import {RequestNotFoundError} from "../../lib/errors/RequestNotFoundError"; import {UserNotFoundError} from "../../lib/errors/UserNotFoundError"; -import {Activity, BlacklistedPhrase, ChatRoom, Event, Group, Post, Report, Request, User} from "../../lib/models"; +import { + Activity, + BlacklistedPhrase, + ChatRoom, + Event, + Group, + Level, + Post, + Report, + Request, + User +} from "../../lib/models"; import {BlacklistedResult} from "./BlacklistedResult"; import {MutationResolver} from "./MutationResolver"; import {SearchResult} from "./SearchResult"; @@ -198,6 +209,15 @@ export class QueryResolver extends MutationResolver { if (!user?.isAdmin) { throw new NotAnAdminError(); } - return Report.findAll({limit: first, offset}); + return Report.findAll({limit: first, offset, order: [["id", "DESC"]]}); + } + + /** + * Returns the levels that are configured + * @param first + * @param offset + */ + public async getLevels({first, offset}: {first: number, offset: number}): Promise { + return Level.findAll({limit: first, offset, order: [["points", "ASC"]]}); } } diff --git a/src/routes/graphql/schema.graphql b/src/routes/graphql/schema.graphql index e0082e2..4ddf7be 100644 --- a/src/routes/graphql/schema.graphql +++ b/src/routes/graphql/schema.graphql @@ -48,6 +48,9 @@ type Query { "Returns all issued reports with pagination" getReports(first: Int = 20, offset: Int = 0): [Report!]! @complexity(value: 1, multipliers: ["first"]) + + "Returns the levels configured in the backend" + getLevels(first: Int =20, offset: Int = 0): [Level!]! @complexity(value: 1, multipliers: ["first"]) } type Mutation { @@ -143,6 +146,9 @@ type Mutation { "Creates a new report reason" createReportReason(name: String!, description: String!): ReportReason + + "Creates a new level" + createLevel(name: String!, requiredPoints: Int!): Level! } interface UserData { @@ -574,11 +580,17 @@ type ReportReason { "A level of a user" type Level { + "The level id" + id: ID! + "The name of the level" name: String! "The number of the level in the ranking" - levelNumber: String! + levelNumber: Int! + + "The points required for this level" + points: Int! } "represents the type of media"