Pārlūkot izejas kodu

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 9 mēneši atpakaļ
vecāks
revīzija
c45a4603b0
66 mainītis faili ar 864 papildinājumiem un 507 dzēšanām
  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 { SessionSchema } from "@models/sessions/schema";
 import { getErrorMessage } from "@common/utils/getErrorMessage";
 import { getErrorMessage } from "@common/utils/getErrorMessage";
 import { generateUuid } from "@common/utils/generateUuid";
 import { generateUuid } from "@common/utils/generateUuid";
-import { HydratedDocument } from "mongoose";
 import Joi from "joi";
 import Joi from "joi";
 import * as inflection from "inflection";
 import * as inflection from "inflection";
 import JobContext from "@/JobContext";
 import JobContext from "@/JobContext";
@@ -10,7 +9,7 @@ import LogBook, { Log } from "@/LogBook";
 import BaseModule from "./BaseModule";
 import BaseModule from "./BaseModule";
 import EventsModule from "./modules/EventsModule";
 import EventsModule from "./modules/EventsModule";
 import JobCompletedEvent from "./modules/EventsModule/events/JobCompletedEvent";
 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 {
 export enum JobStatus {
 	QUEUED = "QUEUED",
 	QUEUED = "QUEUED",
@@ -187,9 +186,7 @@ export default abstract class Job {
 		| (boolean | CallableFunction)[] = false;
 		| (boolean | CallableFunction)[] = false;
 
 
 	// Check if a given user has generic permission to execute a job, using _hasPermission
 	// 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)
 		const options = Array.isArray(this._hasPermission)
 			? this._hasPermission
 			? 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 Job, { JobOptions } from "@/Job";
 import { Log } from "@/LogBook";
 import { Log } from "@/LogBook";
 import DataModule from "@/modules/DataModule";
 import DataModule from "@/modules/DataModule";
-import { UserModel } from "@/modules/DataModule/models/users/schema";
 import { JobDerived } from "./types/JobDerived";
 import { JobDerived } from "./types/JobDerived";
 import assertJobDerived from "./utils/assertJobDerived";
 import assertJobDerived from "./utils/assertJobDerived";
 import {
 import {
 	GetMultipleModelPermissionsResult,
 	GetMultipleModelPermissionsResult,
 	GetSingleModelPermissionsResult
 	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 =
 const permissionRegex =
 	// eslint-disable-next-line max-len
 	// eslint-disable-next-line max-len
@@ -83,9 +83,9 @@ export default class JobContext {
 		if (!this._session?.userId)
 		if (!this._session?.userId)
 			throw new Error("No user found for session");
 			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");
 		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 * as readline from "node:readline";
+import { NewsStatus } from "@models/News/NewsStatus";
 import ModuleManager from "@/ModuleManager";
 import ModuleManager from "@/ModuleManager";
 import LogBook from "@/LogBook";
 import LogBook from "@/LogBook";
 import JobQueue from "@/JobQueue";
 import JobQueue from "@/JobQueue";
@@ -8,7 +9,6 @@ import EventsModule from "./modules/EventsModule";
 // import { NewsModel } from "./modules/DataModule/models/news/schema";
 // import { NewsModel } from "./modules/DataModule/models/news/schema";
 // import { FilterType } from "./modules/DataModule/plugins/getData";
 // import { FilterType } from "./modules/DataModule/plugins/getData";
 import News from "./modules/DataModule/models/News";
 import News from "./modules/DataModule/models/News";
-import { NewsStatus } from "@models/News/NewsStatus";
 import GetData from "./modules/DataModule/models/News/jobs/GetData";
 import GetData from "./modules/DataModule/models/News/jobs/GetData";
 
 
 process.removeAllListeners("uncaughtException");
 process.removeAllListeners("uncaughtException");
@@ -82,16 +82,16 @@ ModuleManager.startup().then(async () => {
 	);
 	);
 
 
 	console.log(
 	console.log(
-		await (new GetData({
+		await new GetData({
 			page: 1,
 			page: 1,
 			pageSize: 10,
 			pageSize: 10,
-			properties: ['id'],
+			properties: ["id"],
 			sort: {
 			sort: {
-				id: 'ascending'
+				id: "ascending"
 			},
 			},
 			queries: [],
 			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 { readdir } from "fs/promises";
 import path from "path";
 import path from "path";
 import { forEachIn } from "@common/utils/forEachIn";
 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 { Dirent } from "fs";
 import * as inflection from "inflection";
 import * as inflection from "inflection";
 import BaseModule, { ModuleStatus } from "@/BaseModule";
 import BaseModule, { ModuleStatus } from "@/BaseModule";
-import EventsModule from "./EventsModule";
 import DataModuleJob from "./DataModule/DataModuleJob";
 import DataModuleJob from "./DataModule/DataModuleJob";
 import Job from "@/Job";
 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 {
 export class DataModule extends BaseModule {
 	private _sequelize?: Sequelize;
 	private _sequelize?: Sequelize;
 
 
@@ -50,7 +107,8 @@ export class DataModule extends BaseModule {
 	 * setupSequelize - Setup sequelize instance
 	 * setupSequelize - Setup sequelize instance
 	 */
 	 */
 	private async _setupSequelize() {
 	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, {
 		this._sequelize = new Sequelize(database, username, password, {
 			host,
 			host,
 			port,
 			port,
@@ -60,6 +118,8 @@ export class DataModule extends BaseModule {
 
 
 		await this._sequelize.authenticate();
 		await this._sequelize.authenticate();
 
 
+		const setupAssociationFunctions: Function[] = [];
+
 		await forEachIn(
 		await forEachIn(
 			await readdir(
 			await readdir(
 				path.resolve(__dirname, `./${this.constructor.name}/models`),
 				path.resolve(__dirname, `./${this.constructor.name}/models`),
@@ -75,7 +135,8 @@ export class DataModule extends BaseModule {
 					default: ModelClass,
 					default: ModelClass,
 					schema,
 					schema,
 					options = {},
 					options = {},
-					setup
+					setup,
+					setupAssociations
 				} = await import(`${modelFile.path}/${modelFile.name}`);
 				} = await import(`${modelFile.path}/${modelFile.name}`);
 
 
 				const tableName = inflection.camelize(
 				const tableName = inflection.camelize(
@@ -91,13 +152,25 @@ export class DataModule extends BaseModule {
 
 
 				if (typeof setup === "function") await setup();
 				if (typeof setup === "function") await setup();
 
 
+				if (typeof setupAssociations === "function")
+					setupAssociationFunctions.push(setupAssociations);
+
 				await this._loadModelEvents(ModelClass.name);
 				await this._loadModelEvents(ModelClass.name);
 
 
 				await this._loadModelJobs(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)
 		if (this.getStatus() !== ModuleStatus.STARTED)
 			throw new Error("Module not 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) {
 	private async _loadModelJobs(modelClassName: string) {
@@ -190,8 +266,12 @@ export class DataModule extends BaseModule {
 				error instanceof Error &&
 				error instanceof Error &&
 				"code" in error &&
 				"code" in error &&
 				error.code === "ENOENT"
 				error.code === "ENOENT"
-			)
+			) {
+				this.log(
+					`Loading ${modelClassName} jobs failed - folder doesn't exist`
+				);
 				return;
 				return;
+			}
 
 
 			throw error;
 			throw error;
 		}
 		}

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

@@ -1,5 +1,4 @@
 import Joi from "joi";
 import Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 import DataModuleJob from "./DataModuleJob";
 
 
 export default abstract class DeleteByIdJob extends 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 Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 import DataModuleJob from "./DataModuleJob";
 
 
 export default abstract class DeleteManyByIdJob extends 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 Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 import DataModuleJob from "./DataModuleJob";
 
 
 export default abstract class FindByIdJob extends 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 Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 import DataModuleJob from "./DataModuleJob";
 
 
 export default abstract class FindManyByIdJob extends 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 Joi from "joi";
-import DataModule from "../DataModule";
-import DataModuleJob from "./DataModuleJob";
 import { FindOptions, WhereOptions, Op } from "sequelize";
 import { FindOptions, WhereOptions, Op } from "sequelize";
+import DataModuleJob from "./DataModuleJob";
 
 
 export enum FilterType {
 export enum FilterType {
 	REGEX = "regex",
 	REGEX = "regex",
@@ -49,14 +48,24 @@ export default abstract class GetDataJob extends DataModuleJob {
 
 
 	protected _blacklistedProperties?: string[];
 	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() {
 	protected async _execute() {
-		const { page, pageSize, properties, sort, queries, operator } = this._payload;
+		const { page, pageSize, properties, sort, queries, operator } =
+			this._payload;
 
 
 		let findQuery: FindOptions = {};
 		let findQuery: FindOptions = {};
 
 
@@ -65,15 +74,11 @@ export default abstract class GetDataJob extends DataModuleJob {
 			if (
 			if (
 				queries.some(query =>
 				queries.some(query =>
 					this._blacklistedProperties!.some(blacklistedProperty =>
 					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 (
 			if (
 				Object.keys(sort).some(property =>
 				Object.keys(sort).some(property =>
 					this._blacklistedProperties!.some(blacklistedProperty =>
 					this._blacklistedProperties!.some(blacklistedProperty =>
@@ -155,9 +160,12 @@ export default abstract class GetDataJob extends DataModuleJob {
 				case FilterType.SPECIAL:
 				case FilterType.SPECIAL:
 					if (
 					if (
 						typeof this._specialFilters?.[filter.property] ===
 						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 };
 						newQuery[property] = { [Op.eq]: true };
 					}
 					}
 					break;
 					break;
@@ -165,9 +173,7 @@ export default abstract class GetDataJob extends DataModuleJob {
 					throw new Error(`Invalid filter type for "${filter}"`);
 					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);
 				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
 		// Adds order stage to query if there is at least one column being sorted, responsible for sorting data
 		if (Object.keys(sort).length > 0)
 		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 = {
 		findQuery.attributes = {
 			include: properties,
 			include: properties,
@@ -194,7 +202,9 @@ export default abstract class GetDataJob extends DataModuleJob {
 		findQuery.limit = pageSize;
 		findQuery.limit = pageSize;
 
 
 		// Executes the query
 		// 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
 		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 Joi from "joi";
-import DataModule from "../DataModule";
 import DataModuleJob from "./DataModuleJob";
 import DataModuleJob from "./DataModuleJob";
 
 
 export default abstract class UpdateByIdJob extends 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 ModelCreatedEvent from "@/modules/DataModule/ModelCreatedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import MinifiedUser from "../../MinifiedUser";
 
 
 export default abstract class MinifiedUserCreatedEvent extends ModelCreatedEvent {
 export default abstract class MinifiedUserCreatedEvent extends ModelCreatedEvent {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 
 	protected static _hasPermission = isAdmin;
 	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 ModelDeletedEvent from "@/modules/DataModule/ModelDeletedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import doesModelExist from "@/modules/DataModule/permissions/modelPermissions/doesModelExist";
 import doesModelExist from "@/modules/DataModule/permissions/modelPermissions/doesModelExist";
+import MinifiedUser from "../../MinifiedUser";
 
 
 export default abstract class MinifiedUserDeletedEvent extends ModelDeletedEvent {
 export default abstract class MinifiedUserDeletedEvent extends ModelDeletedEvent {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 
 	protected static _hasPermission = isAdmin;
 	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 ModelUpdatedEvent from "@/modules/DataModule/ModelUpdatedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import doesModelExist from "@/modules/DataModule/permissions/modelPermissions/doesModelExist";
 import doesModelExist from "@/modules/DataModule/permissions/modelPermissions/doesModelExist";
+import MinifiedUser from "../../MinifiedUser";
 
 
 export default abstract class MinifiedUserUpdatedEvent extends ModelUpdatedEvent {
 export default abstract class MinifiedUserUpdatedEvent extends ModelUpdatedEvent {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 
 	protected static _hasPermission = isAdmin;
 	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 FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import MinifiedUser from "../../MinifiedUser";
 
 
 export default class FindById extends FindByIdJob {
 export default class FindById extends FindByIdJob {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 
 	protected static _hasPermission = true;
 	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 FindManyByIdJob from "@/modules/DataModule/FindManyByIdJob";
+import MinifiedUser from "../../MinifiedUser";
 
 
 export default class FindManyById extends FindManyByIdJob {
 export default class FindManyById extends FindManyByIdJob {
-	protected static _modelName = "minifiedUsers";
+	protected static _model = MinifiedUser;
 
 
 	protected static _hasPermission = true;
 	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 {
 import {
-	Sequelize,
 	DataTypes,
 	DataTypes,
 	Model,
 	Model,
 	InferAttributes,
 	InferAttributes,
 	InferCreationAttributes,
 	InferCreationAttributes,
-	CreationOptional,
+	CreationOptional
 } from "sequelize";
 } from "sequelize";
 import { NewsStatus } from "@models/News/NewsStatus";
 import { NewsStatus } from "@models/News/NewsStatus";
 import EventsModule from "@/modules/EventsModule";
 import EventsModule from "@/modules/EventsModule";
+import User from "./User";
+import { ObjectIdType } from "@/modules/DataModule";
 
 
 export class News extends Model<
 export class News extends Model<
 	InferAttributes<News>,
 	InferAttributes<News>,
 	InferCreationAttributes<News>
 	InferCreationAttributes<News>
 > {
 > {
-	declare id: CreationOptional<number>;
+	declare _id: CreationOptional<ObjectIdType>;
 
 
 	declare title: string;
 	declare title: string;
 
 
@@ -29,10 +30,11 @@ export class News extends Model<
 }
 }
 
 
 export const schema = {
 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: {
 	title: {
 		type: DataTypes.STRING,
 		type: DataTypes.STRING,
@@ -52,12 +54,15 @@ export const schema = {
 		defaultValue: false,
 		defaultValue: false,
 		allowNull: false
 		allowNull: false
 	},
 	},
-	createdBy: {
-		type: DataTypes.BIGINT,
-		allowNull: false
-	},
 	createdAt: DataTypes.DATE,
 	createdAt: DataTypes.DATE,
-	updatedAt: DataTypes.DATE
+	updatedAt: DataTypes.DATE,
+
+	_name: {
+		type: DataTypes.VIRTUAL,
+		get() {
+			return `news`;
+		}
+	}
 };
 };
 
 
 export const options = {};
 export const options = {};
@@ -66,38 +71,80 @@ export const setup = async () => {
 	News.afterSave(async record => {
 	News.afterSave(async record => {
 		const oldDoc = record.previous();
 		const oldDoc = record.previous();
 		const doc = record.get();
 		const doc = record.get();
-	
+
 		if (oldDoc.status === doc.status) return;
 		if (oldDoc.status === doc.status) return;
-	
+
 		if (doc.status === NewsStatus.PUBLISHED) {
 		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) {
 		} 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 => {
 	News.afterDestroy(async record => {
 		const oldDoc = record.previous();
 		const oldDoc = record.previous();
-	
+
 		if (oldDoc.status === NewsStatus.PUBLISHED) {
 		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;
 export default News;

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

@@ -1,18 +1,24 @@
 import News from "@models/News";
 import News from "@models/News";
+import { FindOptions, WhereOptions, Op } from "sequelize";
 import GetDataJob from "@/modules/DataModule/GetDataJob";
 import GetDataJob from "@/modules/DataModule/GetDataJob";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
-import { FindOptions, WhereOptions, Op } from "sequelize";
 
 
 export default class GetData extends GetDataJob {
 export default class GetData extends GetDataJob {
 	protected static _model = News;
 	protected static _model = News;
 
 
 	protected static _hasPermission = isAdmin;
 	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
 		createdBy: query => query
 	};
 	};
 
 
-	protected _specialQueries?: Record<string, (where: WhereOptions<News>) => WhereOptions<News>> = {
+	protected _specialQueries?: Record<
+		string,
+		(where: WhereOptions<News>) => WhereOptions<News>
+	> = {
 		createdBy: where => ({
 		createdBy: where => ({
 			...where,
 			...where,
 			[Op.or]: [
 			[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() {
 	protected async _execute() {
 		return this.getModel().findAll({
 		return this.getModel().findAll({
-			order: [["created_at", "DESC"]],
+			order: [["createdAt", "DESC"]],
 			limit: this._payload?.limit,
 			limit: this._payload?.limit,
 			where: {
 			where: {
 				status: NewsStatus.PUBLISHED
 				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 ModelCreatedEvent from "@/modules/DataModule/ModelCreatedEvent";
+import Session from "../../Session";
 
 
 export default abstract class SessionCreatedEvent extends ModelCreatedEvent {
 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 ModelDeletedEvent from "@/modules/DataModule/ModelDeletedEvent";
+import Session from "../../Session";
 
 
 export default abstract class SessionDeletedEvent extends ModelDeletedEvent {
 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 ModelUpdatedEvent from "@/modules/DataModule/ModelUpdatedEvent";
+import Session from "../../Session";
 
 
 export default abstract class SessionUpdatedEvent extends ModelUpdatedEvent {
 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 CreateJob from "@/modules/DataModule/CreateJob";
+import Session from "../../Session";
 
 
 export default class Create extends CreateJob {
 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 DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import Session from "../../Session";
 
 
 export default class DeleteById extends DeleteByIdJob {
 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 FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import Session from "../../Session";
 
 
 export default class FindById extends FindByIdJob {
 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 FindManyByIdJob from "@/modules/DataModule/FindManyByIdJob";
+import Session from "../../Session";
 
 
 export default class FindManyById extends FindManyByIdJob {
 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 UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import Session from "../../Session";
 
 
 export default class UpdateById extends UpdateByIdJob {
 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 ModelCreatedEvent from "@/modules/DataModule/ModelCreatedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import User from "../../User";
 
 
 export default abstract class UserCreatedEvent extends ModelCreatedEvent {
 export default abstract class UserCreatedEvent extends ModelCreatedEvent {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 
 	protected static _hasPermission = isAdmin;
 	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 ModelDeletedEvent from "@/modules/DataModule/ModelDeletedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import User from "../../User";
 
 
 export default abstract class UserDeletedEvent extends ModelDeletedEvent {
 export default abstract class UserDeletedEvent extends ModelDeletedEvent {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 
 	protected static _hasPermission = isAdmin;
 	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 ModelUpdatedEvent from "@/modules/DataModule/ModelUpdatedEvent";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import User from "../../User";
 
 
 export default abstract class UserUpdatedEvent extends ModelUpdatedEvent {
 export default abstract class UserUpdatedEvent extends ModelUpdatedEvent {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 
 	protected static _hasPermission = isAdmin;
 	protected static _hasPermission = isAdmin;
 	// TODO maybe allow this for the current logged in user?
 	// 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 CreateJob from "@/modules/DataModule/CreateJob";
+import User from "../../User";
 
 
 export default class Create extends CreateJob {
 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 DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import User from "../../User";
 
 
 export default class DeleteById extends DeleteByIdJob {
 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 DeleteManyByIdJob from "@/modules/DataModule/DeleteManyByIdJob";
+import User from "../../User";
 
 
 export default class DeleteManyById extends DeleteManyByIdJob {
 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 FindManyByIdJob from "@/modules/DataModule/FindManyByIdJob";
+import User from "../../User";
 
 
 export default class FindManyById extends FindManyByIdJob {
 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 GetDataJob from "@/modules/DataModule/GetDataJob";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
 import isAdmin from "@/modules/DataModule/permissions/isAdmin";
+import User from "../../User";
 
 
 export default class GetData extends GetDataJob {
 export default class GetData extends GetDataJob {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 
 	protected static _hasPermission = isAdmin;
 	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 DataModuleJob from "@/modules/DataModule/DataModuleJob";
 import DataModuleEvent from "@/modules/DataModule/DataModuleEvent";
 import DataModuleEvent from "@/modules/DataModule/DataModuleEvent";
 import { EventClass } from "@/modules/EventsModule/Event";
 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 GetSingleModelPermissionsResult = Record<string, boolean>; // Returned when getting permissions for a single modelId
 export type GetMultipleModelPermissionsResult = Record<
 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)
  * 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 {
 export default class GetModelPermissions extends DataModuleJob {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 
 	protected static _hasPermission = true;
 	protected static _hasPermission = true;
 
 
@@ -78,7 +79,7 @@ export default class GetModelPermissions extends DataModuleJob {
 			const cached = await CacheModule.get(cacheKey);
 			const cached = await CacheModule.get(cacheKey);
 			if (cached) return cached;
 			if (cached) return cached;
 
 
-			const model = await Model.findById(modelId);
+			const model = await Model.findByPk(modelId);
 			if (!model) throw new Error("Model not found");
 			if (!model) throw new Error("Model not found");
 
 
 			const modelPermissions = await this._getPermissionsForModel(
 			const modelPermissions = await this._getPermissionsForModel(
@@ -108,8 +109,11 @@ export default class GetModelPermissions extends DataModuleJob {
 			uncachedModelIds.push(modelId);
 			uncachedModelIds.push(modelId);
 		});
 		});
 
 
+		console.log("GMP", uncachedModelIds);
 		// For the modelIds that were not cached, get the documents from MongoDB
 		// 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
 		// Loop through the modelIds that were not cached, and get the permissions for each one individually
 		await forEachIn(uncachedModelIds, async modelId => {
 		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 { forEachIn } from "@common/utils/forEachIn";
 import CacheModule from "@/modules/CacheModule";
 import CacheModule from "@/modules/CacheModule";
 import DataModuleJob from "@/modules/DataModule/DataModuleJob";
 import DataModuleJob from "@/modules/DataModule/DataModuleJob";
-import { UserSchema } from "../schema";
 import ModuleManager from "@/ModuleManager";
 import ModuleManager from "@/ModuleManager";
 import Job from "@/Job";
 import Job from "@/Job";
 import Event from "@/modules/EventsModule/Event";
 import Event from "@/modules/EventsModule/Event";
 import { UserRole } from "../UserRole";
 import { UserRole } from "../UserRole";
+import User from "../../User";
 
 
 export type GetPermissionsResult = Record<string, boolean>;
 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.
  * Permissions are cached. No cache invalidation machanism has been implemented yet, but it expires naturally after 6 minutes.
  */
  */
 export default class GetPermissions extends DataModuleJob {
 export default class GetPermissions extends DataModuleJob {
-	protected static _modelName = "users";
+	protected static _model = User;
 
 
 	protected static _hasPermission = true;
 	protected static _hasPermission = true;
 
 
@@ -38,7 +37,7 @@ export default class GetPermissions extends DataModuleJob {
 		return permissions;
 		return permissions;
 	}
 	}
 
 
-	protected async _getPermissions(user: HydratedDocument<UserSchema> | null) {
+	protected async _getPermissions(user: User | null) {
 		const jobs = this._getAllJobs();
 		const jobs = this._getAllJobs();
 		const events = this._getAllEvents();
 		const events = this._getAllEvents();
 
 
@@ -68,7 +67,7 @@ export default class GetPermissions extends DataModuleJob {
 	}
 	}
 
 
 	protected _getFrontendViewPermissions(
 	protected _getFrontendViewPermissions(
-		user: HydratedDocument<UserSchema> | null
+		user: User | null
 	): Record<string, boolean> {
 	): Record<string, boolean> {
 		if (!user) return {};
 		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 UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import User from "../../User";
 
 
 export default class UpdateById extends UpdateByIdJob {
 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 { HydratedDocument } from "mongoose";
 import { StationSchema } from "../../models/stations/schema";
 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 { HydratedDocument, Schema } from "mongoose";
-import { UserSchema } from "../../models/users/schema";
+import User from "../../models/User";
 
 
 export default <ModelSchemaType extends Schema>(
 export default <ModelSchemaType extends Schema>(
 	model: HydratedDocument<ModelSchemaType>,
 	model: HydratedDocument<ModelSchemaType>,
-	user?: HydratedDocument<UserSchema>
+	user?: 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
  * 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 { HydratedDocument } from "mongoose";
-import { UserSchema } from "../../models/users/schema";
+import User from "../../models/User";
 
 
 export default (
 export default (
 	model:
 	model:
 		| (HydratedDocument<any> & { owner?: any })
 		| (HydratedDocument<any> & { owner?: any })
 		| (HydratedDocument<any> & { createdBy?: any }),
 		| (HydratedDocument<any> & { createdBy?: any }),
-	user?: HydratedDocument<UserSchema>
+	user?: User
 ) => {
 ) => {
 	if (!user || !model) return false;
 	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 Event from "@/modules/EventsModule/Event";
 import ModuleManager from "@/ModuleManager";
 import ModuleManager from "@/ModuleManager";
 import DataModule from "@/modules/DataModule";
 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";
 import JobContext from "@/JobContext";
 
 
 const permissionRegex =
 const permissionRegex =
@@ -212,6 +212,10 @@ export class EventsModule extends BaseModule {
 	public async assertPermission(jobContext: JobContext, permission: string) {
 	public async assertPermission(jobContext: JobContext, permission: string) {
 		let hasPermission = false;
 		let hasPermission = false;
 
 
+		// TODO improve
+		if (!permissionRegex.test(permission))
+			throw new Error("Regex doesn't match");
+
 		const {
 		const {
 			moduleName,
 			moduleName,
 			modelOrEventName,
 			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 {
 export default abstract class Event {
 	protected static _namespace: string;
 	protected static _namespace: string;
@@ -104,9 +103,7 @@ export default abstract class Event {
 		| (boolean | CallableFunction)[] = false;
 		| (boolean | CallableFunction)[] = false;
 
 
 	// Check if a given user has generic permission to subscribe to an event, using _hasPermission
 	// 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)
 		const options = Array.isArray(this._hasPermission)
 			? this._hasPermission
 			? 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 sinon from "sinon";
 import mongoose from "mongoose";
 import mongoose from "mongoose";
 import news from "logic/db/schemas/news";
 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 { TestModule } from "@/tests/support/TestModule";
 import Subscribe from "@/modules/EventsModule/jobs/Subscribe";
 import Subscribe from "@/modules/EventsModule/jobs/Subscribe";
 import DataModule from "@/modules/DataModule";
 import DataModule from "@/modules/DataModule";
 import EventsModule from "@/modules/EventsModule";
 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 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 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("Subscribe job", async function () {
 	describe("execute", function () {
 	describe("execute", function () {

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

@@ -10,9 +10,9 @@ import WebSocket from "@/WebSocket";
 import ModuleManager from "@/ModuleManager";
 import ModuleManager from "@/ModuleManager";
 import JobQueue from "@/JobQueue";
 import JobQueue from "@/JobQueue";
 import DataModule from "./DataModule";
 import DataModule from "./DataModule";
-import { UserModel } from "./DataModule/models/users/schema";
-import { SessionModel } from "./DataModule/models/sessions/schema";
 import EventsModule from "./EventsModule";
 import EventsModule from "./EventsModule";
+import User from "./DataModule/models/User";
+import Session from "./DataModule/models/Session";
 // import assertEventDerived from "@/utils/assertEventDerived";
 // import assertEventDerived from "@/utils/assertEventDerived";
 
 
 export class WebSocketModule extends BaseModule {
 export class WebSocketModule extends BaseModule {
@@ -132,16 +132,24 @@ export class WebSocketModule extends BaseModule {
 		if (sessionId && isObjectIdOrHexString(sessionId)) {
 		if (sessionId && isObjectIdOrHexString(sessionId)) {
 			socket.setSessionId(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) {
 			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;
 			let session;
 			if (socket.getSessionId()) {
 			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.");
 				if (!session) throw new Error("Session not found.");
 			}
 			}