Browse Source

refactor: worked more on replacing mongoose with sequalize

Very much WIP.
Many linting, TS and test-related things have to be fixed.
Not all schemas are (fully) converted yet.
Lots of new TODO comments will need to be fixed.
Kristian Vos 10 months ago
parent
commit
c45a4603b0
66 changed files with 864 additions and 507 deletions
  1. 2 5
      backend/src/Job.ts
  2. 5 5
      backend/src/JobContext.ts
  3. 6 6
      backend/src/main.ts
  4. 87 7
      backend/src/modules/DataModule.ts
  5. 0 1
      backend/src/modules/DataModule/DeleteByIdJob.ts
  6. 0 1
      backend/src/modules/DataModule/DeleteManyByIdJob.ts
  7. 0 1
      backend/src/modules/DataModule/FindByIdJob.ts
  8. 0 1
      backend/src/modules/DataModule/FindManyByIdJob.ts
  9. 32 22
      backend/src/modules/DataModule/GetDataJob.ts
  10. 0 1
      backend/src/modules/DataModule/UpdateByIdJob.ts
  11. 55 0
      backend/src/modules/DataModule/models/MinifiedUser.ts
  12. 2 1
      backend/src/modules/DataModule/models/MinifiedUser/events/MinifiedUserCreatedEvent.ts
  13. 2 1
      backend/src/modules/DataModule/models/MinifiedUser/events/MinifiedUserDeletedEvent.ts
  14. 2 1
      backend/src/modules/DataModule/models/MinifiedUser/events/MinifiedUserUpdatedEvent.ts
  15. 2 1
      backend/src/modules/DataModule/models/MinifiedUser/jobs/FindById.ts
  16. 2 1
      backend/src/modules/DataModule/models/MinifiedUser/jobs/FindManyById.ts
  17. 0 0
      backend/src/modules/DataModule/models/MinifiedUser/migrations/170526579600-create-minified-users-view.ts
  18. 72 0
      backend/src/modules/DataModule/models/MinifiedUser/schema.ts
  19. 78 31
      backend/src/modules/DataModule/models/News.ts
  20. 9 3
      backend/src/modules/DataModule/models/News/jobs/GetData.ts
  21. 1 1
      backend/src/modules/DataModule/models/News/jobs/Newest.ts
  22. 52 0
      backend/src/modules/DataModule/models/Session.ts
  23. 2 1
      backend/src/modules/DataModule/models/Session/events/SessionCreatedEvent.ts
  24. 2 1
      backend/src/modules/DataModule/models/Session/events/SessionDeletedEvent.ts
  25. 2 1
      backend/src/modules/DataModule/models/Session/events/SessionUpdatedEvent.ts
  26. 2 1
      backend/src/modules/DataModule/models/Session/jobs/Create.ts
  27. 2 1
      backend/src/modules/DataModule/models/Session/jobs/DeleteById.ts
  28. 2 1
      backend/src/modules/DataModule/models/Session/jobs/FindById.ts
  29. 2 1
      backend/src/modules/DataModule/models/Session/jobs/FindManyById.ts
  30. 2 1
      backend/src/modules/DataModule/models/Session/jobs/UpdateById.ts
  31. 25 0
      backend/src/modules/DataModule/models/Session/schema.ts
  32. 74 0
      backend/src/modules/DataModule/models/User.ts
  33. 0 0
      backend/src/modules/DataModule/models/User/UserAvatarColor.ts
  34. 0 0
      backend/src/modules/DataModule/models/User/UserAvatarType.ts
  35. 0 0
      backend/src/modules/DataModule/models/User/UserRole.ts
  36. 0 0
      backend/src/modules/DataModule/models/User/config.ts
  37. 2 1
      backend/src/modules/DataModule/models/User/events/UserCreatedEvent.ts
  38. 2 1
      backend/src/modules/DataModule/models/User/events/UserDeletedEvent.ts
  39. 2 1
      backend/src/modules/DataModule/models/User/events/UserUpdatedEvent.ts
  40. 0 0
      backend/src/modules/DataModule/models/User/getData.ts
  41. 2 1
      backend/src/modules/DataModule/models/User/jobs/Create.ts
  42. 2 1
      backend/src/modules/DataModule/models/User/jobs/DeleteById.ts
  43. 2 1
      backend/src/modules/DataModule/models/User/jobs/DeleteManyById.ts
  44. 12 0
      backend/src/modules/DataModule/models/User/jobs/FindById.ts
  45. 2 1
      backend/src/modules/DataModule/models/User/jobs/FindManyById.ts
  46. 2 1
      backend/src/modules/DataModule/models/User/jobs/GetData.ts
  47. 7 3
      backend/src/modules/DataModule/models/User/jobs/GetModelPermissions.ts
  48. 4 5
      backend/src/modules/DataModule/models/User/jobs/GetPermissions.ts
  49. 2 1
      backend/src/modules/DataModule/models/User/jobs/UpdateById.ts
  50. 227 0
      backend/src/modules/DataModule/models/User/schema.ts
  51. 0 72
      backend/src/modules/DataModule/models/minifiedUsers/schema.ts
  52. 15 0
      backend/src/modules/DataModule/models/news/listeners/NewsUpdatedListener.ts
  53. 0 25
      backend/src/modules/DataModule/models/sessions/schema.ts
  54. 0 16
      backend/src/modules/DataModule/models/users/jobs/FindById.ts
  55. 0 227
      backend/src/modules/DataModule/models/users/schema.ts
  56. 3 5
      backend/src/modules/DataModule/permissions/isAdmin.ts
  57. 2 3
      backend/src/modules/DataModule/permissions/isLoggedIn.ts
  58. 3 5
      backend/src/modules/DataModule/permissions/isModerator.ts
  59. 3 5
      backend/src/modules/DataModule/permissions/modelPermissions/isDj.ts
  60. 2 2
      backend/src/modules/DataModule/permissions/modelPermissions/isLoggedIn.ts
  61. 1 2
      backend/src/modules/DataModule/permissions/modelPermissions/isNewsPublished.ts
  62. 2 2
      backend/src/modules/DataModule/permissions/modelPermissions/isOwner.ts
  63. 6 2
      backend/src/modules/EventsModule.ts
  64. 2 5
      backend/src/modules/EventsModule/Event.ts
  65. 7 7
      backend/src/modules/EventsModule/jobs/Subscribe.spec.ts
  66. 26 14
      backend/src/modules/WebSocketModule.ts

+ 2 - 5
backend/src/Job.ts

@@ -1,7 +1,6 @@
 import { SessionSchema } from "@models/sessions/schema";
 import { getErrorMessage } from "@common/utils/getErrorMessage";
 import { generateUuid } from "@common/utils/generateUuid";
-import { HydratedDocument } from "mongoose";
 import Joi from "joi";
 import * as inflection from "inflection";
 import JobContext from "@/JobContext";
@@ -10,7 +9,7 @@ import LogBook, { Log } from "@/LogBook";
 import BaseModule from "./BaseModule";
 import EventsModule from "./modules/EventsModule";
 import JobCompletedEvent from "./modules/EventsModule/events/JobCompletedEvent";
