From 9328367bb58288e4ccadfb36766d38537afbc886 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 25 Jan 2020 11:15:46 +0100 Subject: [PATCH] Add configurable levels - Add Level table and gql type - change level field on User - add hook on user to update the level --- CHANGELOG.md | 3 +++ src/lib/dataAccess.ts | 2 +- src/lib/models/Level.ts | 33 +++++++++++++++++++++++++++++++ src/lib/models/Media.ts | 4 +++- src/lib/models/User.ts | 32 ++++++++++++++++++++++++++++-- src/lib/models/index.ts | 1 + src/routes/graphql/schema.graphql | 16 ++++++++++++--- 7 files changed, 84 insertions(+), 7 deletions(-) create mode 100644 src/lib/models/Level.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index add2222..f2ff883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - delete handler for media to delete the corresponding file - type for create post to know if it is a media or text post (media posts are invisible until a media file is uploaded) - reports and mutations to report posts and create reasons to report +- level entity ### Removed @@ -51,6 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - config behaviour to use all files that reside in the ./config directory with the .toml format - default response timeout from 2 minutes to 30 seconds - cluster api to start workers with a 2 second delay each to avoid race conditions +- levels to be configured in the backend ### Fixed @@ -60,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - style issues - graphql schema for denyRequest using the wrong parameters - sendRequest allowing duplicates +- upload throwing an error when the old picture doesn't exist ## [0.9] - 2019-10-29 diff --git a/src/lib/dataAccess.ts b/src/lib/dataAccess.ts index 471e38e..cc566eb 100644 --- a/src/lib/dataAccess.ts +++ b/src/lib/dataAccess.ts @@ -67,12 +67,12 @@ namespace dataaccess { models.Media, models.Report, models.ReportReason, + models.Level, ]); } catch (err) { globals.logger.error(err.message); globals.logger.debug(err.stack); } - await databaseCleanup(); setInterval(databaseCleanup, config.get("database.cleanupInterval") * 1000); } diff --git a/src/lib/models/Level.ts b/src/lib/models/Level.ts new file mode 100644 index 0000000..51863e1 --- /dev/null +++ b/src/lib/models/Level.ts @@ -0,0 +1,33 @@ +import * as sqz from "sequelize"; +import {Column, Model, NotNull, Table, Unique} from "sequelize-typescript"; + +/** + * A level of the ranking system + */ +@Table({underscored: true}) +export class Level extends Model { + + /** + * The name of the level + */ + @NotNull + @Unique + @Column({allowNull: false, type: sqz.STRING(64), unique: true}) + public name: string; + + /** + * The number of the level + */ + @NotNull + @Unique + @Column({allowNull: false, unique: true}) + public levelNumber: number; + + /** + * The required points for the level + */ + @NotNull + @Unique + @Column({allowNull: false, unique: true}) + public points: number; +} diff --git a/src/lib/models/Media.ts b/src/lib/models/Media.ts index cc5c62e..606cafb 100644 --- a/src/lib/models/Media.ts +++ b/src/lib/models/Media.ts @@ -18,7 +18,9 @@ export class Media extends Model { */ @BeforeDestroy public static async deleteMediaFile(instance: Media) { - await fsx.unlink(instance.path); + if (await fsx.pathExists(instance.path)) { + await fsx.unlink(instance.path); + } } /** diff --git a/src/lib/models/User.ts b/src/lib/models/User.ts index c7ae2ec..7d6c147 100644 --- a/src/lib/models/User.ts +++ b/src/lib/models/User.ts @@ -1,5 +1,6 @@ import * as sqz from "sequelize"; import { + BeforeUpdate, BelongsTo, BelongsToMany, Column, @@ -24,6 +25,7 @@ import {Friendship} from "./Friendship"; import {Group} from "./Group"; import {GroupAdmin} from "./GroupAdmin"; import {GroupMember} from "./GroupMember"; +import {Level} from "./Level"; import {Media} from "./Media"; import {Post} from "./Post"; import {PostVote} from "./PostVote"; @@ -35,6 +37,19 @@ import {Request, RequestType} from "./Request"; @Table({underscored: true}) export class User extends Model { + /** + * A function that is called before the user is updated. + * It assigns the corresponding level to the user + * @param instance + */ + @BeforeUpdate + public static async assignLevel(instance: User) { + const level = await Level.findOne({where: {points: {[sqz.Op.lte]: instance.rankpoints}}, order: [["levelNumber", "desc"]]}) as Level; + if (level) { + instance.$set("rLevel", level); + } + } + /** * The name of the user */ @@ -99,6 +114,13 @@ export class User extends Model { @Column({defaultValue: false, allowNull: false}) public isAdmin: boolean; + /** + * The level of the user + */ + @ForeignKey(() => Level) + @Column({allowNull: true}) + public levelId: number; + /** * The id of the media that is the users profile picture */ @@ -106,6 +128,12 @@ export class User extends Model { @Column({allowNull: true}) public mediaId: number; + /** + * The level of the user + */ + @BelongsTo(() => Level) + public rLevel: Level; + /** * The media of the user */ @@ -220,8 +248,8 @@ export class User extends Model { /** * The level of the user which is the points divided by 100 */ - public get level(): number { - return Math.ceil(this.getDataValue("rankpoints") / 100); + public async level(): Promise { + return await this.$get("rLevel") as Level; } /** diff --git a/src/lib/models/index.ts b/src/lib/models/index.ts index 55fff59..99e86de 100644 --- a/src/lib/models/index.ts +++ b/src/lib/models/index.ts @@ -16,3 +16,4 @@ export {BlacklistedPhrase} from "./BlacklistedPhrase"; export {Media} from "./Media"; export {Report} from "./Report"; export {ReportReason} from "./ReportReason"; +export {Level} from "./Level"; diff --git a/src/routes/graphql/schema.graphql b/src/routes/graphql/schema.graphql index 044c166..e0082e2 100644 --- a/src/routes/graphql/schema.graphql +++ b/src/routes/graphql/schema.graphql @@ -189,7 +189,7 @@ interface UserData { points: Int! "the levels of the user depending on the points" - level: Int! + level: Level } "represents a single user account" @@ -240,7 +240,7 @@ type User implements UserData{ eventCount: Int! "the levels of the user depending on the points" - level: Int! + level: Level } type Profile implements UserData { @@ -311,7 +311,7 @@ type Profile implements UserData { points: Int! "the levels of the user depending on the points" - level: Int! + level: Level "the custom settings for the frontend" settings: String! @@ -571,6 +571,16 @@ type ReportReason { description: String! } +"A level of a user" +type Level { + + "The name of the level" + name: String! + + "The number of the level in the ranking" + levelNumber: String! +} + "represents the type of media" enum MediaType { VIDEO