-import { UserSchema } from "./modules/DataModule/models/users/schema";
+import User from "./modules/DataModule/models/User";
 
 export enum JobStatus {
 	QUEUED = "QUEUED",
@@ -187,9 +186,7 @@ export default abstract class Job {
 		| (boolean | CallableFunction)[] = false;
 
 	// Check if a given user has generic permission to execute a job, using _hasPermission
-	public static async hasPermission(
-		user: HydratedDocument<UserSchema> | null
-	) {
+	public static async hasPermission(user: User | null) {
 		const options = Array.isArray(this._hasPermission)
 			? this._hasPermission
 			: [this._hasPermission];

+ 5 - 5
backend/src/JobContext.ts

@@ -3,14 +3,14 @@ import { SessionSchema } from "@/modules/DataModule/models/sessions/schema";
 import Job, { JobOptions } from "@/Job";
 import { Log } from "@/LogBook";
 import DataModule from "@/modules/DataModule";
-import { UserModel } from "@/modules/DataModule/models/users/schema";
 import { JobDerived } from "./types/JobDerived";
 import assertJobDerived from "./utils/assertJobDerived";
 import {
 	GetMultipleModelPermissionsResult,
 	GetSingleModelPermissionsResult
-} from "./modules/DataModule/models/users/jobs/GetModelPermissions";
-import { GetPermissionsResult } from "./modules/DataModule/models/users/jobs/GetPermissions";
+} from "./modules/DataModule/models/User/jobs/GetModelPermissions";
+import { GetPermissionsResult } from "./modules/DataModule/models/User/jobs/GetPermissions";
+import User from "./modules/DataModule/models/User";
 
 const permissionRegex =
 	// eslint-disable-next-line max-len
@@ -83,9 +83,9 @@ export default class JobContext {
 		if (!this._session?.userId)
 			throw new Error("No user found for session");
 
-		const User = await DataModule.getModel<UserModel>("users");
+		const User = await DataModule.getModel<User>("users");
 
-		const user = await User.findById(this._session.userId);
+		const user = await User.findByPk(this._session.userId);
 
 		if (!user) throw new Error("No user found for session");
 

+ 6 - 6
backend/src/main.ts

@@ -1,4 +1,5 @@
 import * as readline from "node:readline";
+import { NewsStatus } from "@models/News/NewsStatus";
 import ModuleManager from "@/ModuleManager";
 import LogBook from "@/LogBook";
 import JobQueue from "@/JobQueue";
@@ -8,7 +9,6 @@ import EventsModule from "./modules/EventsModule";
 // import { NewsModel } from "./modules/DataModule/models/news/schema";
 // import { FilterType } from "./modules/DataModule/plugins/getData";
 import News from "./modules/DataModule/models/News";
-import { NewsStatus } from "@models/News/NewsStatus";
 import GetData from "./modules/DataModule/models/News/jobs/GetData";
 
 process.removeAllListeners("uncaughtException");
@@ -82,16 +82,16 @@ ModuleManager.startup().then(async () => {
 	);
 
 	console.log(
-		await (new GetData({
+		await new GetData({
 			page: 1,
 			pageSize: 10,
-			properties: ['id'],
+			properties: ["id"],
 			sort: {
-				id: 'ascending'
+				id: "ascending"
 			},
 			queries: [],
-			operator: 'and'
-		}).execute())
+			operator: "and"
+		}).execute()
 	);
 });
 

+ 87 - 7
backend/src/modules/DataModule.ts

@@ -2,14 +2,71 @@ import config from "config";
 import { readdir } from "fs/promises";
 import path from "path";
 import { forEachIn } from "@common/utils/forEachIn";
-import { Sequelize, Model as SequelizeModel, ModelStatic } from "sequelize";
+import {
+	Sequelize,
+	Model as SequelizeModel,
+	ModelStatic,
+	DataTypes,
+	Utils
+} from "sequelize";
 import { Dirent } from "fs";
 import * as inflection from "inflection";
 import BaseModule, { ModuleStatus } from "@/BaseModule";
-import EventsModule from "./EventsModule";
 import DataModuleJob from "./DataModule/DataModuleJob";
 import Job from "@/Job";
 
+export type ObjectIdType = string;
+
+// TODO fix TS
+// TODO implement actual checking of ObjectId's
+// TODO move to a better spot
+// Strange behavior would result if we extended DataTypes.ABSTRACT because
+// it's a class wrapped in a Proxy by Utils.classToInvokable.
+class OBJECTID extends DataTypes.ABSTRACT.prototype.constructor {
+	// Mandatory: set the type key
+	static key = "OBJECTID";
+
+	key = OBJECTID.key;
+
+	// Mandatory: complete definition of the new type in the database
+	toSql() {
+		return "VARCHAR(24)";
+	}
+
+	// Optional: validator function
+	// @ts-ignore
+	validate(value, options) {
+		return true;
+		// return (typeof value === 'number') && (!Number.isNaN(value));
+	}
+
+	// Optional: sanitizer
+	// @ts-ignore
+	_sanitize(value) {
+		return value;
+		// Force all numbers to be positive
+		// return value < 0 ? 0 : Math.round(value);
+	}
+
+	// Optional: value stringifier before sending to database
+	// @ts-ignore
+	_stringify(value) {
+		return value;
+		// return value.toString();
+	}
+
+	// Optional: parser for values received from the database
+	// @ts-ignore
+	static parse(value) {
+		return value;
+		// return Number.parseInt(value);
+	}
+}
+
+// Optional: add the new type to DataTypes. Optionally wrap it on `Utils.classToInvokable` to
+// be able to use this datatype directly without having to call `new` on it.
+DataTypes.OBJECTID = Utils.classToInvokable(OBJECTID);
+
 export class DataModule extends BaseModule {
 	private _sequelize?: Sequelize;
 
@@ -50,7 +107,8 @@ export class DataModule extends BaseModule {
 	 * setupSequelize - Setup sequelize instance
 	 */
 	private async _setupSequelize() {
-		const { username, password, host, port, database } = config.get<any>("postgres");
+		const { username, password, host, port, database } =
+			config.get<any>("postgres");
 		this._sequelize = new Sequelize(database, username, password, {
 			host,
 			port,
@@ -60,6 +118,8 @@ export class DataModule extends BaseModule {
 
 		await this._sequelize.authenticate();
 
+		const setupAssociationFunctions: Function[] = [];
+
 		await forEachIn(
 			await readdir(
 				path.resolve(__dirname, `./${this.constructor.name}/models`),
@@ -75,7 +135,8 @@ export class DataModule extends BaseModule {
 					default: ModelClass,
 					schema,
 					options = {},
-					setup
+					setup,
+					setupAssociations
 				} = await import(`${modelFile.path}/${modelFile.name}`);
 
 				const tableName = inflection.camelize(
@@ -91,13 +152,25 @@ export class DataModule extends BaseModule {
 
 				if (typeof setup === "function") await setup();
 
+				if (typeof setupAssociations === "function")
+					setupAssociationFunctions.push(setupAssociations);
+
 				await this._loadModelEvents(ModelClass.name);
 
 				await this._loadModelJobs(ModelClass.name);
 			}
 		);
 
-		this._sequelize.sync();
+		setupAssociationFunctions.forEach(setupAssociation => {
+			setupAssociation();
+		});
+
+		await this._sequelize.sync({ force: true });
+
+		// TODO move to a better spot
+		await this._sequelize.query(
+			`CREATE OR REPLACE VIEW "minifiedUsers" AS SELECT _id, username, name, role FROM users`
+		);
 	}
 
 	// /**
@@ -169,7 +242,10 @@ export class DataModule extends BaseModule {
 		if (this.getStatus() !== ModuleStatus.STARTED)
 			throw new Error("Module not started");
 
-		return this._sequelize.model(name) as ModelStatic<ModelType>;
+		// TODO check if we want to do it via singularize&camelize, or another way
+		const camelizedName = inflection.singularize(inflection.camelize(name));
+
+		return this._sequelize.model(camelizedName) as ModelStatic<ModelType>; // This fails - news has not been defined
 	}
 
 	private async _loadModelJobs(modelClassName: string) {
@@ -190,8 +266,12 @@ export class DataModule extends BaseModule {
 				error instanceof Error &&
 				"code" in error &&
 				error.code === "ENOENT"
-			)
+			) {
+				this.log(
+					`Loading ${modelClassName} jobs failed - folder doesn't exist`
+				);
 				return;
+			}
 
 			throw error;
 		}

+ 0 - 1
backend/src/modules/DataModule/DeleteByIdJob.ts

@@ -1,5 +1,4 @@
 import Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 
 export default abstract class DeleteByIdJob extends DataModuleJob {

+ 0 - 1
backend/src/modules/DataModule/DeleteManyByIdJob.ts

@@ -1,5 +1,4 @@
 import Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 
 export default abstract class DeleteManyByIdJob extends DataModuleJob {

+ 0 - 1
backend/src/modules/DataModule/FindByIdJob.ts

@@ -1,5 +1,4 @@
 import Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 
 export default abstract class FindByIdJob extends DataModuleJob {

+ 0 - 1
backend/src/modules/DataModule/FindManyByIdJob.ts

@@ -1,5 +1,4 @@
 import Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 
 export default abstract class FindManyByIdJob extends DataModuleJob {

+ 32 - 22
backend/src/modules/DataModule/GetDataJob.ts

@@ -1,7 +1,6 @@
 import Joi from "joi";
-import DataModule from "../DataModule";
-import DataModuleJob from "./DataModuleJob";
 import { FindOptions, WhereOptions, Op } from "sequelize";
+import DataModuleJob from "./DataModuleJob";
 
 export enum FilterType {
 	REGEX = "regex",
@@ -49,14 +48,24 @@ export default abstract class GetDataJob extends DataModuleJob {
 
 	protected _blacklistedProperties?: string[];
 
-	protected _specialFilters?: Record<string, (query: FindOptions, data: any) => FindOptions>;
+	protected _specialFilters?: Record<
+		string,
+		(query: FindOptions, data: any) => FindOptions
+	>;
 
-	protected _specialProperties?: Record<string, (query: FindOptions) => FindOptions>;
+	protected _specialProperties?: Record<
+		string,
+		(query: FindOptions) => FindOptions
+	>;
 
-	protected _specialQueries?: Record<string, (query: WhereOptions) => WhereOptions>;
+	protected _specialQueries?: Record<
+		string,
+		(query: WhereOptions) => WhereOptions
+	>;
 
 	protected async _execute() {
-		const { page, pageSize, properties, sort, queries, operator } = this._payload;
+		const { page, pageSize, properties, sort, queries, operator } =
+			this._payload;
 
 		let findQuery: FindOptions = {};
 
@@ -65,15 +74,11 @@ export default abstract class GetDataJob extends DataModuleJob {
 			if (
 				queries.some(query =>
 					this._blacklistedProperties!.some(blacklistedProperty =>
-						blacklistedProperty.startsWith(
-							query.filter.property
-						)
+						blacklistedProperty.startsWith(query.filter.property)
 					)
 				)
 			)
-				throw new Error(
-					"Unable to filter by blacklisted property."
-				);
+				throw new Error("Unable to filter by blacklisted property.");
 			if (
 				Object.keys(sort).some(property =>
 					this._blacklistedProperties!.some(blacklistedProperty =>
@@ -155,9 +160,12 @@ export default abstract class GetDataJob extends DataModuleJob {
 				case FilterType.SPECIAL:
 					if (
 						typeof this._specialFilters?.[filter.property] ===
-							"function"
+						"function"
 					) {
-						findQuery = this._specialFilters[filter.property](findQuery, data);
+						findQuery = this._specialFilters[filter.property](
+							findQuery,
+							data
+						);
 						newQuery[property] = { [Op.eq]: true };
 					}
 					break;
@@ -165,9 +173,7 @@ export default abstract class GetDataJob extends DataModuleJob {
 					throw new Error(`Invalid filter type for "${filter}"`);
 			}
 
-			if (
-				typeof this._specialQueries?.[filter.property] === "function"
-			) {
+			if (typeof this._specialQueries?.[filter.property] === "function") {
 				return this._specialQueries[filter.property](newQuery);
 			}
 
@@ -181,10 +187,12 @@ export default abstract class GetDataJob extends DataModuleJob {
 
 		// Adds order stage to query if there is at least one column being sorted, responsible for sorting data
 		if (Object.keys(sort).length > 0)
-			findQuery.order = Object.entries(sort).map(([property, direction]) => [
-				property,
-				direction === "ascending" ? "ASC" : "DESC"
-			]);
+			findQuery.order = Object.entries(sort).map(
+				([property, direction]) => [
+					property,
+					direction === "ascending" ? "ASC" : "DESC"
+				]
+			);
 
 		findQuery.attributes = {
 			include: properties,
@@ -194,7 +202,9 @@ export default abstract class GetDataJob extends DataModuleJob {
 		findQuery.limit = pageSize;
 
 		// Executes the query
-		const { rows, count } = (await this.getModel().findAndCountAll(findQuery));
+		const { rows, count } = await this.getModel().findAndCountAll(
+			findQuery
+		);
 
 		const data = rows.map(model => model.toJSON()); // TODO: Review generally
 

+ 0 - 1
backend/src/modules/DataModule/UpdateByIdJob.ts

@@ -1,5 +1,4 @@
 import Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 
 export default abstract class UpdateByIdJob extends DataModuleJob {

+ 55 - 0
backend/src/modules/DataModule/models/MinifiedUser.ts

@@ -0,0 +1,55 @@
+import {
+	DataTypes,
+	Model,
+	InferAttributes,
+	InferCreationAttributes,
+	CreationOptional
+} from "sequelize";
+import { ObjectIdType } from "@/modules/DataModule";
+import { UserRole } from "./User/UserRole";
+import { schema as userSchema } from "./User";
+
+export class MinifiedUser extends Model<
+	InferAttributes<MinifiedUser>,
+	InferCreationAttributes<MinifiedUser>
+> {
+	declare _id: CreationOptional<ObjectIdType>;
+
+	declare username: string;
+
+	declare name: string;
+
+	declare role: CreationOptional<UserRole>;
+
+	declare createdAt: CreationOptional<Date>;
+
+	declare updatedAt: CreationOptional<Date>;
+}
+
+export const schema = {
+	_id: userSchema._id,
+	username: userSchema.username,
+	name: userSchema.name,
+	role: userSchema.role,
+	_name: {
+		type: DataTypes.VIRTUAL,
+		get() {
+			return `minifiedUsers`;
+		}
+	}
+};
+
+export const options = {
+	timestamps: false
+};
+
+export const setup = async () => {
+	// Session.afterSave(async record => {
+	// });
+	// Session.afterDestroy(async record => {
+	// });
+};
+
+export default MinifiedUser;
+
+// When we get have to do more with TS, check out https://github.com/sequelize/sequelize/issues/3078#issuecomment-1226261914

+ 2 - 1
backend/src/modules/DataModule/models/minifiedUsers/events/MinifiedUserCreatedEvent.ts → backend/src/modules/DataModule/models/MinifiedUser/events/MinifiedUserCreatedEvent.ts

@@ -1,8 +1,9 @@
 import ModelCreatedEvent from "@/modules/DataModule/ModelCreatedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import MinifiedUser from "../../MinifiedUser";
 
 export default abstract class MinifiedUserCreatedEvent extends ModelCreatedEvent {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 	protected static _hasPermission = isAdmin;
 }

+ 2 - 1
backend/src/modules/DataModule/models/minifiedUsers/events/MinifiedUserDeletedEvent.ts → backend/src/modules/DataModule/models/MinifiedUser/events/MinifiedUserDeletedEvent.ts

@@ -1,9 +1,10 @@
 import ModelDeletedEvent from "@/modules/DataModule/ModelDeletedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import doesModelExist from "@/modules/DataModule/permissions/modelPermissions/doesModelExist";
+import MinifiedUser from "../../MinifiedUser";
 
 export default abstract class MinifiedUserDeletedEvent extends ModelDeletedEvent {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 	protected static _hasPermission = isAdmin;
 

+ 2 - 1
backend/src/modules/DataModule/models/minifiedUsers/events/MinifiedUserUpdatedEvent.ts → backend/src/modules/DataModule/models/MinifiedUser/events/MinifiedUserUpdatedEvent.ts

@@ -1,9 +1,10 @@
 import ModelUpdatedEvent from "@/modules/DataModule/ModelUpdatedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import doesModelExist from "@/modules/DataModule/permissions/modelPermissions/doesModelExist";
+import MinifiedUser from "../../MinifiedUser";
 
 export default abstract class MinifiedUserUpdatedEvent extends ModelUpdatedEvent {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 	protected static _hasPermission = isAdmin;
 

+ 2 - 1
backend/src/modules/DataModule/models/minifiedUsers/jobs/FindById.ts → backend/src/modules/DataModule/models/MinifiedUser/jobs/FindById.ts

@@ -1,7 +1,8 @@
 import FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import MinifiedUser from "../../MinifiedUser";
 
 export default class FindById extends FindByIdJob {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 	protected static _hasPermission = true;
 }

+ 2 - 1
backend/src/modules/DataModule/models/minifiedUsers/jobs/FindManyById.ts → backend/src/modules/DataModule/models/MinifiedUser/jobs/FindManyById.ts

@@ -1,7 +1,8 @@
 import FindManyByIdJob from "@/modules/DataModule/FindManyByIdJob";
+import MinifiedUser from "../../MinifiedUser";
 
 export default class FindManyById extends FindManyByIdJob {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 	protected static _hasPermission = true;
 }

+ 0 - 0
backend/src/modules/DataModule/models/minifiedUsers/migrations/170526579600-create-minified-users-view.ts → backend/src/modules/DataModule/models/MinifiedUser/migrations/170526579600-create-minified-users-view.ts


+ 72 - 0
backend/src/modules/DataModule/models/MinifiedUser/schema.ts

@@ -0,0 +1,72 @@
+// import { Model, Schema, SchemaOptions, SchemaTypes } from "mongoose";
+// import { UserSchema } from "../User/schema";
+// import { UserRole } from "../User/UserRole";
+// import { UserAvatarType } from "../User/UserAvatarType";
+// import { UserAvatarColor } from "../User/UserAvatarColor";
+
+// export type MinifiedUserSchema = Pick<
+// 	UserSchema,
+// 	| "_id"
+// 	| "name"
+// 	| "username"
+// 	| "location"
+// 	| "bio"
+// 	| "role"
+// 	| "avatar"
+// 	| "createdAt"
+// 	| "updatedAt"
+// >;
+
+// export type MinifiedUserModel = Model<MinifiedUserSchema>;
+
+// export const schema = new Schema<MinifiedUserSchema, MinifiedUserModel>(
+// 	{
+// 		username: {
+// 			type: SchemaTypes.String,
+// 			required: true
+// 		},
+// 		role: {
+// 			type: SchemaTypes.String,
+// 			enum: Object.values(UserRole),
+// 			required: true
+// 		},
+// 		avatar: {
+// 			type: {
+// 				type: SchemaTypes.String,
+// 				enum: Object.values(UserAvatarType),
+// 				required: true
+// 			},
+// 			url: {
+// 				type: SchemaTypes.String,
+// 				required: false
+// 			},
+// 			color: {
+// 				type: SchemaTypes.String,
+// 				enum: Object.values(UserAvatarColor),
+// 				required: false
+// 			}
+// 		},
+// 		name: {
+// 			type: SchemaTypes.String,
+// 			required: true
+// 		},
+// 		location: {
+// 			type: SchemaTypes.String,
+// 			required: false
+// 		},
+// 		bio: {
+// 			type: SchemaTypes.String,
+// 			required: false
+// 		}
+// 	},
+// 	{
+// 		autoCreate: false,
+// 		autoIndex: false,
+// 		collection: "minifiedUsers",
+// 		patchHistory: { enabled: false }
+// 	}
+// );
+
+// export type UserSchemaType = typeof schema;
+
+// export type UserSchemaOptions = SchemaOptions<UserSchema>;

+ 78 - 31
backend/src/modules/DataModule/models/News.ts

@@ -1,19 +1,20 @@
 import {
-	Sequelize,
 	DataTypes,
 	Model,
 	InferAttributes,
 	InferCreationAttributes,
-	CreationOptional,
+	CreationOptional
 } from "sequelize";
 import { NewsStatus } from "@models/News/NewsStatus";
 import EventsModule from "@/modules/EventsModule";
+import User from "./User";
+import { ObjectIdType } from "@/modules/DataModule";
 
 export class News extends Model<
 	InferAttributes<News>,
 	InferCreationAttributes<News>
 > {
-	declare id: CreationOptional<number>;
+	declare _id: CreationOptional<ObjectIdType>;
 
 	declare title: string;
 
@@ -29,10 +30,11 @@ export class News extends Model<
 }
 
 export const schema = {
-	id: {
-		type: DataTypes.BIGINT,
-		autoIncrement: true,
-		primaryKey: true
+	_id: {
+		type: DataTypes.OBJECTID,
+		allowNull: false,
+		primaryKey: true,
+		defaultValue: () => "66d6d2d2065de4fd650278be" // TODO add ObjectId generator
 	},
 	title: {
 		type: DataTypes.STRING,
@@ -52,12 +54,15 @@ export const schema = {
 		defaultValue: false,
 		allowNull: false
 	},
-	createdBy: {
-		type: DataTypes.BIGINT,
-		allowNull: false
-	},
 	createdAt: DataTypes.DATE,
-	updatedAt: DataTypes.DATE
+	updatedAt: DataTypes.DATE,
+
+	_name: {
+		type: DataTypes.VIRTUAL,
+		get() {
+			return `news`;
+		}
+	}
 };
 
 export const options = {};
@@ -66,38 +71,80 @@ export const setup = async () => {
 	News.afterSave(async record => {
 		const oldDoc = record.previous();
 		const doc = record.get();
-	
+
 		if (oldDoc.status === doc.status) return;
-	
+
 		if (doc.status === NewsStatus.PUBLISHED) {
-			const EventClass = EventsModule.getEvent(
-				`data.news.published`
+			const EventClass = EventsModule.getEvent(`data.news.published`);
+			await EventsModule.publish(
+				new EventClass({
+					doc
+				})
 			);
-			await EventsModule.publish(new EventClass({
-				doc
-			}));
 		} else if (oldDoc.status === NewsStatus.PUBLISHED) {
-			const EventClass = EventsModule.getEvent(
-				`data.news.unpublished`
+			const EventClass = EventsModule.getEvent(`data.news.unpublished`);
+			await EventsModule.publish(
+				new EventClass(
+					{
+						oldDoc
+					},
+					oldDoc._id!.toString()
+				)
 			);
-			await EventsModule.publish(new EventClass({
-				oldDoc
-			}, oldDoc.id!.toString()));
 		}
 	});
-	
+
 	News.afterDestroy(async record => {
 		const oldDoc = record.previous();
-	
+
 		if (oldDoc.status === NewsStatus.PUBLISHED) {
-			const EventClass = EventsModule.getEvent(
-				`data.news.unpublished`
+			const EventClass = EventsModule.getEvent(`data.news.unpublished`);
+			await EventsModule.publish(
+				new EventClass(
+					{
+						oldDoc
+					},
+					oldDoc._id!.toString()
+				)
 			);
-			await EventsModule.publish(new EventClass({
-				oldDoc
-			}, oldDoc.id!.toString()));
 		}
 	});
+
+	News.addHook("afterFind", (_news, options) => {
+		if (!_news) return;
+
+		// TODO improve TS
+		let news: Model<
+			InferAttributes<
+				News,
+				{
+					omit: never;
+				}
+			>,
+			InferCreationAttributes<
+				News,
+				{
+					omit: never;
+				}
+			>
+		>[] = [];
+
+		if (Array.isArray(_news)) news = _news;
+		// @ts-ignore - possibly not needed after TS update
+		else news.push(_news);
+
+		news.forEach(news => {
+			news.dataValues.createdBy = {
+				_id: news.dataValues.createdBy,
+				_name: "minifiedUsers"
+			};
+		});
+	});
+};
+
+export const setupAssociations = () => {
+	News.belongsTo(User, { foreignKey: "createdBy" });
+	User.hasMany(News, { foreignKey: "createdBy" });
 };
 
 export default News;

+ 9 - 3
backend/src/modules/DataModule/models/News/jobs/GetData.ts

@@ -1,18 +1,24 @@
 import News from "@models/News";
+import { FindOptions, WhereOptions, Op } from "sequelize";
 import GetDataJob from "@/modules/DataModule/GetDataJob";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
-import { FindOptions, WhereOptions, Op } from "sequelize";
 
 export default class GetData extends GetDataJob {
 	protected static _model = News;
 
 	protected static _hasPermission = isAdmin;
 
-	protected _specialProperties?: Record<string, (query: FindOptions<News>) => FindOptions<News>> = {
+	protected _specialProperties?: Record<
+		string,
+		(query: FindOptions<News>) => FindOptions<News>
+	> = {
 		createdBy: query => query
 	};
 
-	protected _specialQueries?: Record<string, (where: WhereOptions<News>) => WhereOptions<News>> = {
+	protected _specialQueries?: Record<
+		string,
+		(where: WhereOptions<News>) => WhereOptions<News>
+	> = {
 		createdBy: where => ({
 			...where,
 			[Op.or]: [

+ 1 - 1
backend/src/modules/DataModule/models/News/jobs/Newest.ts

@@ -15,7 +15,7 @@ export default class Newest extends DataModuleJob {
 
 	protected async _execute() {
 		return this.getModel().findAll({
-			order: [["created_at", "DESC"]],
+			order: [["createdAt", "DESC"]],
 			limit: this._payload?.limit,
 			where: {
 				status: NewsStatus.PUBLISHED

+ 52 - 0
backend/src/modules/DataModule/models/Session.ts

@@ -0,0 +1,52 @@
+import {
+	DataTypes,
+	Model,
+	InferAttributes,
+	InferCreationAttributes,
+	CreationOptional
+} from "sequelize";
+import { ObjectIdType } from "@/modules/DataModule";
+
+export class Session extends Model<
+	InferAttributes<Session>,
+	InferCreationAttributes<Session>
+> {
+	declare sessionId: ObjectIdType;
+
+	declare userId: number;
+
+	declare createdAt: CreationOptional<Date>;
+
+	declare updatedAt: CreationOptional<Date>;
+}
+
+export const schema = {
+	sessionId: {
+		type: DataTypes.OBJECTID,
+		allowNull: false,
+		primaryKey: true
+	},
+	userId: {
+		type: DataTypes.OBJECTID,
+		allowNull: false
+	},
+	createdAt: DataTypes.DATE,
+	updatedAt: DataTypes.DATE,
+	_name: {
+		type: DataTypes.VIRTUAL,
+		get() {
+			return `session`;
+		}
+	}
+};
+
+export const options = {};
+
+export const setup = async () => {
+	// Session.afterSave(async record => {
+	// });
+	// Session.afterDestroy(async record => {
+	// });
+};
+
+export default Session;

+ 2 - 1
backend/src/modules/DataModule/models/sessions/events/SessionCreatedEvent.ts → backend/src/modules/DataModule/models/Session/events/SessionCreatedEvent.ts

@@ -1,5 +1,6 @@
 import ModelCreatedEvent from "@/modules/DataModule/ModelCreatedEvent";
+import Session from "../../Session";
 
 export default abstract class SessionCreatedEvent extends ModelCreatedEvent {
-	protected static _modelName = "sessions";
+	protected static _model = Session;
 }

+ 2 - 1
backend/src/modules/DataModule/models/sessions/events/SessionDeletedEvent.ts → backend/src/modules/DataModule/models/Session/events/SessionDeletedEvent.ts

@@ -1,5 +1,6 @@
 import ModelDeletedEvent from "@/modules/DataModule/ModelDeletedEvent";
+import Session from "../../Session";
 
 export default abstract class SessionDeletedEvent extends ModelDeletedEvent {
-	protected static _modelName = "sessions";
+	protected static _model = Session;
 }

+ 2 - 1
backend/src/modules/DataModule/models/sessions/events/SessionUpdatedEvent.ts → backend/src/modules/DataModule/models/Session/events/SessionUpdatedEvent.ts

@@ -1,5 +1,6 @@
 import ModelUpdatedEvent from "@/modules/DataModule/ModelUpdatedEvent";
+import Session from "../../Session";
 
 export default abstract class SessionUpdatedEvent extends ModelUpdatedEvent {
-	protected static _modelName = "sessions";
+	protected static _model = Session;
 }

+ 2 - 1
backend/src/modules/DataModule/models/users/jobs/Create.ts → backend/src/modules/DataModule/models/Session/jobs/Create.ts

@@ -1,5 +1,6 @@
 import CreateJob from "@/modules/DataModule/CreateJob";
+import Session from "../../Session";
 
 export default class Create extends CreateJob {
-	protected static _modelName = "users";
+	protected static _model = Session;
 }

+ 2 - 1
backend/src/modules/DataModule/models/sessions/jobs/DeleteById.ts → backend/src/modules/DataModule/models/Session/jobs/DeleteById.ts

@@ -1,5 +1,6 @@
 import DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import Session from "../../Session";
 
 export default class DeleteById extends DeleteByIdJob {
-	protected static _modelName = "sessions";
+	protected static _model = Session;
 }

+ 2 - 1
backend/src/modules/DataModule/models/sessions/jobs/FindById.ts → backend/src/modules/DataModule/models/Session/jobs/FindById.ts

@@ -1,5 +1,6 @@
 import FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import Session from "../../Session";
 
 export default class FindById extends FindByIdJob {
-	protected static _modelName = "sessions";
+	protected static _model = Session;
 }

+ 2 - 1
backend/src/modules/DataModule/models/users/jobs/FindManyById.ts → backend/src/modules/DataModule/models/Session/jobs/FindManyById.ts

@@ -1,5 +1,6 @@
 import FindManyByIdJob from "@/modules/DataModule/FindManyByIdJob";
+import Session from "../../Session";
 
 export default class FindManyById extends FindManyByIdJob {
-	protected static _modelName = "users";
+	protected static _model = Session;
 }

+ 2 - 1
backend/src/modules/DataModule/models/users/jobs/UpdateById.ts → backend/src/modules/DataModule/models/Session/jobs/UpdateById.ts

@@ -1,5 +1,6 @@
 import UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import Session from "../../Session";
 
 export default class UpdateById extends UpdateByIdJob {
-	protected static _modelName = "users";
+	protected static _model = Session;
 }

+ 25 - 0
backend/src/modules/DataModule/models/Session/schema.ts

@@ -0,0 +1,25 @@
+// import { Model, Schema, SchemaTypes, Types } from "mongoose";
+// import { BaseSchema } from "@/modules/DataModule/types/Schemas";
+
+// export interface SessionSchema extends BaseSchema {
+// 	userId: Types.ObjectId;
+// }
+
+// export type SessionModel = Model<SessionSchema>;
+
+// export const schema = new Schema<SessionSchema, SessionModel>(
+// 	{
+// 		userId: {
+// 			type: SchemaTypes.ObjectId,
+// 			ref: "users",
+// 			required: true
+// 		}
+// 	},
+// 	{
+// 		patchHistory: {
+// 			enabled: false
+// 		}
+// 	}
+// );
+
+// export type SessionSchemaType = typeof schema;

+ 74 - 0
backend/src/modules/DataModule/models/User.ts

@@ -0,0 +1,74 @@
+import {
+	DataTypes,
+	Model,
+	InferAttributes,
+	InferCreationAttributes,
+	CreationOptional
+} from "sequelize";
+import { UserRole } from "./User/UserRole";
+import { ObjectIdType } from "@/modules/DataModule";
+
+export class User extends Model<
+	InferAttributes<User>,
+	InferCreationAttributes<User>
+> {
+	declare _id: CreationOptional<ObjectIdType>;
+
+	declare username: string;
+
+	declare name: string;
+
+	declare role: CreationOptional<UserRole>;
+
+	declare createdAt: CreationOptional<Date>;
+
+	declare updatedAt: CreationOptional<Date>;
+}
+
+export const schema = {
+	_id: {
+		type: DataTypes.OBJECTID,
+		primaryKey: true,
+		allowNull: false,
+		defaultValue: () => "66d6d2d2065de4fd650278be"
+	},
+	username: {
+		type: DataTypes.STRING,
+		allowNull: false
+	},
+	name: {
+		type: DataTypes.STRING,
+		allowNull: false
+	},
+	role: {
+		type: DataTypes.ENUM(...Object.values(UserRole)),
+		defaultValue: UserRole.USER,
+		allowNull: false
+	},
+	// createdBy: {
+	// 	type: DataTypes.OBJECTID,
+	// 	allowNull: false
+	// },
+	createdAt: DataTypes.DATE,
+	updatedAt: DataTypes.DATE,
+	_name: {
+		type: DataTypes.VIRTUAL,
+		get() {
+			return `users`;
+		}
+	}
+};
+
+export const options = {};
+
+export const setup = async () => {
+	// User.afterSave(async record => {});
+
+	// User.afterDestroy(async record => {});
+
+	User.addHook("afterFind", (user, options) => {
+		console.log("AFTER FIND USER", user, options);
+	});
+};
+
+export default User;

+ 0 - 0
backend/src/modules/DataModule/models/users/UserAvatarColor.ts → backend/src/modules/DataModule/models/User/UserAvatarColor.ts


+ 0 - 0
backend/src/modules/DataModule/models/users/UserAvatarType.ts → backend/src/modules/DataModule/models/User/UserAvatarType.ts


+ 0 - 0
backend/src/modules/DataModule/models/users/UserRole.ts → backend/src/modules/DataModule/models/User/UserRole.ts


+ 0 - 0
backend/src/modules/DataModule/models/users/config.ts → backend/src/modules/DataModule/models/User/config.ts


+ 2 - 1
backend/src/modules/DataModule/models/users/events/UserCreatedEvent.ts → backend/src/modules/DataModule/models/User/events/UserCreatedEvent.ts

@@ -1,8 +1,9 @@
 import ModelCreatedEvent from "@/modules/DataModule/ModelCreatedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import User from "../../User";
 
 export default abstract class UserCreatedEvent extends ModelCreatedEvent {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 	protected static _hasPermission = isAdmin;
 }

+ 2 - 1
backend/src/modules/DataModule/models/users/events/UserDeletedEvent.ts → backend/src/modules/DataModule/models/User/events/UserDeletedEvent.ts

@@ -1,8 +1,9 @@
 import ModelDeletedEvent from "@/modules/DataModule/ModelDeletedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import User from "../../User";
 
 export default abstract class UserDeletedEvent extends ModelDeletedEvent {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 	protected static _hasPermission = isAdmin;
 }

+ 2 - 1
backend/src/modules/DataModule/models/users/events/UserUpdatedEvent.ts → backend/src/modules/DataModule/models/User/events/UserUpdatedEvent.ts

@@ -1,8 +1,9 @@
 import ModelUpdatedEvent from "@/modules/DataModule/ModelUpdatedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import User from "../../User";
 
 export default abstract class UserUpdatedEvent extends ModelUpdatedEvent {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 	protected static _hasPermission = isAdmin;
 	// TODO maybe allow this for the current logged in user?

+ 0 - 0
backend/src/modules/DataModule/models/users/getData.ts → backend/src/modules/DataModule/models/User/getData.ts


+ 2 - 1
backend/src/modules/DataModule/models/sessions/jobs/Create.ts → backend/src/modules/DataModule/models/User/jobs/Create.ts

@@ -1,5 +1,6 @@
 import CreateJob from "@/modules/DataModule/CreateJob";
+import User from "../../User";
 
 export default class Create extends CreateJob {
-	protected static _modelName = "sessions";
+	protected static _model = User;
 }

+ 2 - 1
backend/src/modules/DataModule/models/users/jobs/DeleteById.ts → backend/src/modules/DataModule/models/User/jobs/DeleteById.ts

@@ -1,5 +1,6 @@
 import DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import User from "../../User";
 
 export default class DeleteById extends DeleteByIdJob {
-	protected static _modelName = "users";
+	protected static _model = User;
 }

+ 2 - 1
backend/src/modules/DataModule/models/users/jobs/DeleteManyById.ts → backend/src/modules/DataModule/models/User/jobs/DeleteManyById.ts

@@ -1,5 +1,6 @@
 import DeleteManyByIdJob from "@/modules/DataModule/DeleteManyByIdJob";
+import User from "../../User";
 
 export default class DeleteManyById extends DeleteManyByIdJob {
-	protected static _modelName = "users";
+	protected static _model = User;
 }

+ 12 - 0
backend/src/modules/DataModule/models/User/jobs/FindById.ts

@@ -0,0 +1,12 @@
+import FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import User from "../../User";
+
+export default class FindById extends FindByIdJob {
+	protected static _model = User;
+
+	protected static _hasModelPermission = this._isSelf;
+
+	protected static _isSelf(model: User, user?: User) {
+		return model._id === user?._id;
+	}
+}

+ 2 - 1
backend/src/modules/DataModule/models/sessions/jobs/FindManyById.ts → backend/src/modules/DataModule/models/User/jobs/FindManyById.ts

@@ -1,5 +1,6 @@
 import FindManyByIdJob from "@/modules/DataModule/FindManyByIdJob";
+import User from "../../User";
 
 export default class FindManyById extends FindManyByIdJob {
-	protected static _modelName = "sessions";
+	protected static _model = User;
 }

+ 2 - 1
backend/src/modules/DataModule/models/users/jobs/GetData.ts → backend/src/modules/DataModule/models/User/jobs/GetData.ts

@@ -1,8 +1,9 @@
 import GetDataJob from "@/modules/DataModule/GetDataJob";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import User from "../../User";
 
 export default class GetData extends GetDataJob {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 	protected static _hasPermission = isAdmin;
 }

+ 7 - 3
backend/src/modules/DataModule/models/users/jobs/GetModelPermissions.ts → backend/src/modules/DataModule/models/User/jobs/GetModelPermissions.ts

@@ -6,6 +6,7 @@ import GetPermissions, { GetPermissionsResult } from "./GetPermissions";
 import DataModuleJob from "@/modules/DataModule/DataModuleJob";
 import DataModuleEvent from "@/modules/DataModule/DataModuleEvent";
 import { EventClass } from "@/modules/EventsModule/Event";
+import User from "../../User";
 
 export type GetSingleModelPermissionsResult = Record<string, boolean>; // Returned when getting permissions for a single modelId
 export type GetMultipleModelPermissionsResult = Record<
@@ -25,7 +26,7 @@ export type GetModelPermissionsResult =
  * If no modelId is provided, it will not include jobs that apply specifically to a single modelId (those ending in ById)
  */
 export default class GetModelPermissions extends DataModuleJob {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 	protected static _hasPermission = true;
 
@@ -78,7 +79,7 @@ export default class GetModelPermissions extends DataModuleJob {
 			const cached = await CacheModule.get(cacheKey);
 			if (cached) return cached;
 
-			const model = await Model.findById(modelId);
+			const model = await Model.findByPk(modelId);
 			if (!model) throw new Error("Model not found");
 
 			const modelPermissions = await this._getPermissionsForModel(
@@ -108,8 +109,11 @@ export default class GetModelPermissions extends DataModuleJob {
 			uncachedModelIds.push(modelId);
 		});
 
+		console.log("GMP", uncachedModelIds);
 		// For the modelIds that were not cached, get the documents from MongoDB
-		const uncachedModels = await Model.find({ _id: uncachedModelIds });
+		const uncachedModels = await Model.findAll({
+			where: { _id: uncachedModelIds }
+		});
 
 		// Loop through the modelIds that were not cached, and get the permissions for each one individually
 		await forEachIn(uncachedModelIds, async modelId => {

+ 4 - 5
backend/src/modules/DataModule/models/users/jobs/GetPermissions.ts → backend/src/modules/DataModule/models/User/jobs/GetPermissions.ts

@@ -1,12 +1,11 @@
-import { HydratedDocument } from "mongoose";
 import { forEachIn } from "@common/utils/forEachIn";
 import CacheModule from "@/modules/CacheModule";
 import DataModuleJob from "@/modules/DataModule/DataModuleJob";
-import { UserSchema } from "../schema";
 import ModuleManager from "@/ModuleManager";
 import Job from "@/Job";
 import Event from "@/modules/EventsModule/Event";
 import { UserRole } from "../UserRole";
+import User from "../../User";
 
 export type GetPermissionsResult = Record<string, boolean>;
 
@@ -15,7 +14,7 @@ export type GetPermissionsResult = Record<string, boolean>;
  * Permissions are cached. No cache invalidation machanism has been implemented yet, but it expires naturally after 6 minutes.
  */
 export default class GetPermissions extends DataModuleJob {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 	protected static _hasPermission = true;
 
@@ -38,7 +37,7 @@ export default class GetPermissions extends DataModuleJob {
 		return permissions;
 	}
 
-	protected async _getPermissions(user: HydratedDocument<UserSchema> | null) {
+	protected async _getPermissions(user: User | null) {
 		const jobs = this._getAllJobs();
 		const events = this._getAllEvents();
 
@@ -68,7 +67,7 @@ export default class GetPermissions extends DataModuleJob {
 	}
 
 	protected _getFrontendViewPermissions(
-		user: HydratedDocument<UserSchema> | null
+		user: User | null
 	): Record<string, boolean> {
 		if (!user) return {};
 

+ 2 - 1
backend/src/modules/DataModule/models/sessions/jobs/UpdateById.ts → backend/src/modules/DataModule/models/User/jobs/UpdateById.ts

@@ -1,5 +1,6 @@
 import UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import User from "../../User";
 
 export default class UpdateById extends UpdateByIdJob {
-	protected static _modelName = "sessions";
+	protected static _model = User;
 }

+ 227 - 0
backend/src/modules/DataModule/models/User/schema.ts

@@ -0,0 +1,227 @@
+// import { Model, Schema, SchemaOptions, SchemaTypes, Types } from "mongoose";
+// import { BaseSchema } from "@/modules/DataModule/types/Schemas";
+// import config from "./config";
+// import { UserRole } from "./UserRole";
+// import { UserAvatarType } from "./UserAvatarType";
+// import { UserAvatarColor } from "./UserAvatarColor";
+
+// export interface UserSchema extends BaseSchema {
+// 	username: string;
+// 	role: UserRole;
+// 	email: {
+// 		address: string;
+// 		verified: boolean;
+// 		verificationToken?: string;
+// 	};
+// 	avatar: {
+// 		type: UserAvatarType;
+// 		url?: string;
+// 		color?: UserAvatarColor;
+// 	};
+// 	services: {
+// 		password?: {
+// 			password: string;
+// 			reset: {
+// 				code: string;
+// 				expires: number;
+// 			};
+// 			set: {
+// 				code: string;
+// 				expires: number;
+// 			};
+// 		};
+// 		github?: {
+// 			id: number;
+// 			access_token: string;
+// 		};
+// 	};
+// 	statistics: {
+// 		songsRequested: number;
+// 	};
+// 	likedSongsPlaylist: Types.ObjectId;
+// 	dislikedSongsPlaylist: Types.ObjectId;
+// 	favoriteStations: Types.ObjectId[];
+// 	name: string;
+// 	location?: string;
+// 	bio?: string;
+// 	preferences: {
+// 		orderOfPlaylists: Types.ObjectId[];
+// 		nightmode: boolean;
+// 		autoSkipDisliked: boolean;
+// 		activityLogPublic: boolean;
+// 		anonymousSongRequests: boolean;
+// 		activityWatch: boolean;
+// 	};
+// }
+
+// export type UserModel = Model<UserSchema>;
+
+// export const schema = new Schema<UserSchema, UserModel>(
+// 	{
+// 		username: {
+// 			type: SchemaTypes.String,
+// 			required: true
+// 		},
+// 		role: {
+// 			type: SchemaTypes.String,
+// 			enum: Object.values(UserRole),
+// 			required: true
+// 		},
+// 		email: {
+// 			address: {
+// 				type: SchemaTypes.String,
+// 				required: true
+// 			},
+// 			verified: {
+// 				type: SchemaTypes.Boolean,
+// 				default: false,
+// 				required: true
+// 			},
+// 			verificationToken: {
+// 				type: SchemaTypes.String,
+// 				required: false,
+// 				select: false
+// 			}
+// 		},
+// 		avatar: {
+// 			type: {
+// 				type: SchemaTypes.String,
+// 				enum: Object.values(UserAvatarType),
+// 				required: true
+// 			},
+// 			url: {
+// 				type: SchemaTypes.String,
+// 				required: false
+// 			},
+// 			color: {
+// 				type: SchemaTypes.String,
+// 				enum: Object.values(UserAvatarColor),
+// 				required: false
+// 			}
+// 		},
+// 		services: {
+// 			type: {
+// 				password: {
+// 					type: {
+// 						password: {
+// 							type: SchemaTypes.String,
+// 							required: true,
+// 							select: false
+// 						},
+// 						reset: {
+// 							code: {
+// 								type: SchemaTypes.String,
+// 								minLength: 8,
+// 								maxLength: 8,
+// 								required: false,
+// 								select: false
+// 							},
+// 							expires: {
+// 								type: SchemaTypes.Date,
+// 								required: false,
+// 								select: false
+// 							}
+// 						},
+// 						set: {
+// 							code: {
+// 								type: SchemaTypes.String,
+// 								minLength: 8,
+// 								maxLength: 8,
+// 								required: false,
+// 								select: false
+// 							},
+// 							expires: {
+// 								type: SchemaTypes.Date,
+// 								required: false,
+// 								select: false
+// 							}
+// 						}
+// 					},
+// 					required: false
+// 				},
+// 				github: {
+// 					type: {
+// 						id: {
+// 							type: SchemaTypes.Number,
+// 							required: true,
+// 							select: false
+// 						},
+// 						access_token: {
+// 							type: SchemaTypes.String,
+// 							required: true,
+// 							select: false
+// 						}
+// 					},
+// 					required: false
+// 				}
+// 			},
+// 			required: true
+// 		},
+// 		statistics: {
+// 			songsRequested: {
+// 				type: SchemaTypes.Number,
+// 				default: 0,
+// 				required: true
+// 			}
+// 		},
+// 		likedSongsPlaylist: {
+// 			type: SchemaTypes.ObjectId,
+// 			required: true
+// 		},
+// 		dislikedSongsPlaylist: {
+// 			type: SchemaTypes.ObjectId,
+// 			required: true
+// 		},
+// 		favoriteStations: [
+// 			{
+// 				type: SchemaTypes.ObjectId,
+// 				ref: "stations"
+// 			}
+// 		],
+// 		name: {
+// 			type: SchemaTypes.String,
+// 			required: true
+// 		},
+// 		location: {
+// 			type: SchemaTypes.String,
+// 			required: false
+// 		},
+// 		bio: {
+// 			type: SchemaTypes.String,
+// 			required: false
+// 		},
+// 		preferences: {
+// 			orderOfPlaylists: [SchemaTypes.ObjectId],
+// 			nightmode: {
+// 				type: SchemaTypes.Boolean,
+// 				default: false,
+// 				required: true
+// 			},
+// 			autoSkipDisliked: {
+// 				type: SchemaTypes.Boolean,
+// 				default: true,
+// 				required: true
+// 			},
+// 			activityLogPublic: {
+// 				type: SchemaTypes.Boolean,
+// 				default: false,
+// 				required: true
+// 			},
+// 			anonymousSongRequests: {
+// 				type: SchemaTypes.Boolean,
+// 				default: false,
+// 				required: true
+// 			},
+// 			activityWatch: {
+// 				type: SchemaTypes.Boolean,
+// 				default: false,
+// 				required: true
+// 			}
+// 		}
+// 	},
+// 	config
+// );
+
+// export type UserSchemaType = typeof schema;
+
+// export type UserSchemaOptions = SchemaOptions<UserSchema>;

+ 0 - 72
backend/src/modules/DataModule/models/minifiedUsers/schema.ts

@@ -1,72 +0,0 @@
-import { Model, Schema, SchemaOptions, SchemaTypes } from "mongoose";
-import { UserSchema } from "../users/schema";
-import { UserRole } from "../users/UserRole";
-import { UserAvatarType } from "../users/UserAvatarType";
-import { UserAvatarColor } from "../users/UserAvatarColor";
-
-export type MinifiedUserSchema = Pick<
-	UserSchema,
-	| "_id"
-	| "name"
-	| "username"
-	| "location"
-	| "bio"
-	| "role"
-	| "avatar"
-	| "createdAt"
-	| "updatedAt"
->;
-
-export type MinifiedUserModel = Model<MinifiedUserSchema>;
-
-export const schema = new Schema<MinifiedUserSchema, MinifiedUserModel>(
-	{
-		username: {
-			type: SchemaTypes.String,
-			required: true
-		},
-		role: {
-			type: SchemaTypes.String,
-			enum: Object.values(UserRole),
-			required: true
-		},
-		avatar: {
-			type: {
-				type: SchemaTypes.String,
-				enum: Object.values(UserAvatarType),
-				required: true
-			},
-			url: {
-				type: SchemaTypes.String,
-				required: false
-			},
-			color: {
-				type: SchemaTypes.String,
-				enum: Object.values(UserAvatarColor),
-				required: false
-			}
-		},
-		name: {
-			type: SchemaTypes.String,
-			required: true
-		},
-		location: {
-			type: SchemaTypes.String,
-			required: false
-		},
-		bio: {
-			type: SchemaTypes.String,
-			required: false
-		}
-	},
-	{
-		autoCreate: false,
-		autoIndex: false,
-		collection: "minifiedUsers",
-		patchHistory: { enabled: false }
-	}
-);
-
-export type UserSchemaType = typeof schema;
-
-export type UserSchemaOptions = SchemaOptions<UserSchema>;

+ 15 - 0
backend/src/modules/DataModule/models/news/listeners/NewsUpdatedListener.ts

@@ -0,0 +1,15 @@
+// import NewsUpdatedEvent from "../events/NewsUpdatedEvent";
+
+// export default abstract class NewsUpdatedListener {
+// 	protected static _event = NewsUpdatedEvent;
+
+//     public static getName() {
+//         return "NewsUpdatedListener";
+//     }
+
+//     protected async _onEvent() {
+//         console.log("On event");
+//     }
+
+// 	// protected static _hasPermission = isAdmin;
+// }

+ 0 - 25
backend/src/modules/DataModule/models/sessions/schema.ts

@@ -1,25 +0,0 @@
-import { Model, Schema, SchemaTypes, Types } from "mongoose";
-import { BaseSchema } from "@/modules/DataModule/types/Schemas";
-
-export interface SessionSchema extends BaseSchema {
-	userId: Types.ObjectId;
-}
-
-export type SessionModel = Model<SessionSchema>;
-
-export const schema = new Schema<SessionSchema, SessionModel>(
-	{
-		userId: {
-			type: SchemaTypes.ObjectId,
-			ref: "users",
-			required: true
-		}
-	},
-	{
-		patchHistory: {
-			enabled: false
-		}
-	}
-);
-
-export type SessionSchemaType = typeof schema;

+ 0 - 16
backend/src/modules/DataModule/models/users/jobs/FindById.ts

@@ -1,16 +0,0 @@
-import { HydratedDocument } from "mongoose";
-import FindByIdJob from "@/modules/DataModule/FindByIdJob";
-import { UserModel } from "../schema";
-
-export default class FindById extends FindByIdJob {
-	protected static _modelName = "users";
-
-	protected static _hasModelPermission = this._isSelf;
-
-	protected static _isSelf(
-		model: HydratedDocument<UserModel>,
-		user?: HydratedDocument<UserModel>
-	) {
-		return model._id === user?._id;
-	}
-}

+ 0 - 227
backend/src/modules/DataModule/models/users/schema.ts

@@ -1,227 +0,0 @@
-import { Model, Schema, SchemaOptions, SchemaTypes, Types } from "mongoose";
-import { BaseSchema } from "@/modules/DataModule/types/Schemas";
-import config from "./config";
-import { UserRole } from "./UserRole";
-import { UserAvatarType } from "./UserAvatarType";
-import { UserAvatarColor } from "./UserAvatarColor";
-
-export interface UserSchema extends BaseSchema {
-	username: string;
-	role: UserRole;
-	email: {
-		address: string;
-		verified: boolean;
-		verificationToken?: string;
-	};
-	avatar: {
-		type: UserAvatarType;
-		url?: string;
-		color?: UserAvatarColor;
-	};
-	services: {
-		password?: {
-			password: string;
-			reset: {
-				code: string;
-				expires: number;
-			};
-			set: {
-				code: string;
-				expires: number;
-			};
-		};
-		github?: {
-			id: number;
-			access_token: string;
-		};
-	};
-	statistics: {
-		songsRequested: number;
-	};
-	likedSongsPlaylist: Types.ObjectId;
-	dislikedSongsPlaylist: Types.ObjectId;
-	favoriteStations: Types.ObjectId[];
-	name: string;
-	location?: string;
-	bio?: string;
-	preferences: {
-		orderOfPlaylists: Types.ObjectId[];
-		nightmode: boolean;
-		autoSkipDisliked: boolean;
-		activityLogPublic: boolean;
-		anonymousSongRequests: boolean;
-		activityWatch: boolean;
-	};
-}
-
-export type UserModel = Model<UserSchema>;
-
-export const schema = new Schema<UserSchema, UserModel>(
-	{
-		username: {
-			type: SchemaTypes.String,
-			required: true
-		},
-		role: {
-			type: SchemaTypes.String,
-			enum: Object.values(UserRole),
-			required: true
-		},
-		email: {
-			address: {
-				type: SchemaTypes.String,
-				required: true
-			},
-			verified: {
-				type: SchemaTypes.Boolean,
-				default: false,
-				required: true
-			},
-			verificationToken: {
-				type: SchemaTypes.String,
-				required: false,
-				select: false
-			}
-		},
-		avatar: {
-			type: {
-				type: SchemaTypes.String,
-				enum: Object.values(UserAvatarType),
-				required: true
-			},
-			url: {
-				type: SchemaTypes.String,
-				required: false
-			},
-			color: {
-				type: SchemaTypes.String,
-				enum: Object.values(UserAvatarColor),
-				required: false
-			}
-		},
-		services: {
-			type: {
-				password: {
-					type: {
-						password: {
-							type: SchemaTypes.String,
-							required: true,
-							select: false
-						},
-						reset: {
-							code: {
-								type: SchemaTypes.String,
-								minLength: 8,
-								maxLength: 8,
-								required: false,
-								select: false
-							},
-							expires: {
-								type: SchemaTypes.Date,
-								required: false,
-								select: false
-							}
-						},
-						set: {
-							code: {
-								type: SchemaTypes.String,
-								minLength: 8,
-								maxLength: 8,
-								required: false,
-								select: false
-							},
-							expires: {
-								type: SchemaTypes.Date,
-								required: false,
-								select: false
-							}
-						}
-					},
-					required: false
-				},
-				github: {
-					type: {
-						id: {
-							type: SchemaTypes.Number,
-							required: true,
-							select: false
-						},
-						access_token: {
-							type: SchemaTypes.String,
-							required: true,
-							select: false
-						}
-					},
-					required: false
-				}
-			},
-			required: true
-		},
-		statistics: {
-			songsRequested: {
-				type: SchemaTypes.Number,
-				default: 0,
-				required: true
-			}
-		},
-		likedSongsPlaylist: {
-			type: SchemaTypes.ObjectId,
-			required: true
-		},
-		dislikedSongsPlaylist: {
-			type: SchemaTypes.ObjectId,
-			required: true
-		},
-		favoriteStations: [
-			{
-				type: SchemaTypes.ObjectId,
-				ref: "stations"
-			}
-		],
-		name: {
-			type: SchemaTypes.String,
-			required: true
-		},
-		location: {
-			type: SchemaTypes.String,
-			required: false
-		},
-		bio: {
-			type: SchemaTypes.String,
-			required: false
-		},
-		preferences: {
-			orderOfPlaylists: [SchemaTypes.ObjectId],
-			nightmode: {
-				type: SchemaTypes.Boolean,
-				default: false,
-				required: true
-			},
-			autoSkipDisliked: {
-				type: SchemaTypes.Boolean,
-				default: true,
-				required: true
-			},
-			activityLogPublic: {
-				type: SchemaTypes.Boolean,
-				default: false,
-				required: true
-			},
-			anonymousSongRequests: {
-				type: SchemaTypes.Boolean,
-				default: false,
-				required: true
-			},
-			activityWatch: {
-				type: SchemaTypes.Boolean,
-				default: false,
-				required: true
-			}
-		}
-	},
-	config
-);
-
-export type UserSchemaType = typeof schema;
-
-export type UserSchemaOptions = SchemaOptions<UserSchema>;

+ 3 - 5
backend/src/modules/DataModule/permissions/isAdmin.ts

@@ -1,6 +1,4 @@
-import { HydratedDocument } from "mongoose";
-import { UserSchema } from "../models/users/schema";
-import { UserRole } from "../models/users/UserRole";
+import User from "../models/User";
+import { UserRole } from "../models/User/UserRole";
 
-export default (user: HydratedDocument<UserSchema>) =>
-	user && user.role === UserRole.ADMIN;
+export default (user: User) => user && user.role === UserRole.ADMIN;

+ 2 - 3
backend/src/modules/DataModule/permissions/isLoggedIn.ts

@@ -1,4 +1,3 @@
-import { HydratedDocument } from "mongoose";
-import { UserSchema } from "../models/users/schema";
+import User from "../models/User";
 
-export default (user: HydratedDocument<UserSchema>) => user;
+export default (user: User) => user;

+ 3 - 5
backend/src/modules/DataModule/permissions/isModerator.ts

@@ -1,6 +1,4 @@
-import { HydratedDocument } from "mongoose";
-import { UserSchema } from "../models/users/schema";
-import { UserRole } from "../models/users/UserRole";
+import { UserRole } from "../models/User/UserRole";
+import User from "../models/User";
 
-export default (user: HydratedDocument<UserSchema>) =>
-	user && user.role === UserRole.ADMIN;
+export default (user: User) => user && user.role === UserRole.ADMIN;

+ 3 - 5
backend/src/modules/DataModule/permissions/modelPermissions/isDj.ts

@@ -1,8 +1,6 @@
 import { HydratedDocument } from "mongoose";
 import { StationSchema } from "../../models/stations/schema";
-import { UserSchema } from "../../models/users/schema";
+import User from "../../models/User";
 
-export default (
-	model: HydratedDocument<StationSchema>,
-	user?: HydratedDocument<UserSchema>
-) => model && user && model.djs.includes(user._id);
+export default (model: HydratedDocument<StationSchema>, user?: User) =>
+	model && user && model.djs.includes(user._id);

+ 2 - 2
backend/src/modules/DataModule/permissions/modelPermissions/isLoggedIn.ts

@@ -1,7 +1,7 @@
 import { HydratedDocument, Schema } from "mongoose";
-import { UserSchema } from "../../models/users/schema";
+import User from "../../models/User";
 
 export default <ModelSchemaType extends Schema>(
 	model: HydratedDocument<ModelSchemaType>,
-	user?: HydratedDocument<UserSchema>
+	user?: User
 ) => !!user;

+ 1 - 2
backend/src/modules/DataModule/permissions/modelPermissions/isNewsPublished.ts

@@ -4,5 +4,4 @@ import { NewsStatus } from "@models/News/NewsStatus";
 /**
  * Simply used to check if a news model exists and is published
  */
-export default (model?: News) =>
-	model && model.status === NewsStatus.PUBLISHED;
+export default (model?: News) => model && model.status === NewsStatus.PUBLISHED;

+ 2 - 2
backend/src/modules/DataModule/permissions/modelPermissions/isOwner.ts

@@ -1,11 +1,11 @@
 import { HydratedDocument } from "mongoose";
-import { UserSchema } from "../../models/users/schema";
+import User from "../../models/User";
 
 export default (
 	model:
 		| (HydratedDocument<any> & { owner?: any })
 		| (HydratedDocument<any> & { createdBy?: any }),
-	user?: HydratedDocument<UserSchema>
+	user?: User
 ) => {
 	if (!user || !model) return false;
 

+ 6 - 2
backend/src/modules/EventsModule.ts

@@ -14,8 +14,8 @@ import WebSocketModule from "./WebSocketModule";
 import Event from "@/modules/EventsModule/Event";
 import ModuleManager from "@/ModuleManager";
 import DataModule from "@/modules/DataModule";
-import { GetPermissionsResult } from "@/modules/DataModule/models/users/jobs/GetPermissions";
-import { GetSingleModelPermissionsResult } from "@/modules/DataModule/models/users/jobs/GetModelPermissions";
+import { GetPermissionsResult } from "@/modules/DataModule/models/User/jobs/GetPermissions";
+import { GetSingleModelPermissionsResult } from "@/modules/DataModule/models/User/jobs/GetModelPermissions";
 import JobContext from "@/JobContext";
 
 const permissionRegex =
@@ -212,6 +212,10 @@ export class EventsModule extends BaseModule {
 	public async assertPermission(jobContext: JobContext, permission: string) {
 		let hasPermission = false;
 
+		// TODO improve
+		if (!permissionRegex.test(permission))
+			throw new Error("Regex doesn't match");
+
 		const {
 			moduleName,
 			modelOrEventName,

+ 2 - 5
backend/src/modules/EventsModule/Event.ts

@@ -1,5 +1,4 @@
-import { HydratedDocument } from "mongoose";
-import { UserSchema } from "../DataModule/models/users/schema";
+import User from "../DataModule/models/User";
 
 export default abstract class Event {
 	protected static _namespace: string;
@@ -104,9 +103,7 @@ export default abstract class Event {
 		| (boolean | CallableFunction)[] = false;
 
 	// Check if a given user has generic permission to subscribe to an event, using _hasPermission
-	public static async hasPermission(
-		user: HydratedDocument<UserSchema> | null
-	) {
+	public static async hasPermission(user: User | null) {
 		const options = Array.isArray(this._hasPermission)
 			? this._hasPermission
 			: [this._hasPermission];

+ 7 - 7
backend/src/modules/EventsModule/jobs/Subscribe.spec.ts

@@ -2,19 +2,19 @@ import "@/tests/support/setup";
 import sinon from "sinon";
 import mongoose from "mongoose";
 import news from "logic/db/schemas/news";
+import NewsCreatedEvent from "@models/News/events/NewsCreatedEvent";
+import NewsUpdatedEvent from "@models/News/events/NewsUpdatedEvent";
+import NewsDeletedEvent from "@models/News/events/NewsDeletedEvent";
+import { NewsStatus } from "@models/News/NewsStatus";
 import { TestModule } from "@/tests/support/TestModule";
 import Subscribe from "@/modules/EventsModule/jobs/Subscribe";
 import DataModule from "@/modules/DataModule";
 import EventsModule from "@/modules/EventsModule";
-import NewsCreatedEvent from "@models/News/events/NewsCreatedEvent";
-import GetModelPermissions from "@models/users/jobs/GetModelPermissions";
+import GetModelPermissions from "@/modules/DataModule/models/User/jobs/GetModelPermissions";
 import JobContext from "@/JobContext";
-import { UserRole } from "@models/users/UserRole";
-import GetPermissions from "@models/users/jobs/GetPermissions";
+import { UserRole } from "@/modules/DataModule/models/User/UserRole";
+import GetPermissions from "@/modules/DataModule/models/User/jobs/GetPermissions";
 import CacheModule from "@/modules/CacheModule";
-import NewsUpdatedEvent from "@models/News/events/NewsUpdatedEvent";
-import NewsDeletedEvent from "@models/News/events/NewsDeletedEvent";
-import { NewsStatus } from "@models/News/NewsStatus";
 
 describe("Subscribe job", async function () {
 	describe("execute", function () {

+ 26 - 14
backend/src/modules/WebSocketModule.ts

@@ -10,9 +10,9 @@ import WebSocket from "@/WebSocket";
 import ModuleManager from "@/ModuleManager";
 import JobQueue from "@/JobQueue";
 import DataModule from "./DataModule";
-import { UserModel } from "./DataModule/models/users/schema";
-import { SessionModel } from "./DataModule/models/sessions/schema";
 import EventsModule from "./EventsModule";
+import User from "./DataModule/models/User";
+import Session from "./DataModule/models/Session";
 // import assertEventDerived from "@/utils/assertEventDerived";
 
 export class WebSocketModule extends BaseModule {
@@ -132,16 +132,24 @@ export class WebSocketModule extends BaseModule {
 		if (sessionId && isObjectIdOrHexString(sessionId)) {
 			socket.setSessionId(sessionId);
 
-			const Session = await DataModule.getModel("sessions");
+			const Session = await DataModule.getModel<Session>("sessions");
 
-			const session = await Session.findByIdAndUpdate(sessionId, {
-				updatedAt: Date.now()
-			});
+			await Session.update(
+				{
+					updatedAt: new Date()
+				},
+				{
+					where: {
+						sessionId
+					}
+				}
+			);
+			const session = await Session.findByPk(sessionId); // pk = primary key
 
 			if (session) {
-				const User = await DataModule.getModel<UserModel>("users");
+				const User = await DataModule.getModel<User>("users");
 
-				user = await User.findById(session.userId);
+				user = await User.findByPk(session.userId);
 			}
 		}
 
@@ -247,17 +255,21 @@ export class WebSocketModule extends BaseModule {
 
 			let session;
 			if (socket.getSessionId()) {
-				const Session = await DataModule.getModel<SessionModel>(
-					"sessions"
-				);
+				const Session = await DataModule.getModel<Session>("sessions");
 
-				session = await Session.findByIdAndUpdate(
-					socket.getSessionId(),
+				await Session.update(
 					{
-						updatedAt: Date.now()
+						updatedAt: new Date()
+					},
+					{
+						where: {
+							sessionId: socket.getSessionId()
+						}
 					}
 				);
 
+				session = await Session.findByPk(socket.getSessionId());
+
 				if (!session) throw new Error("Session not found.");
 			}