Browse Source

refactor: Separated jobs into their own classes

Owen Diffey 1 year ago
parent
commit
1f4df43cbf
90 changed files with 933 additions and 952 deletions
  1. 27 94
      backend/src/BaseModule.ts
  2. 58 41
      backend/src/Job.ts
  3. 14 49
      backend/src/JobContext.ts
  4. 15 33
      backend/src/JobQueue.ts
  5. 6 3
      backend/src/ModuleManager.ts
  6. 0 14
      backend/src/main.ts
  7. 0 314
      backend/src/modules/APIModule.ts
  8. 0 2
      backend/src/modules/CacheModule.ts
  9. 57 198
      backend/src/modules/DataModule.ts
  10. 18 0
      backend/src/modules/DataModule/CreateJob.ts
  11. 78 0
      backend/src/modules/DataModule/DataModuleJob.ts
  12. 14 0
      backend/src/modules/DataModule/DeleteByIdJob.ts
  13. 13 0
      backend/src/modules/DataModule/FindByIdJob.ts
  14. 11 0
      backend/src/modules/DataModule/GetDataJob.ts
  15. 0 0
      backend/src/modules/DataModule/Migration.ts
  16. 25 0
      backend/src/modules/DataModule/UpdateByIdJob.ts
  17. 6 0
      backend/src/modules/DataModule/models/abc/jobs/Create.ts
  18. 6 0
      backend/src/modules/DataModule/models/abc/jobs/DeleteById.ts
  19. 6 0
      backend/src/modules/DataModule/models/abc/jobs/FindById.ts
  20. 6 0
      backend/src/modules/DataModule/models/abc/jobs/UpdateById.ts
  21. 0 0
      backend/src/modules/DataModule/models/abc/schema.ts
  22. 0 0
      backend/src/modules/DataModule/models/news/NewsStatus.ts
  23. 18 0
      backend/src/modules/DataModule/models/news/config.ts
  24. 0 0
      backend/src/modules/DataModule/models/news/getData.ts
  25. 6 0
      backend/src/modules/DataModule/models/news/jobs/Create.ts
  26. 6 0
      backend/src/modules/DataModule/models/news/jobs/DeleteById.ts
  27. 8 0
      backend/src/modules/DataModule/models/news/jobs/FindById.ts
  28. 6 0
      backend/src/modules/DataModule/models/news/jobs/GetData.ts
  29. 22 0
      backend/src/modules/DataModule/models/news/jobs/Newest.ts
  30. 15 0
      backend/src/modules/DataModule/models/news/jobs/Published.ts
  31. 6 0
      backend/src/modules/DataModule/models/news/jobs/UpdateById.ts
  32. 1 1
      backend/src/modules/DataModule/models/news/migrations/1620330161000-news-markdown.ts
  33. 1 1
      backend/src/modules/DataModule/models/news/schema.ts
  34. 0 3
      backend/src/modules/DataModule/models/permissions/isPrivate.ts
  35. 0 3
      backend/src/modules/DataModule/models/permissions/isPublic.ts
  36. 0 3
      backend/src/modules/DataModule/models/permissions/isUnlisted.ts
  37. 0 38
      backend/src/modules/DataModule/models/schemas/news/config.ts
  38. 0 25
      backend/src/modules/DataModule/models/schemas/stations/config.ts
  39. 6 0
      backend/src/modules/DataModule/models/sessions/jobs/Create.ts
  40. 6 0
      backend/src/modules/DataModule/models/sessions/jobs/DeleteById.ts
  41. 6 0
      backend/src/modules/DataModule/models/sessions/jobs/FindById.ts
  42. 6 0
      backend/src/modules/DataModule/models/sessions/jobs/UpdateById.ts
  43. 10 7
      backend/src/modules/DataModule/models/sessions/schema.ts
  44. 0 0
      backend/src/modules/DataModule/models/stations/StationAutofillMode.ts
  45. 0 0
      backend/src/modules/DataModule/models/stations/StationPrivacy.ts
  46. 0 0
      backend/src/modules/DataModule/models/stations/StationRequestsAccess.ts
  47. 0 0
      backend/src/modules/DataModule/models/stations/StationTheme.ts
  48. 0 0
      backend/src/modules/DataModule/models/stations/StationType.ts
  49. 6 0
      backend/src/modules/DataModule/models/stations/config.ts
  50. 0 0
      backend/src/modules/DataModule/models/stations/getData.ts
  51. 9 0
      backend/src/modules/DataModule/models/stations/jobs/Create.ts
  52. 9 0
      backend/src/modules/DataModule/models/stations/jobs/DeleteById.ts
  53. 12 0
      backend/src/modules/DataModule/models/stations/jobs/FindById.ts
  54. 6 0
      backend/src/modules/DataModule/models/stations/jobs/GetData.ts
  55. 10 0
      backend/src/modules/DataModule/models/stations/jobs/UpdateById.ts
  56. 1 1
      backend/src/modules/DataModule/models/stations/schema.ts
  57. 0 0
      backend/src/modules/DataModule/models/users/UserAvatarColor.ts
  58. 0 0
      backend/src/modules/DataModule/models/users/UserAvatarType.ts
  59. 0 0
      backend/src/modules/DataModule/models/users/UserRole.ts
  60. 0 0
      backend/src/modules/DataModule/models/users/config.ts
  61. 6 0
      backend/src/modules/DataModule/models/users/jobs/Create.ts
  62. 6 0
      backend/src/modules/DataModule/models/users/jobs/DeleteById.ts
  63. 6 0
      backend/src/modules/DataModule/models/users/jobs/FindById.ts
  64. 76 0
      backend/src/modules/DataModule/models/users/jobs/GetModelPermissions.ts
  65. 35 0
      backend/src/modules/DataModule/models/users/jobs/GetPermissions.ts
  66. 6 0
      backend/src/modules/DataModule/models/users/jobs/UpdateById.ts
  67. 0 0
      backend/src/modules/DataModule/models/users/schema.ts
  68. 0 0
      backend/src/modules/DataModule/permissions/isDj.ts
  69. 0 0
      backend/src/modules/DataModule/permissions/isLoggedIn.ts
  70. 0 0
      backend/src/modules/DataModule/permissions/isOwner.ts
  71. 3 0
      backend/src/modules/DataModule/permissions/isPrivate.ts
  72. 3 0
      backend/src/modules/DataModule/permissions/isPublic.ts
  73. 3 0
      backend/src/modules/DataModule/permissions/isUnlisted.ts
  74. 0 0
      backend/src/modules/DataModule/plugins/documentVersion.ts
  75. 0 10
      backend/src/modules/DataModule/plugins/getData.ts
  76. 75 38
      backend/src/modules/EventsModule.ts
  77. 41 0
      backend/src/modules/EventsModule/jobs/Subscribe.ts
  78. 45 0
      backend/src/modules/EventsModule/jobs/SubscribeMany.ts
  79. 22 0
      backend/src/modules/EventsModule/jobs/Unsubscribe.ts
  80. 22 0
      backend/src/modules/EventsModule/jobs/UnsubscribeAll.ts
  81. 22 0
      backend/src/modules/EventsModule/jobs/UnsubscribeMany.ts
  82. 0 37
      backend/src/modules/StationsModule.ts
  83. 4 10
      backend/src/modules/WebSocketModule.ts
  84. 5 5
      backend/src/types/Models.ts
  85. 0 5
      backend/src/types/Modules.ts
  86. 6 6
      backend/src/types/Schemas.ts
  87. 1 1
      frontend/src/Model.ts
  88. 1 1
      frontend/src/stores/model.ts
  89. 2 2
      frontend/src/stores/userAuth.ts
  90. 7 7
      frontend/src/stores/websocket.ts

+ 27 - 94
backend/src/BaseModule.ts

@@ -1,7 +1,9 @@
-import JobContext from "@/JobContext";
+import { readdir } from "fs/promises";
+import path from "path";
 import LogBook, { Log } from "@/LogBook";
 import LogBook, { Log } from "@/LogBook";
 import ModuleManager from "@/ModuleManager";
 import ModuleManager from "@/ModuleManager";
 import { Modules } from "@/types/Modules";
 import { Modules } from "@/types/Modules";
+import Job from "./Job";
 
 
 export enum ModuleStatus {
 export enum ModuleStatus {
 	LOADED = "LOADED",
 	LOADED = "LOADED",
@@ -20,26 +22,7 @@ export default abstract class BaseModule {
 
 
 	protected _dependentModules: (keyof Modules)[];
 	protected _dependentModules: (keyof Modules)[];
 
 
-	protected _jobConfigDefault: boolean | "disabled";
-
-	protected _jobConfig: Record<
-		string,
-		| "disabled"
-		| boolean
-		| ((context: JobContext, payload?: any) => Promise<any>)
-		| {
-				api?: boolean;
-				method?: (context: JobContext, payload?: any) => Promise<any>;
-		  }
-	>;
-
-	protected _jobs: Record<
-		string,
-		{
-			api: boolean;
-			method: (context: JobContext, payload?: any) => Promise<any>;
-		}
-	>;
+	protected _jobs: Record<string, typeof Job>;
 
 
 	/**
 	/**
 	 * Base Module
 	 * Base Module
@@ -50,8 +33,6 @@ export default abstract class BaseModule {
 		this._name = name;
 		this._name = name;
 		this._status = ModuleStatus.LOADED;
 		this._status = ModuleStatus.LOADED;
 		this._dependentModules = [];
 		this._dependentModules = [];
-		this._jobConfigDefault = true;
-		this._jobConfig = {};
 		this._jobs = {};
 		this._jobs = {};
 		this.log(`Module (${this._name}) loaded`);
 		this.log(`Module (${this._name}) loaded`);
 	}
 	}
@@ -94,82 +75,30 @@ export default abstract class BaseModule {
 	 * _loadJobs - Load jobs available via api module
 	 * _loadJobs - Load jobs available via api module
 	 */
 	 */
 	private async _loadJobs() {
 	private async _loadJobs() {
-		this._jobs = {};
+		let jobs;
 
 
-		const module = Object.getPrototypeOf(this);
-		await Promise.all(
-			Object.getOwnPropertyNames(module).map(async property => {
-				if (
-					typeof module[property] !== "function" ||
-					Object.prototype.hasOwnProperty.call(
-						BaseModule.prototype,
-						property
-					) ||
-					property.startsWith("_")
+		try {
+			jobs = await readdir(
+				path.resolve(
+					__dirname,
+					`./modules/${this.constructor.name}/jobs`
 				)
 				)
-					return;
-
-				const options = this._jobConfig[property];
+			);
+		} catch (error) {
+			if (error.code === "ENOENT") return;
 
 
-				let api = this._jobConfigDefault === true;
-				if (
-					typeof options === "object" &&
-					typeof options.api === "boolean"
-				)
-					api = options.api;
-				else if (typeof options === "boolean") api = options;
-				else if (this._jobConfigDefault === "disabled") return;
-
-				this._jobs[property] = {
-					api,
-					method: module[property]
-				};
-			})
-		);
+			throw error;
+		}
 
 
 		await Promise.all(
 		await Promise.all(
-			Object.entries(this._jobConfig).map(async ([name, options]) => {
-				if (options === "disabled") {
-					if (this._jobs[name]) delete this._jobs[name];
+			jobs.map(async jobFile => {
+				const { default: Job } = await import(
+					`./modules/${this.constructor.name}/jobs/${jobFile}`
+				);
 
 
-					return;
-				}
+				const jobName = Job.getName();
 
 
-				if (
-					typeof options === "boolean" ||
-					(typeof options === "object" &&
-						typeof options.method !== "function")
-				)
-					return;
-
-				if (this._jobs[name])
-					throw new Error(`Job "${name}" is already defined`);
-
-				let api = this._jobConfigDefault === true;
-
-				if (
-					typeof options === "object" &&
-					typeof options.api === "boolean"
-				)
-					api = options.api;
-
-				let method = options;
-
-				if (
-					typeof method === "object" &&
-					typeof method.method === "function"
-				)
-					method = method.method;
-
-				if (typeof method !== "function")
-					throw new Error(
-						`Job "${name}" has no function method defined`
-					);
-
-				this._jobs[name] = {
-					api,
-					method
-				};
+				this._jobs[jobName] = Job;
 			})
 			})
 		);
 		);
 	}
 	}
@@ -178,9 +107,13 @@ export default abstract class BaseModule {
 	 * getJob - Get module job
 	 * getJob - Get module job
 	 */
 	 */
 	public getJob(name: string) {
 	public getJob(name: string) {
-		if (!this._jobs[name]) throw new Error(`Job "${name}" not found.`);
+		const [, Job] =
+			Object.entries(this._jobs).find(([jobName]) => jobName === name) ??
+			[];
+
+		if (!Job) throw new Error(`Job "${name}" not found.`);
 
 
-		return this._jobs[name];
+		return Job;
 	}
 	}
 
 
 	/**
 	/**

+ 58 - 41
backend/src/Job.ts

@@ -1,7 +1,6 @@
 import JobContext from "@/JobContext";
 import JobContext from "@/JobContext";
 import JobStatistics from "@/JobStatistics";
 import JobStatistics from "@/JobStatistics";
 import LogBook, { Log } from "@/LogBook";
 import LogBook, { Log } from "@/LogBook";
-import ModuleManager from "@/ModuleManager";
 import { JobOptions } from "@/types/JobOptions";
 import { JobOptions } from "@/types/JobOptions";
 import { Modules } from "@/types/Modules";
 import { Modules } from "@/types/Modules";
 import WebSocketModule from "./modules/WebSocketModule";
 import WebSocketModule from "./modules/WebSocketModule";
@@ -13,20 +12,18 @@ export enum JobStatus {
 	COMPLETED = "COMPLETED"
 	COMPLETED = "COMPLETED"
 }
 }
 
 
-export default class Job {
-	private _name: string;
+export default abstract class Job {
+	protected static _apiEnabled = true;
 
 
-	private _module: Modules[keyof Modules];
+	protected _module: Modules[keyof Modules];
 
 
-	private _jobFunction: any;
+	protected _payload: any;
 
 
-	private _payload: any;
+	protected _context: JobContext;
 
 
-	private _context: JobContext;
+	protected _priority: number;
 
 
-	private _priority: number;
-
-	private _longJob?: {
+	protected _longJob?: {
 		title: string;
 		title: string;
 		progress?: {
 		progress?: {
 			data: unknown;
 			data: unknown;
@@ -35,15 +32,15 @@ export default class Job {
 		};
 		};
 	};
 	};
 
 
-	private _uuid: string;
+	protected _uuid: string;
 
 
-	private _status: JobStatus;
+	protected _status: JobStatus;
 
 
-	private _createdAt: number;
+	protected _createdAt: number;
 
 
-	private _startedAt?: number;
+	protected _startedAt?: number;
 
 
-	private _completedAt?: number;
+	protected _completedAt?: number;
 
 
 	/**
 	/**
 	 * Job
 	 * Job
@@ -54,23 +51,13 @@ export default class Job {
 	 * @param options - Job options
 	 * @param options - Job options
 	 */
 	 */
 	public constructor(
 	public constructor(
-		name: string,
-		moduleName: keyof Modules,
+		module: Modules[keyof Modules],
 		payload: any,
 		payload: any,
 		options?: Omit<JobOptions, "runDirectly">
 		options?: Omit<JobOptions, "runDirectly">
 	) {
 	) {
-		this._name = name;
-		this._priority = 1;
-
-		const module = ModuleManager.getModule(moduleName);
-		if (!module) throw new Error("Module not found.");
 		this._module = module;
 		this._module = module;
-
-		this._jobFunction = this._module.getJob(this._name).method;
-
 		this._payload = payload;
 		this._payload = payload;
-
-		JobStatistics.updateStats(this.getName(), "added");
+		this._priority = 1;
 
 
 		let contextOptions;
 		let contextOptions;
 
 
@@ -105,12 +92,27 @@ export default class Job {
 	}
 	}
 
 
 	/**
 	/**
-	 * getName - Get module and job name in a dot format, e.g. module.jobName
-	 *
-	 * @returns module.name
+	 * getName - Get job name
+	 */
+	public static getName() {
+		return this.name.substring(0, 1).toLowerCase() + this.name.substring(1);
+	}
+
+	/**
+	 * getName - Get job name
 	 */
 	 */
 	public getName() {
 	public getName() {
-		return `${this._module.getName()}.${this._name}`;
+		return (
+			this.constructor.name.substring(0, 1).toLowerCase() +
+			this.constructor.name.substring(1)
+		);
+	}
+
+	/**
+	 * getPath - Get module and job name in a dot format, e.g. module.jobName
+	 */
+	public getPath() {
+		return `${this._module.getName()}.${this.getName()}`;
 	}
 	}
 
 
 	/**
 	/**
@@ -145,7 +147,7 @@ export default class Job {
 	 *
 	 *
 	 * @param status - Job status
 	 * @param status - Job status
 	 */
 	 */
-	private _setStatus(status: JobStatus) {
+	protected _setStatus(status: JobStatus) {
 		this._status = status;
 		this._status = status;
 	}
 	}
 
 
@@ -158,6 +160,20 @@ export default class Job {
 		return this._module;
 		return this._module;
 	}
 	}
 
 
+	public static isApiEnabled() {
+		return this._apiEnabled;
+	}
+
+	public isApiEnabled() {
+		return this.constructor._apiEnabled;
+	}
+
+	protected async _authorize() {
+		await this._context.assertPermission(this.getPath());
+	}
+
+	protected abstract _execute();
+
 	/**
 	/**
 	 * execute - Execute job
 	 * execute - Execute job
 	 *
 	 *
@@ -173,8 +189,8 @@ export default class Job {
 		this._startedAt = performance.now();
 		this._startedAt = performance.now();
 
 
 		return (
 		return (
-			this._jobFunction
-				.apply(this._module, [this._context, this._payload])
+			this._authorize()
+				.then(() => this._execute(this._payload))
 				// eslint-disable-next-line
 				// eslint-disable-next-line
 				// @ts-ignore
 				// @ts-ignore
 				.then(async data => {
 				.then(async data => {
@@ -198,7 +214,7 @@ export default class Job {
 						type: "success"
 						type: "success"
 					});
 					});
 
 
-					JobStatistics.updateStats(this.getName(), "successful");
+					JobStatistics.updateStats(this.getPath(), "successful");
 
 
 					return data;
 					return data;
 				})
 				})
@@ -226,16 +242,16 @@ export default class Job {
 						data: { error }
 						data: { error }
 					});
 					});
 
 
-					JobStatistics.updateStats(this.getName(), "failed");
+					JobStatistics.updateStats(this.getPath(), "failed");
 
 
 					throw error;
 					throw error;
 				})
 				})
 				.finally(() => {
 				.finally(() => {
 					this._completedAt = performance.now();
 					this._completedAt = performance.now();
-					JobStatistics.updateStats(this.getName(), "total");
+					JobStatistics.updateStats(this.getPath(), "total");
 					if (this._startedAt)
 					if (this._startedAt)
 						JobStatistics.updateStats(
 						JobStatistics.updateStats(
-							this.getName(),
+							this.getPath(),
 							"duration",
 							"duration",
 							this._completedAt - this._startedAt
 							this._completedAt - this._startedAt
 						);
 						);
@@ -260,7 +276,7 @@ export default class Job {
 		LogBook.log({
 		LogBook.log({
 			message,
 			message,
 			type,
 			type,
-			category: this.getName(),
+			category: this.getPath(),
 			data: {
 			data: {
 				...this.toJSON(),
 				...this.toJSON(),
 				...data
 				...data
@@ -277,12 +293,13 @@ export default class Job {
 		return {
 		return {
 			uuid: this.getUuid(),
 			uuid: this.getUuid(),
 			priority: this.getPriority(),
 			priority: this.getPriority(),
-			name: this.getName(),
+			name: this.getPath(),
 			status: this.getStatus(),
 			status: this.getStatus(),
 			moduleStatus: this._module.getStatus(),
 			moduleStatus: this._module.getStatus(),
 			createdAt: this._createdAt,
 			createdAt: this._createdAt,
 			startedAt: this._startedAt,
 			startedAt: this._startedAt,
-			completedAt: this._completedAt
+			completedAt: this._completedAt,
+			payload: JSON.stringify(this._payload)
 		};
 		};
 	}
 	}
 }
 }

+ 14 - 49
backend/src/JobContext.ts

@@ -1,11 +1,7 @@
-import { Types } from "mongoose";
-import { SessionSchema } from "@models/schemas/sessions/schema";
-import BaseModule from "@/BaseModule";
+import { SessionSchema } from "@/modules/DataModule/models/sessions/schema";
 import Job from "@/Job";
 import Job from "@/Job";
 import { Log } from "@/LogBook";
 import { Log } from "@/LogBook";
 import { JobOptions } from "@/types/JobOptions";
 import { JobOptions } from "@/types/JobOptions";
-import { Jobs, Modules } from "@/types/Modules";
-import { Models } from "@/types/Models";
 import DataModule from "@/modules/DataModule";
 import DataModule from "@/modules/DataModule";
 
 
 export default class JobContext {
 export default class JobContext {
@@ -56,32 +52,12 @@ export default class JobContext {
 		return this._callbackRef;
 		return this._callbackRef;
 	}
 	}
 
 
-	/**
-	 * executeJob - Execute a job
-	 *
-	 * @param moduleName - Module name
-	 * @param jobName - Job name
-	 * @param params - Params
-	 */
-	public async executeJob<
-		ModuleNameType extends keyof Jobs & keyof Modules,
-		JobNameType extends keyof Jobs[ModuleNameType] &
-			keyof Omit<Modules[ModuleNameType], keyof BaseModule>,
-		PayloadType extends "payload" extends keyof Jobs[ModuleNameType][JobNameType]
-			? Jobs[ModuleNameType][JobNameType]["payload"] extends undefined
-				? Record<string, never>
-				: Jobs[ModuleNameType][JobNameType]["payload"]
-			: Record<string, never>,
-		ReturnType = "returns" extends keyof Jobs[ModuleNameType][JobNameType]
-			? Jobs[ModuleNameType][JobNameType]["returns"]
-			: never
-	>(
-		moduleName: ModuleNameType,
-		jobName: JobNameType,
-		payload: PayloadType,
+	public executeJob(
+		JobClass: typeof Job,
+		payload?: any,
 		options?: JobOptions
 		options?: JobOptions
-	): Promise<ReturnType> {
-		return new Job(jobName.toString(), moduleName, payload, {
+	) {
+		return new JobClass(payload, {
 			session: this._session,
 			session: this._session,
 			socketId: this._socketId,
 			socketId: this._socketId,
 			...(options ?? {})
 			...(options ?? {})
@@ -106,23 +82,6 @@ export default class JobContext {
 			throw new Error("No user found for session");
 			throw new Error("No user found for session");
 	}
 	}
 
 
-	public async getUserPermissions() {
-		return this.executeJob("api", "getUserPermissions", {});
-	}
-
-	public async getUserModelPermissions({
-		modelName,
-		modelId
-	}: {
-		modelName: keyof Models;
-		modelId?: Types.ObjectId;
-	}) {
-		return this.executeJob("api", "getUserModelPermissions", {
-			modelName,
-			modelId
-		});
-	}
-
 	public async assertPermission(permission: string) {
 	public async assertPermission(permission: string) {
 		let hasPermission = false;
 		let hasPermission = false;
 
 
@@ -131,14 +90,20 @@ export default class JobContext {
 			[];
 			[];
 
 
 		if (moduleName === "data" && modelOrJobName && jobName) {
 		if (moduleName === "data" && modelOrJobName && jobName) {
-			const permissions = await this.getUserModelPermissions({
+			const GetModelPermissions = DataModule.getJob(
+				"users.getModelPermissions"
+			);
+
+			const permissions = await this.executeJob(GetModelPermissions, {
 				modelName: modelOrJobName,
 				modelName: modelOrJobName,
 				modelId
 				modelId
 			});
 			});
 
 
 			hasPermission = permissions[`data.${modelOrJobName}.${jobName}`];
 			hasPermission = permissions[`data.${modelOrJobName}.${jobName}`];
 		} else {
 		} else {
-			const permissions = await this.getUserPermissions();
+			const GetPermissions = DataModule.getJob("users.getPermissions");
+
+			const permissions = await this.executeJob(GetPermissions);
 
 
 			hasPermission = permissions[permission];
 			hasPermission = permissions[permission];
 		}
 		}

+ 15 - 33
backend/src/JobQueue.ts

@@ -2,6 +2,7 @@ import BaseModule from "@/BaseModule";
 import Job, { JobStatus } from "@/Job";
 import Job, { JobStatus } from "@/Job";
 import { JobOptions } from "@/types/JobOptions";
 import { JobOptions } from "@/types/JobOptions";
 import { Jobs, Modules } from "@/types/Modules";
 import { Jobs, Modules } from "@/types/Modules";
+import ModuleManager from "./ModuleManager";
 
 
 export class JobQueue {
 export class JobQueue {
 	private _concurrency: number;
 	private _concurrency: number;
@@ -28,7 +29,7 @@ export class JobQueue {
 	 * Job Queue
 	 * Job Queue
 	 */
 	 */
 	public constructor() {
 	public constructor() {
-		this._concurrency = 10000;
+		this._concurrency = 50;
 		this._isPaused = true;
 		this._isPaused = true;
 		this._jobs = [];
 		this._jobs = [];
 		this._queue = [];
 		this._queue = [];
@@ -71,22 +72,10 @@ export class JobQueue {
 	 * @param jobName - Job name
 	 * @param jobName - Job name
 	 * @param params - Params
 	 * @param params - Params
 	 */
 	 */
-	public async runJob<
-		ModuleNameType extends keyof Jobs & keyof Modules,
-		JobNameType extends keyof Jobs[ModuleNameType] &
-			keyof Omit<Modules[ModuleNameType], keyof BaseModule>,
-		PayloadType extends "payload" extends keyof Jobs[ModuleNameType][JobNameType]
-			? Jobs[ModuleNameType][JobNameType]["payload"] extends undefined
-				? Record<string, never>
-				: Jobs[ModuleNameType][JobNameType]["payload"]
-			: Record<string, never>,
-		ReturnType = "returns" extends keyof Jobs[ModuleNameType][JobNameType]
-			? Jobs[ModuleNameType][JobNameType]["returns"]
-			: never
-	>(
+	public async runJob<ModuleNameType extends keyof Jobs & keyof Modules>(
 		moduleName: ModuleNameType,
 		moduleName: ModuleNameType,
-		jobName: JobNameType,
-		payload: PayloadType,
+		jobName: string,
+		payload: any,
 		options?: JobOptions
 		options?: JobOptions
 	): Promise<ReturnType> {
 	): Promise<ReturnType> {
 		return new Promise<ReturnType>((resolve, reject) => {
 		return new Promise<ReturnType>((resolve, reject) => {
@@ -107,29 +96,22 @@ export class JobQueue {
 	 * @param jobName - Job name
 	 * @param jobName - Job name
 	 * @param params - Params
 	 * @param params - Params
 	 */
 	 */
-	public async queueJob<
-		ModuleNameType extends keyof Jobs & keyof Modules,
-		JobNameType extends keyof Jobs[ModuleNameType] &
-			keyof Omit<Modules[ModuleNameType], keyof BaseModule>,
-		PayloadType extends "payload" extends keyof Jobs[ModuleNameType][JobNameType]
-			? Jobs[ModuleNameType][JobNameType]["payload"] extends undefined
-				? Record<string, never>
-				: Jobs[ModuleNameType][JobNameType]["payload"]
-			: Record<string, never>,
-		ReturnType = "returns" extends keyof Jobs[ModuleNameType][JobNameType]
-			? Jobs[ModuleNameType][JobNameType]["returns"]
-			: never
-	>(
+	public async queueJob<ModuleNameType extends keyof Jobs & keyof Modules>(
 		moduleName: ModuleNameType,
 		moduleName: ModuleNameType,
-		jobName: JobNameType,
-		payload: PayloadType,
+		jobName: string,
+		payload: any,
 		callback: {
 		callback: {
-			resolve: (value: ReturnType) => void;
+			resolve: (value: any) => void;
 			reject: (reason?: any) => void;
 			reject: (reason?: any) => void;
 		},
 		},
 		options?: JobOptions
 		options?: JobOptions
 	): Promise<string> {
 	): Promise<string> {
-		const job = new Job(jobName.toString(), moduleName, payload, options);
+		const module = ModuleManager.getModule(moduleName);
+		if (!module) throw new Error("Module not found.");
+
+		const JobClass = module.getJob(jobName);
+
+		const job = new JobClass(payload, options);
 
 
 		this._callbacks[job.getUuid()] = callback;
 		this._callbacks[job.getUuid()] = callback;
 
 

+ 6 - 3
backend/src/ModuleManager.ts

@@ -34,7 +34,6 @@ export class ModuleManager {
 	 */
 	 */
 	private async _loadModule<T extends keyof Modules>(moduleName: T) {
 	private async _loadModule<T extends keyof Modules>(moduleName: T) {
 		const mapper = {
 		const mapper = {
-			api: "APIModule",
 			cache: "CacheModule",
 			cache: "CacheModule",
 			data: "DataModule",
 			data: "DataModule",
 			events: "EventsModule",
 			events: "EventsModule",
@@ -54,7 +53,6 @@ export class ModuleManager {
 	 */
 	 */
 	private async _loadModules() {
 	private async _loadModules() {
 		this._modules = {
 		this._modules = {
-			api: await this._loadModule("api"),
 			cache: await this._loadModule("cache"),
 			cache: await this._loadModule("cache"),
 			data: await this._loadModule("data"),
 			data: await this._loadModule("data"),
 			events: await this._loadModule("events"),
 			events: await this._loadModule("events"),
@@ -105,7 +103,12 @@ export class ModuleManager {
 		return Object.fromEntries(
 		return Object.fromEntries(
 			Object.entries(this._modules).map(([name, module]) => [
 			Object.entries(this._modules).map(([name, module]) => [
 				name,
 				name,
-				module.getJobs()
+				Object.fromEntries(
+					Object.entries(module.getJobs()).map(([jobName, Job]) => [
+						jobName,
+						{ api: Job.isApiEnabled() }
+					])
+				)
 			])
 			])
 		);
 		);
 	}
 	}

+ 0 - 14
backend/src/main.ts

@@ -83,20 +83,6 @@ global.rs = () => {
 	process.exit();
 	process.exit();
 };
 };
 
 
-// setTimeout(async () => {
-//	const start = Date.now();
-//	const x = [];
-//	while (x.length < 1) {
-//		x.push(JobQueue.runJob("stations", "addC", {}).catch(() => {}));
-//	}
-//	const y = await Promise.all(x);
-//	console.log(y);
-//	// const a = await JobQueue.runJob("stations", "addC", {}).catch(() => {});
-//	// console.log(555, a);
-//	const difference = Date.now() - start;
-//	console.log({ difference });
-// }, 100);
-
 const rl = readline.createInterface({
 const rl = readline.createInterface({
 	input: process.stdin,
 	input: process.stdin,
 	output: process.stdout,
 	output: process.stdout,

+ 0 - 314
backend/src/modules/APIModule.ts

@@ -1,314 +0,0 @@
-import { Types } from "mongoose";
-import { UserRole } from "@models/schemas/users/UserRole";
-import JobContext from "@/JobContext";
-import BaseModule from "@/BaseModule";
-import { UniqueMethods } from "@/types/Modules";
-import permissions from "@/permissions";
-import Job from "@/Job";
-import { Models } from "@/types/Models";
-import ModuleManager from "@/ModuleManager";
-import JobQueue from "@/JobQueue";
-import DataModule from "@/modules/DataModule";
-import EventsModule from "./EventsModule";
-import CacheModule from "./CacheModule";
-
-export class APIModule extends BaseModule {
-	private _subscriptions: Record<string, Set<string>>;
-
-	/**
-	 * API Module
-	 */
-	public constructor() {
-		super("api");
-
-		this._dependentModules = ["cache", "data", "events", "websocket"];
-
-		this._subscriptions = {};
-	}
-
-	/**
-	 * startup - Startup api module
-	 */
-	public override async startup() {
-		await super.startup();
-
-		await super._started();
-	}
-
-	/**
-	 * shutdown - Shutdown api module
-	 */
-	public override async shutdown() {
-		await super.shutdown();
-
-		await this._removeAllSubscriptions();
-
-		await super._stopped();
-	}
-
-	public async getUserPermissions(context: JobContext) {
-		const user = await context.getUser().catch(() => null);
-
-		if (!user) return permissions.guest;
-
-		const cacheKey = `user-permissions.${user._id}`;
-
-		const cached = await CacheModule.get(cacheKey);
-
-		if (cached) return cached;
-
-		const roles: UserRole[] = [user.role];
-
-		let rolePermissions: Record<string, boolean> = {};
-		roles.forEach(role => {
-			if (permissions[role])
-				rolePermissions = { ...rolePermissions, ...permissions[role] };
-		});
-
-		await CacheModule.set(cacheKey, rolePermissions, 360);
-
-		return rolePermissions;
-	}
-
-	public async getUserModelPermissions(
-		context: JobContext,
-		{
-			modelName,
-			modelId
-		}: { modelName: keyof Models; modelId?: Types.ObjectId }
-	) {
-		const user = await context.getUser().catch(() => null);
-		const permissions = await context.getUserPermissions();
-
-		let cacheKey = `model-permissions.${modelName}`;
-
-		if (modelId) cacheKey += `.${modelId}`;
-
-		if (user) cacheKey += `.user.${user._id}`;
-		else cacheKey += `.guest`;
-
-		const cached = await CacheModule.get(cacheKey);
-
-		if (cached) return cached;
-
-		const Model = await DataModule.getModel(modelName);
-
-		if (!Model) throw new Error("Model not found");
-
-		const model = modelId ? await Model.findById(modelId) : null;
-
-		if (modelId && !model) throw new Error("Model not found");
-
-		const jobs = await Promise.all(
-			Object.keys(ModuleManager.getModule("data")?.getJobs() ?? {})
-				.filter(
-					jobName =>
-						jobName.startsWith(modelName.toString()) &&
-						(modelId ? true : !jobName.endsWith("ById"))
-				)
-				.map(async jobName => {
-					jobName = `data.${jobName}`;
-
-					let hasPermission = permissions[jobName];
-
-					if (!hasPermission && modelId)
-						hasPermission =
-							permissions[`${jobName}.*`] ||
-							permissions[`${jobName}.${modelId}`];
-
-					if (hasPermission) return [jobName, true];
-
-					const [, shortJobName] =
-						new RegExp(`^data.${modelName}.([A-z]+)`).exec(
-							jobName
-						) ?? [];
-
-					const schemaOptions = (Model.schema.get("jobConfig") ?? {})[
-						shortJobName
-					];
-					let options = schemaOptions?.hasPermission ?? [];
-
-					if (!Array.isArray(options)) options = [options];
-
-					hasPermission = await options.reduce(
-						async (previous, option) => {
-							if (await previous) return true;
-
-							if (typeof option === "boolean") return option;
-
-							if (typeof option === "function")
-								return option(model, user);
-
-							return false;
-						},
-						Promise.resolve(false)
-					);
-
-					return [jobName, !!hasPermission];
-				})
-		);
-
-		const modelPermissions = Object.fromEntries(jobs);
-
-		await CacheModule.set(cacheKey, modelPermissions, 360);
-
-		return modelPermissions;
-	}
-
-	private async _subscriptionCallback(channel: string, value?: any) {
-		const promises = [];
-		for await (const socketId of this._subscriptions[channel].values()) {
-			promises.push(
-				JobQueue.runJob("websocket", "dispatch", {
-					socketId,
-					channel,
-					value
-				})
-			);
-		}
-		await Promise.all(promises);
-	}
-
-	public async subscribe(context: JobContext, payload: { channel: string }) {
-		const socketId = context.getSocketId();
-
-		if (!socketId) throw new Error("No socketId specified");
-
-		const { channel } = payload;
-		const [, moduleName, modelName, event, modelId] =
-			/^([a-z]+)\.([a-z]+)\.([A-z]+)\.?([A-z0-9]+)?$/.exec(channel) ?? [];
-
-		let permission = `event.${channel}`;
-
-		if (
-			moduleName === "model" &&
-			modelName &&
-			(modelId || event === "created")
-		) {
-			if (event === "created")
-				permission = `event.model.${modelName}.created`;
-			else permission = `data.${modelName}.findById.${modelId}`;
-		}
-
-		await context.assertPermission(permission);
-
-		if (!this._subscriptions[channel])
-			this._subscriptions[channel] = new Set();
-
-		if (this._subscriptions[channel].has(socketId)) return;
-
-		this._subscriptions[channel].add(socketId);
-
-		if (this._subscriptions[channel].size === 1)
-			await EventsModule.subscribe("event", channel, value =>
-				this._subscriptionCallback(channel, value)
-			);
-	}
-
-	public async subscribeMany(
-		context: JobContext,
-		payload: { channels: string[] }
-	) {
-		const { channels } = payload;
-
-		await Promise.all(
-			channels.map(channel =>
-				context.executeJob("api", "subscribe", {
-					channel
-				})
-			)
-		);
-	}
-
-	public async unsubscribe(
-		context: JobContext,
-		payload: { channel: string }
-	) {
-		const { channel } = payload;
-
-		const socketId = context.getSocketId();
-
-		if (!socketId) throw new Error("No socketId specified");
-
-		if (
-			!(
-				this._subscriptions[channel] &&
-				this._subscriptions[channel].has(socketId)
-			)
-		)
-			return;
-
-		this._subscriptions[channel].delete(socketId);
-
-		if (this._subscriptions[channel].size === 0) {
-			await EventsModule.unsubscribe("event", channel, value =>
-				this._subscriptionCallback(channel, value)
-			);
-
-			delete this._subscriptions[channel];
-		}
-	}
-
-	public async unsubscribeMany(
-		context: JobContext,
-		payload: { channels: string[] }
-	) {
-		const { channels } = payload;
-
-		await Promise.all(
-			channels.map(channel =>
-				context.executeJob("api", "unsubscribe", {
-					channel
-				})
-			)
-		);
-	}
-
-	public async unsubscribeAll(context: JobContext) {
-		const socketId = context.getSocketId();
-
-		if (!socketId) throw new Error("No socketId specified");
-
-		await Promise.all(
-			Object.entries(this._subscriptions)
-				.filter(([, socketIds]) => socketIds.has(socketId))
-				.map(([channel]) =>
-					context.executeJob("api", "unsubscribe", {
-						channel
-					})
-				)
-		);
-	}
-
-	private async _removeAllSubscriptions() {
-		await Promise.all(
-			Object.entries(this._subscriptions).map(
-				async ([channel, socketIds]) => {
-					const promises = [];
-					for await (const socketId of socketIds.values()) {
-						promises.push(
-							new Job(
-								"unsubscribe",
-								"api",
-								{
-									channel
-								},
-								{ socketId }
-							).execute()
-						);
-					}
-					return Promise.all(promises);
-				}
-			)
-		);
-	}
-}
-
-export type APIModuleJobs = {
-	[Property in keyof UniqueMethods<APIModule>]: {
-		payload: Parameters<UniqueMethods<APIModule>[Property]>[1];
-		returns: Awaited<ReturnType<UniqueMethods<APIModule>[Property]>>;
-	};
-};
-
-export default new APIModule();

+ 0 - 2
backend/src/modules/CacheModule.ts

@@ -11,8 +11,6 @@ export class CacheModule extends BaseModule {
 	 */
 	 */
 	public constructor() {
 	public constructor() {
 		super("cache");
 		super("cache");
-
-		this._jobConfigDefault = "disabled";
 	}
 	}
 
 
 	/**
 	/**

+ 57 - 198
backend/src/modules/DataModule.ts

@@ -1,21 +1,15 @@
 import config from "config";
 import config from "config";
-import mongoose, {
-	Connection,
-	isObjectIdOrHexString,
-	SchemaTypes,
-	Types
-} from "mongoose";
+import mongoose, { Connection, SchemaTypes } from "mongoose";
 import { patchHistoryPlugin, patchEventEmitter } from "ts-patch-mongoose";
 import { patchHistoryPlugin, patchEventEmitter } from "ts-patch-mongoose";
 import { readdir } from "fs/promises";
 import { readdir } from "fs/promises";
 import path from "path";
 import path from "path";
 import updateVersioningPlugin from "mongoose-update-versioning";
 import updateVersioningPlugin from "mongoose-update-versioning";
-import documentVersionPlugin from "@models/plugins/documentVersion";
-import getDataPlugin from "@models/plugins/getData";
-import Migration from "@models/Migration";
-import JobContext from "@/JobContext";
+import Migration from "@/modules/DataModule/Migration";
+import documentVersionPlugin from "@/modules/DataModule/plugins/documentVersion";
+import getDataPlugin from "@/modules/DataModule/plugins/getData";
 import BaseModule, { ModuleStatus } from "@/BaseModule";
 import BaseModule, { ModuleStatus } from "@/BaseModule";
 import { UniqueMethods } from "@/types/Modules";
 import { UniqueMethods } from "@/types/Modules";
-import { AnyModel, Models } from "@/types/Models";
+import { Models } from "@/types/Models";
 import { Schemas } from "@/types/Schemas";
 import { Schemas } from "@/types/Schemas";
 import EventsModule from "./EventsModule";
 import EventsModule from "./EventsModule";
 
 
@@ -31,10 +25,6 @@ export class DataModule extends BaseModule {
 		super("data");
 		super("data");
 
 
 		this._dependentModules = ["events"];
 		this._dependentModules = ["events"];
-
-		this._jobConfig = {
-			getModel: "disabled"
-		};
 	}
 	}
 
 
 	/**
 	/**
@@ -51,7 +41,7 @@ export class DataModule extends BaseModule {
 
 
 		await this._syncModelIndexes();
 		await this._syncModelIndexes();
 
 
-		await this._defineModelJobs();
+		await this._loadModelJobs();
 
 
 		await super._started();
 		await super._started();
 	}
 	}
@@ -169,7 +159,7 @@ export class DataModule extends BaseModule {
 		if (!this._mongoConnection) throw new Error("Mongo is not available");
 		if (!this._mongoConnection) throw new Error("Mongo is not available");
 
 
 		const { schema }: { schema: Schemas[ModelName] } = await import(
 		const { schema }: { schema: Schemas[ModelName] } = await import(
-			`./DataModule/models/schemas/${modelName.toString()}/schema`
+			`./DataModule/models/${modelName.toString()}/schema`
 		);
 		);
 
 
 		schema.plugin(documentVersionPlugin);
 		schema.plugin(documentVersionPlugin);
@@ -293,26 +283,47 @@ export class DataModule extends BaseModule {
 		return this._models[name];
 		return this._models[name];
 	}
 	}
 
 
-	private async _loadMigrations() {
+	private async _loadModelMigrations(modelName: string) {
 		if (!this._mongoConnection) throw new Error("Mongo is not available");
 		if (!this._mongoConnection) throw new Error("Mongo is not available");
 
 
-		const migrations = await readdir(
-			path.resolve(__dirname, "./DataModule/models/migrations/")
-		);
+		let migrations;
+
+		try {
+			migrations = await readdir(
+				path.resolve(
+					__dirname,
+					`./DataModule/models/${modelName}/migrations/`
+				)
+			);
+		} catch (error) {
+			if (error.code === "ENOENT") return [];
+
+			throw error;
+		}
 
 
 		return Promise.all(
 		return Promise.all(
 			migrations.map(async migrationFile => {
 			migrations.map(async migrationFile => {
 				const { default: Migrate }: { default: typeof Migration } =
 				const { default: Migrate }: { default: typeof Migration } =
 					await import(
 					await import(
-						`./DataModule/models/migrations/${migrationFile}`
+						`./DataModule/models/${modelName}/migrations/${migrationFile}`
 					);
 					);
 				return new Migrate(this._mongoConnection as Connection);
 				return new Migrate(this._mongoConnection as Connection);
 			})
 			})
 		);
 		);
 	}
 	}
 
 
+	private async _loadMigrations() {
+		const models = await readdir(
+			path.resolve(__dirname, "./DataModule/models/")
+		);
+
+		return Promise.all(
+			models.map(async modelName => this._loadModelMigrations(modelName))
+		);
+	}
+
 	private async _runMigrations() {
 	private async _runMigrations() {
-		const migrations = await this._loadMigrations();
+		const migrations = (await this._loadMigrations()).flat();
 
 
 		for (let i = 0; i < migrations.length; i += 1) {
 		for (let i = 0; i < migrations.length; i += 1) {
 			const migration = migrations[i];
 			const migration = migrations[i];
@@ -321,189 +332,37 @@ export class DataModule extends BaseModule {
 		}
 		}
 	}
 	}
 
 
-	private async _defineModelJobs() {
+	private async _loadModelJobs() {
 		if (!this._models) throw new Error("Models not loaded");
 		if (!this._models) throw new Error("Models not loaded");
 
 
 		await Promise.all(
 		await Promise.all(
-			Object.entries(this._models).map(async ([modelName, model]) => {
-				await Promise.all(
-					["create", "findById", "updateById", "deleteById"].map(
-						async method => {
-							this._jobConfig[`${modelName}.${method}`] = {
-								method: async (context, payload) =>
-									Object.getPrototypeOf(this)[`_${method}`](
-										context,
-										{
-											...payload,
-											modelName,
-											model
-										}
-									)
-							};
-						}
-					)
-				);
-
-				const jobConfig = model.schema.get("jobConfig");
-				if (
-					typeof jobConfig === "object" &&
-					Object.keys(jobConfig).length > 0
-				)
-					await Promise.all(
-						Object.entries(jobConfig).map(
-							async ([name, options]) => {
-								if (options === "disabled") {
-									if (this._jobConfig[`${modelName}.${name}`])
-										delete this._jobConfig[
-											`${modelName}.${name}`
-										];
-
-									return;
-								}
-
-								let api = this._jobConfigDefault === true;
-
-								let method;
-
-								const configOptions =
-									this._jobConfig[`${modelName}.${name}`];
-								if (typeof configOptions === "object") {
-									if (typeof configOptions.api === "boolean")
-										api = configOptions.api;
-									if (
-										typeof configOptions.method ===
-										"function"
-									)
-										method = configOptions.method;
-								} else if (typeof configOptions === "function")
-									method = configOptions;
-								else if (typeof configOptions === "boolean")
-									api = configOptions;
-								else if (
-									this._jobConfigDefault === "disabled"
-								) {
-									if (this._jobConfig[`${modelName}.${name}`])
-										delete this._jobConfig[
-											`${modelName}.${name}`
-										];
-
-									return;
-								}
-
-								if (
-									typeof options === "object" &&
-									typeof options.api === "boolean"
-								)
-									api = options.api;
-								else if (typeof options === "boolean")
-									api = options;
-
-								if (
-									typeof options === "object" &&
-									typeof options.method === "function"
-								)
-									method = async (...args) =>
-										options.method.apply(model, args);
-								else if (typeof options === "function")
-									method = async (...args) =>
-										options.apply(model, args);
-
-								if (typeof method !== "function")
-									throw new Error(
-										`Job "${name}" has no function method defined`
-									);
-
-								this._jobConfig[`${modelName}.${name}`] = {
-									api,
-									method
-								};
-							}
+			Object.keys(this._models).map(async modelName => {
+				let jobs;
+
+				try {
+					jobs = await readdir(
+						path.resolve(
+							__dirname,
+							`./${this.constructor.name}/models/${modelName}/jobs/`
 						)
 						)
 					);
 					);
-			})
-		);
-	}
-
-	private async _findById(
-		context: JobContext,
-		payload: {
-			modelName: keyof Models;
-			model: AnyModel;
-			_id: Types.ObjectId;
-		}
-	) {
-		const { modelName, model, _id } = payload ?? {};
+				} catch (error) {
+					if (error.code === "ENOENT") return;
 
 
-		await context.assertPermission(`data.${modelName}.findById.${_id}`);
+					throw error;
+				}
 
 
-		const query = model.findById(_id);
-
-		return query.exec();
-	}
-
-	private async _create(
-		context: JobContext,
-		payload: {
-			modelName: keyof Models;
-			model: AnyModel;
-			query: Record<string, any[]>;
-		}
-	) {
-		const { modelName, model, query } = payload ?? {};
-
-		await context.assertPermission(`data.${modelName}.create`);
-
-		if (typeof query !== "object")
-			throw new Error("Query is not an object");
-		if (Object.keys(query).length === 0)
-			throw new Error("Empty query object provided");
-
-		if (model.schema.path("createdBy"))
-			query.createdBy = (await context.getUser())._id;
-
-		return model.create(query);
-	}
-
-	private async _updateById(
-		context: JobContext,
-		payload: {
-			modelName: keyof Models;
-			model: AnyModel;
-			_id: Types.ObjectId;
-			query: Record<string, any[]>;
-		}
-	) {
-		const { modelName, model, _id, query } = payload ?? {};
-
-		await context.assertPermission(`data.${modelName}.updateById.${_id}`);
-
-		if (!isObjectIdOrHexString(_id))
-			throw new Error("_id is not an ObjectId");
-
-		if (typeof query !== "object")
-			throw new Error("Query is not an object");
-		if (Object.keys(query).length === 0)
-			throw new Error("Empty query object provided");
-
-		return model.updateOne({ _id }, { $set: query });
-	}
-
-	private async _deleteById(
-		context: JobContext,
-		payload: {
-			modelName: keyof Models;
-			model: AnyModel;
-			_id: Types.ObjectId;
-		}
-	) {
-		const { modelName, model, _id } = payload ?? {};
-
-		await context.assertPermission(`data.${modelName}.deleteById.${_id}`);
-
-		if (!isObjectIdOrHexString(_id))
-			throw new Error("_id is not an ObjectId");
+				await Promise.all(
+					jobs.map(async jobFile => {
+						const { default: Job } = await import(
+							`./${this.constructor.name}/models/${modelName}/jobs/${jobFile}`
+						);
 
 
-		return model.deleteOne({ _id });
+						this._jobs[Job.getName()] = Job;
+					})
+				);
+			})
+		);
 	}
 	}
 }
 }
 
 

+ 18 - 0
backend/src/modules/DataModule/CreateJob.ts

@@ -0,0 +1,18 @@
+import DataModule from "../DataModule";
+import DataModuleJob from "./DataModuleJob";
+
+export default abstract class CreateJob extends DataModuleJob {
+	protected async _execute({ query }: { query: Record<string, any[]> }) {
+		if (typeof query !== "object")
+			throw new Error("Query is not an object");
+		if (Object.keys(query).length === 0)
+			throw new Error("Empty query object provided");
+
+		const model = await DataModule.getModel(this.getModelName());
+
+		if (model.schema.path("createdBy"))
+			query.createdBy = (await this._context.getUser())._id;
+
+		return model.create(query);
+	}
+}

+ 78 - 0
backend/src/modules/DataModule/DataModuleJob.ts

@@ -0,0 +1,78 @@
+import { isObjectIdOrHexString } from "mongoose";
+import Job from "@/Job";
+import DataModule from "../DataModule";
+import { AnyModel, Models } from "@/types/Models";
+import { JobOptions } from "@/types/JobOptions";
+import { UserModel } from "./models/users/schema";
+
+export default abstract class DataModuleJob extends Job {
+	protected static _modelName: keyof Models;
+
+	protected static _hasPermission:
+		| boolean
+		| CallableFunction
+		| (boolean | CallableFunction)[] = false;
+
+	public constructor(
+		payload: any,
+		options?: Omit<JobOptions, "runDirectly">
+	) {
+		super(DataModule, payload, options);
+	}
+
+	public static override getName() {
+		return `${this._modelName}.${super.getName()}`;
+	}
+
+	public override getName() {
+		return `${this.constructor._modelName}.${super.getName()}`;
+	}
+
+	public static getModelName() {
+		return this._modelName;
+	}
+
+	public getModelName() {
+		return this.constructor._modelName;
+	}
+
+	public static async hasPermission(model: AnyModel, user?: UserModel) {
+		const options = Array.isArray(this._hasPermission)
+			? this._hasPermission
+			: [this._hasPermission];
+
+		return options.reduce(async (previous, option) => {
+			if (await previous) return true;
+
+			if (typeof option === "boolean") return option;
+
+			if (typeof option === "function") return option(model, user);
+
+			return false;
+		}, Promise.resolve(false));
+	}
+
+	protected override async _authorize() {
+		const modelId = this._payload?._id;
+
+		if (isObjectIdOrHexString(modelId)) {
+			await this._context.assertPermission(
+				`${this.getPath()}.${modelId}`
+			);
+
+			return;
+		}
+
+		const modelIds = this._payload?.modelIds;
+
+		if (Array.isArray(modelIds)) {
+			await Promise.all(
+				modelIds.map(async _id =>
+					this._context.assertPermission(`${this.getPath()}.${_id}`)
+				)
+			);
+		}
+
+		await this._context.assertPermission(this.getPath());
+	}
+}

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

@@ -0,0 +1,14 @@
+import { Types, isObjectIdOrHexString } from "mongoose";
+import DataModule from "../DataModule";
+import DataModuleJob from "./DataModuleJob";
+
+export default abstract class DeleteByIdJob extends DataModuleJob {
+	protected async _execute({ _id }: { _id: Types.ObjectId }) {
+		const model = await DataModule.getModel(this.getModelName());
+
+		if (!isObjectIdOrHexString(_id))
+			throw new Error("_id is not an ObjectId");
+
+		return model.deleteOne({ _id });
+	}
+}

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

@@ -0,0 +1,13 @@
+import { Types } from "mongoose";
+import DataModule from "../DataModule";
+import DataModuleJob from "./DataModuleJob";
+
+export default abstract class FindByIdJob extends DataModuleJob {
+	protected async _execute({ _id }: { _id: Types.ObjectId }) {
+		const model = await DataModule.getModel(this.getModelName());
+
+		const query = model.findById(_id);
+
+		return query.exec();
+	}
+}

+ 11 - 0
backend/src/modules/DataModule/GetDataJob.ts

@@ -0,0 +1,11 @@
+import DataModule from "../DataModule";
+import DataModuleJob from "./DataModuleJob";
+import { GetData } from "./plugins/getData";
+
+export default abstract class GetDataJob extends DataModuleJob {
+	protected async _execute(payload: Parameters<GetData["getData"]>[0]) {
+		const model = await DataModule.getModel(this.getModelName());
+
+		return model.getData(payload);
+	}
+}

+ 0 - 0
backend/src/modules/DataModule/models/Migration.ts → backend/src/modules/DataModule/Migration.ts


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

@@ -0,0 +1,25 @@
+import { Types, isObjectIdOrHexString } from "mongoose";
+import DataModule from "../DataModule";
+import DataModuleJob from "./DataModuleJob";
+
+export default abstract class UpdateByIdJob extends DataModuleJob {
+	protected async _execute({
+		_id,
+		query
+	}: {
+		_id: Types.ObjectId;
+		query: Record<string, any[]>;
+	}) {
+		const model = await DataModule.getModel(this.getModelName());
+
+		if (!isObjectIdOrHexString(_id))
+			throw new Error("_id is not an ObjectId");
+
+		if (typeof query !== "object")
+			throw new Error("Query is not an object");
+		if (Object.keys(query).length === 0)
+			throw new Error("Empty query object provided");
+
+		return model.updateOne({ _id }, { $set: query });
+	}
+}

+ 6 - 0
backend/src/modules/DataModule/models/abc/jobs/Create.ts

@@ -0,0 +1,6 @@
+import CreateJob from "@/modules/DataModule/CreateJob";
+import { Models } from "@/types/Models";
+
+export default class Create extends CreateJob {
+	protected static _modelName: keyof Models = "abc";
+}

+ 6 - 0
backend/src/modules/DataModule/models/abc/jobs/DeleteById.ts

@@ -0,0 +1,6 @@
+import DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import { Models } from "@/types/Models";
+
+export default class DeleteById extends DeleteByIdJob {
+	protected static _modelName: keyof Models = "abc";
+}

+ 6 - 0
backend/src/modules/DataModule/models/abc/jobs/FindById.ts

@@ -0,0 +1,6 @@
+import FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import { Models } from "@/types/Models";
+
+export default class FindById extends FindByIdJob {
+	protected static _modelName: keyof Models = "abc";
+}

+ 6 - 0
backend/src/modules/DataModule/models/abc/jobs/UpdateById.ts

@@ -0,0 +1,6 @@
+import UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import { Models } from "@/types/Models";
+
+export default class UpdateById extends UpdateByIdJob {
+	protected static _modelName: keyof Models = "abc";
+}

+ 0 - 0
backend/src/modules/DataModule/models/schemas/abc/schema.ts → backend/src/modules/DataModule/models/abc/schema.ts


+ 0 - 0
backend/src/modules/DataModule/models/schemas/news/NewsStatus.ts → backend/src/modules/DataModule/models/news/NewsStatus.ts


+ 18 - 0
backend/src/modules/DataModule/models/news/config.ts

@@ -0,0 +1,18 @@
+import { NewsStatus } from "./NewsStatus";
+import getData from "./getData";
+
+export default {
+	documentVersion: 3,
+	query: {
+		published() {
+			return this.where({ status: NewsStatus.PUBLISHED });
+		},
+		newest(showToNewUsers = false) {
+			const query = this.published().sort({ createdAt: "desc" });
+			if (showToNewUsers)
+				return query.where({ showToNewUsers: !!showToNewUsers });
+			return query;
+		}
+	},
+	getData
+};

+ 0 - 0
backend/src/modules/DataModule/models/schemas/news/getData.ts → backend/src/modules/DataModule/models/news/getData.ts


+ 6 - 0
backend/src/modules/DataModule/models/news/jobs/Create.ts

@@ -0,0 +1,6 @@
+import CreateJob from "@/modules/DataModule/CreateJob";
+import { Models } from "@/types/Models";
+
+export default class Create extends CreateJob {
+	protected static _modelName: keyof Models = "news";
+}

+ 6 - 0
backend/src/modules/DataModule/models/news/jobs/DeleteById.ts

@@ -0,0 +1,6 @@
+import DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import { Models } from "@/types/Models";
+
+export default class DeleteById extends DeleteByIdJob {
+	protected static _modelName: keyof Models = "news";
+}

+ 8 - 0
backend/src/modules/DataModule/models/news/jobs/FindById.ts

@@ -0,0 +1,8 @@
+import FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import { Models } from "@/types/Models";
+
+export default class FindById extends FindByIdJob {
+	protected static _modelName: keyof Models = "news";
+
+	protected static _hasPermission = true;
+}

+ 6 - 0
backend/src/modules/DataModule/models/news/jobs/GetData.ts

@@ -0,0 +1,6 @@
+import GetDataJob from "@/modules/DataModule/GetDataJob";
+import { Models } from "@/types/Models";
+
+export default class GetData extends GetDataJob {
+	protected static _modelName: keyof Models = "news";
+}

+ 22 - 0
backend/src/modules/DataModule/models/news/jobs/Newest.ts

@@ -0,0 +1,22 @@
+import DataModule from "@/modules/DataModule";
+import DataModuleJob from "@/modules/DataModule/DataModuleJob";
+import { Models } from "@/types/Models";
+
+export default class Newest extends DataModuleJob {
+	protected static _modelName: keyof Models = "news";
+
+	protected static _hasPermission = true;
+
+	protected async _execute(payload?: {
+		showToNewUsers?: boolean;
+		limit?: number;
+	}) {
+		const model = await DataModule.getModel(this.getModelName());
+
+		const query = model.find().newest(payload?.showToNewUsers);
+
+		if (payload?.limit) return query.limit(payload?.limit);
+
+		return query;
+	}
+}

+ 15 - 0
backend/src/modules/DataModule/models/news/jobs/Published.ts

@@ -0,0 +1,15 @@
+import DataModule from "@/modules/DataModule";
+import DataModuleJob from "@/modules/DataModule/DataModuleJob";
+import { Models } from "@/types/Models";
+
+export default class Published extends DataModuleJob {
+	protected static _modelName: keyof Models = "news";
+
+	protected static _hasPermission = true;
+
+	protected async _execute() {
+		const model = await DataModule.getModel(this.getModelName());
+
+		return model.find().published();
+	}
+}

+ 6 - 0
backend/src/modules/DataModule/models/news/jobs/UpdateById.ts

@@ -0,0 +1,6 @@
+import UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import { Models } from "@/types/Models";
+
+export default class UpdateById extends UpdateByIdJob {
+	protected static _modelName: keyof Models = "news";
+}

+ 1 - 1
backend/src/modules/DataModule/models/migrations/1620330161000-news-markdown.ts → backend/src/modules/DataModule/models/news/migrations/1620330161000-news-markdown.ts

@@ -1,4 +1,4 @@
-import Migration from "@models/Migration";
+import Migration from "@/modules/DataModule/Migration";
 
 
 export default class Migration1620330161000 extends Migration {
 export default class Migration1620330161000 extends Migration {
 	async up() {
 	async up() {

+ 1 - 1
backend/src/modules/DataModule/models/schemas/news/schema.ts → backend/src/modules/DataModule/models/news/schema.ts

@@ -6,7 +6,7 @@ import {
 	SchemaTypes,
 	SchemaTypes,
 	Types
 	Types
 } from "mongoose";
 } from "mongoose";
-import { GetData } from "@models/plugins/getData";
+import { GetData } from "@/modules/DataModule/plugins/getData";
 import { BaseSchema } from "@/types/Schemas";
 import { BaseSchema } from "@/types/Schemas";
 import JobContext from "@/JobContext";
 import JobContext from "@/JobContext";
 import { NewsStatus } from "./NewsStatus";
 import { NewsStatus } from "./NewsStatus";

+ 0 - 3
backend/src/modules/DataModule/models/permissions/isPrivate.ts

@@ -1,3 +0,0 @@
-import { StationPrivacy } from "@models/schemas/stations/StationPrivacy";
-
-export default model => model && model.privacy === StationPrivacy.PRIVATE;

+ 0 - 3
backend/src/modules/DataModule/models/permissions/isPublic.ts

@@ -1,3 +0,0 @@
-import { StationPrivacy } from "@models/schemas/stations/StationPrivacy";
-
-export default model => model && model.privacy === StationPrivacy.PUBLIC;

+ 0 - 3
backend/src/modules/DataModule/models/permissions/isUnlisted.ts

@@ -1,3 +0,0 @@
-import { StationPrivacy } from "@models/schemas/stations/StationPrivacy";
-
-export default model => model && model.privacy === StationPrivacy.UNLISTED;

+ 0 - 38
backend/src/modules/DataModule/models/schemas/news/config.ts

@@ -1,38 +0,0 @@
-import JobContext from "@/JobContext";
-import { NewsStatus } from "./NewsStatus";
-import getData from "./getData";
-
-export default {
-	documentVersion: 3,
-	query: {
-		published() {
-			return this.where({ status: NewsStatus.PUBLISHED });
-		},
-		newest(showToNewUsers = false) {
-			const query = this.published().sort({ createdAt: "desc" });
-			if (showToNewUsers)
-				return query.where({ showToNewUsers: !!showToNewUsers });
-			return query;
-		}
-	},
-	jobConfig: {
-		published: {
-			async method() {
-				return this.find().published();
-			},
-			hasPermission: true
-		},
-		newest: {
-			async method(
-				context: JobContext,
-				payload?: { showToNewUsers?: boolean; limit?: number }
-			) {
-				const query = this.find().newest(payload?.showToNewUsers);
-				if (payload?.limit) return query.limit(payload?.limit);
-				return query;
-			},
-			hasPermission: true
-		}
-	},
-	getData
-};

+ 0 - 25
backend/src/modules/DataModule/models/schemas/stations/config.ts

@@ -1,25 +0,0 @@
-import isDj from "@models/permissions/isDj";
-import isPublic from "@models/permissions/isPublic";
-import isUnlisted from "@models/permissions/isUnlisted";
-import isLoggedIn from "@models/permissions/isLoggedIn";
-import isOwner from "@models/permissions/isOwner";
-import getData from "./getData";
-
-export default {
-	documentVersion: 10,
-	jobConfig: {
-		create: {
-			hasPermission: isLoggedIn
-		},
-		findById: {
-			hasPermission: [isOwner, isDj, isPublic, isUnlisted]
-		},
-		updateById: {
-			hasPermission: [isOwner, isDj]
-		},
-		deleteById: {
-			hasPermission: [isOwner, isDj]
-		}
-	},
-	getData
-};

+ 6 - 0
backend/src/modules/DataModule/models/sessions/jobs/Create.ts

@@ -0,0 +1,6 @@
+import CreateJob from "@/modules/DataModule/CreateJob";
+import { Models } from "@/types/Models";
+
+export default class Create extends CreateJob {
+	protected static _modelName: keyof Models = "sessions";
+}

+ 6 - 0
backend/src/modules/DataModule/models/sessions/jobs/DeleteById.ts

@@ -0,0 +1,6 @@
+import DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import { Models } from "@/types/Models";
+
+export default class DeleteById extends DeleteByIdJob {
+	protected static _modelName: keyof Models = "sessions";
+}

+ 6 - 0
backend/src/modules/DataModule/models/sessions/jobs/FindById.ts

@@ -0,0 +1,6 @@
+import FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import { Models } from "@/types/Models";
+
+export default class FindById extends FindByIdJob {
+	protected static _modelName: keyof Models = "sessions";
+}

+ 6 - 0
backend/src/modules/DataModule/models/sessions/jobs/UpdateById.ts

@@ -0,0 +1,6 @@
+import UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import { Models } from "@/types/Models";
+
+export default class UpdateById extends UpdateByIdJob {
+	protected static _modelName: keyof Models = "sessions";
+}

+ 10 - 7
backend/src/modules/DataModule/models/schemas/sessions/schema.ts → backend/src/modules/DataModule/models/sessions/schema.ts

@@ -7,12 +7,15 @@ export interface SessionSchema extends BaseSchema {
 
 
 export type SessionModel = Model<SessionSchema>;
 export type SessionModel = Model<SessionSchema>;
 
 
-export const schema = new Schema<SessionSchema, SessionModel>({
-	userId: {
-		type: SchemaTypes.ObjectId,
-		ref: "users",
-		required: true
-	}
-});
+export const schema = new Schema<SessionSchema, SessionModel>(
+	{
+		userId: {
+			type: SchemaTypes.ObjectId,
+			ref: "users",
+			required: true
+		}
+	},
+	{ patchHistory: { enabled: false } }
+);
 
 
 export type SessionSchemaType = typeof schema;
 export type SessionSchemaType = typeof schema;

+ 0 - 0
backend/src/modules/DataModule/models/schemas/stations/StationAutofillMode.ts → backend/src/modules/DataModule/models/stations/StationAutofillMode.ts


+ 0 - 0
backend/src/modules/DataModule/models/schemas/stations/StationPrivacy.ts → backend/src/modules/DataModule/models/stations/StationPrivacy.ts


+ 0 - 0
backend/src/modules/DataModule/models/schemas/stations/StationRequestsAccess.ts → backend/src/modules/DataModule/models/stations/StationRequestsAccess.ts


+ 0 - 0
backend/src/modules/DataModule/models/schemas/stations/StationTheme.ts → backend/src/modules/DataModule/models/stations/StationTheme.ts


+ 0 - 0
backend/src/modules/DataModule/models/schemas/stations/StationType.ts → backend/src/modules/DataModule/models/stations/StationType.ts


+ 6 - 0
backend/src/modules/DataModule/models/stations/config.ts

@@ -0,0 +1,6 @@
+import getData from "./getData";
+
+export default {
+	documentVersion: 10,
+	getData
+};

+ 0 - 0
backend/src/modules/DataModule/models/schemas/stations/getData.ts → backend/src/modules/DataModule/models/stations/getData.ts


+ 9 - 0
backend/src/modules/DataModule/models/stations/jobs/Create.ts

@@ -0,0 +1,9 @@
+import CreateJob from "@/modules/DataModule/CreateJob";
+import isLoggedIn from "@/modules/DataModule/permissions/isLoggedIn";
+import { Models } from "@/types/Models";
+
+export default class Create extends CreateJob {
+	protected static _modelName: keyof Models = "stations";
+
+	protected static _hasPermission = isLoggedIn;
+}

+ 9 - 0
backend/src/modules/DataModule/models/stations/jobs/DeleteById.ts

@@ -0,0 +1,9 @@
+import DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import isOwner from "@/modules/DataModule/permissions/isOwner";
+import { Models } from "@/types/Models";
+
+export default class DeleteById extends DeleteByIdJob {
+	protected static _modelName: keyof Models = "stations";
+
+	protected static _hasPermission = isOwner;
+}

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

@@ -0,0 +1,12 @@
+import FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import { Models } from "@/types/Models";
+import isDj from "@/modules/DataModule/permissions/isDj";
+import isPublic from "@/modules/DataModule/permissions/isPublic";
+import isUnlisted from "@/modules/DataModule/permissions/isUnlisted";
+import isOwner from "@/modules/DataModule/permissions/isOwner";
+
+export default class FindById extends FindByIdJob {
+	protected static _modelName: keyof Models = "stations";
+
+	protected static _hasPermission = [isOwner, isDj, isPublic, isUnlisted];
+}

+ 6 - 0
backend/src/modules/DataModule/models/stations/jobs/GetData.ts

@@ -0,0 +1,6 @@
+import GetDataJob from "@/modules/DataModule/GetDataJob";
+import { Models } from "@/types/Models";
+
+export default class GetData extends GetDataJob {
+	protected static _modelName: keyof Models = "stations";
+}

+ 10 - 0
backend/src/modules/DataModule/models/stations/jobs/UpdateById.ts

@@ -0,0 +1,10 @@
+import UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import isDj from "@/modules/DataModule/permissions/isDj";
+import isOwner from "@/modules/DataModule/permissions/isOwner";
+import { Models } from "@/types/Models";
+
+export default class UpdateById extends UpdateByIdJob {
+	protected static _modelName: keyof Models = "stations";
+
+	protected static _hasPermission = [isOwner, isDj];
+}

+ 1 - 1
backend/src/modules/DataModule/models/schemas/stations/schema.ts → backend/src/modules/DataModule/models/stations/schema.ts

@@ -1,5 +1,5 @@
 import { Model, Schema, SchemaTypes, Types } from "mongoose";
 import { Model, Schema, SchemaTypes, Types } from "mongoose";
-import { GetData } from "@models/plugins/getData";
+import { GetData } from "@/modules/DataModule/plugins/getData";
 import { BaseSchema } from "@/types/Schemas";
 import { BaseSchema } from "@/types/Schemas";
 import { StationType } from "./StationType";
 import { StationType } from "./StationType";
 import { StationPrivacy } from "./StationPrivacy";
 import { StationPrivacy } from "./StationPrivacy";

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


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


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


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


+ 6 - 0
backend/src/modules/DataModule/models/users/jobs/Create.ts

@@ -0,0 +1,6 @@
+import CreateJob from "@/modules/DataModule/CreateJob";
+import { Models } from "@/types/Models";
+
+export default class Create extends CreateJob {
+	protected static _modelName: keyof Models = "users";
+}

+ 6 - 0
backend/src/modules/DataModule/models/users/jobs/DeleteById.ts

@@ -0,0 +1,6 @@
+import DeleteByIdJob from "@/modules/DataModule/DeleteByIdJob";
+import { Models } from "@/types/Models";
+
+export default class DeleteById extends DeleteByIdJob {
+	protected static _modelName: keyof Models = "users";
+}

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

@@ -0,0 +1,6 @@
+import FindByIdJob from "@/modules/DataModule/FindByIdJob";
+import { Models } from "@/types/Models";
+
+export default class FindById extends FindByIdJob {
+	protected static _modelName: keyof Models = "users";
+}

+ 76 - 0
backend/src/modules/DataModule/models/users/jobs/GetModelPermissions.ts

@@ -0,0 +1,76 @@
+import { Types } from "mongoose";
+import CacheModule from "@/modules/CacheModule";
+import DataModule from "@/modules/DataModule";
+import { Models, Models } from "@/types/Models";
+import ModuleManager from "@/ModuleManager";
+import GetPermissions from "./GetPermissions";
+import DataModuleJob from "@/modules/DataModule/DataModuleJob";
+
+export default class GetModelPermissions extends DataModuleJob {
+	protected static _modelName: keyof Models = "users";
+
+	protected override async _authorize() {}
+
+	protected async _execute({
+		modelName,
+		modelId
+	}: {
+		modelName: keyof Models;
+		modelId?: Types.ObjectId;
+	}) {
+		const user = await this._context.getUser().catch(() => null);
+		const permissions = await this._context.executeJob(GetPermissions);
+
+		let cacheKey = `model-permissions.${modelName}`;
+
+		if (modelId) cacheKey += `.${modelId}`;
+
+		if (user) cacheKey += `.user.${user._id}`;
+		else cacheKey += `.guest`;
+
+		const cached = await CacheModule.get(cacheKey);
+
+		if (cached) return cached;
+
+		const Model = await DataModule.getModel(modelName);
+
+		if (!Model) throw new Error("Model not found");
+
+		const model = modelId ? await Model.findById(modelId) : null;
+
+		if (modelId && !model) throw new Error("Model not found");
+
+		const jobs = await Promise.all(
+			Object.entries(ModuleManager.getModule("data")?.getJobs() ?? {})
+				.filter(
+					([jobName]) =>
+						jobName.startsWith(modelName.toString()) &&
+						(modelId ? true : !jobName.endsWith("ById"))
+				)
+				.map(async ([jobName, Job]) => {
+					jobName = `data.${jobName}`;
+
+					let hasPermission = permissions[jobName];
+
+					if (!hasPermission && modelId)
+						hasPermission =
+							permissions[`${jobName}.*`] ||
+							permissions[`${jobName}.${modelId}`];
+
+					if (hasPermission) return [jobName, true];
+
+					if (typeof Job.hasPermission === "function") {
+						hasPermission = await Job.hasPermission(model, user);
+					}
+
+					return [jobName, !!hasPermission];
+				})
+		);
+
+		const modelPermissions = Object.fromEntries(jobs);
+
+		await CacheModule.set(cacheKey, modelPermissions, 360);
+
+		return modelPermissions;
+	}
+}

+ 35 - 0
backend/src/modules/DataModule/models/users/jobs/GetPermissions.ts

@@ -0,0 +1,35 @@
+import CacheModule from "@/modules/CacheModule";
+import { Models } from "@/types/Models";
+import permissions from "@/permissions";
+import { UserRole } from "../UserRole";
+import DataModuleJob from "@/modules/DataModule/DataModuleJob";
+
+export default class GetPermissions extends DataModuleJob {
+	protected static _modelName: keyof Models = "users";
+
+	protected override async _authorize() {}
+
+	protected async _execute() {
+		const user = await this._context.getUser().catch(() => null);
+
+		if (!user) return permissions.guest;
+
+		const cacheKey = `user-permissions.${user._id}`;
+
+		const cached = await CacheModule.get(cacheKey);
+
+		if (cached) return cached;
+
+		const roles: UserRole[] = [user.role];
+
+		let rolePermissions: Record<string, boolean> = {};
+		roles.forEach(role => {
+			if (permissions[role])
+				rolePermissions = { ...rolePermissions, ...permissions[role] };
+		});
+
+		await CacheModule.set(cacheKey, rolePermissions, 360);
+
+		return rolePermissions;
+	}
+}

+ 6 - 0
backend/src/modules/DataModule/models/users/jobs/UpdateById.ts

@@ -0,0 +1,6 @@
+import UpdateByIdJob from "@/modules/DataModule/UpdateByIdJob";
+import { Models } from "@/types/Models";
+
+export default class UpdateById extends UpdateByIdJob {
+	protected static _modelName: keyof Models = "users";
+}

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


+ 0 - 0
backend/src/modules/DataModule/models/permissions/isDj.ts → backend/src/modules/DataModule/permissions/isDj.ts


+ 0 - 0
backend/src/modules/DataModule/models/permissions/isLoggedIn.ts → backend/src/modules/DataModule/permissions/isLoggedIn.ts


+ 0 - 0
backend/src/modules/DataModule/models/permissions/isOwner.ts → backend/src/modules/DataModule/permissions/isOwner.ts


+ 3 - 0
backend/src/modules/DataModule/permissions/isPrivate.ts

@@ -0,0 +1,3 @@
+import { StationPrivacy } from "@/modules/DataModule/models/stations/StationPrivacy";
+
+export default model => model && model.privacy === StationPrivacy.PRIVATE;

+ 3 - 0
backend/src/modules/DataModule/permissions/isPublic.ts

@@ -0,0 +1,3 @@
+import { StationPrivacy } from "@/modules/DataModule/models/stations/StationPrivacy";
+
+export default model => model && model.privacy === StationPrivacy.PUBLIC;

+ 3 - 0
backend/src/modules/DataModule/permissions/isUnlisted.ts

@@ -0,0 +1,3 @@
+import { StationPrivacy } from "@/modules/DataModule/models/stations/StationPrivacy";
+
+export default model => model && model.privacy === StationPrivacy.UNLISTED;

+ 0 - 0
backend/src/modules/DataModule/models/plugins/documentVersion.ts → backend/src/modules/DataModule/plugins/documentVersion.ts


+ 0 - 10
backend/src/modules/DataModule/models/plugins/getData.ts → backend/src/modules/DataModule/plugins/getData.ts

@@ -242,14 +242,4 @@ export default function getDataPlugin(schema: Schema) {
 			return { data, count };
 			return { data, count };
 		}
 		}
 	);
 	);
-
-	schema.set("jobConfig", {
-		async getData(
-			context: JobContext,
-			payload: Parameters<GetData["getData"]>[0]
-		) {
-			return this.getData(payload);
-		},
-		...(schema.get("jobConfig") ?? {})
-	});
 }
 }

+ 75 - 38
backend/src/modules/EventsModule.ts

@@ -3,7 +3,7 @@ import config from "config";
 import crypto from "node:crypto";
 import crypto from "node:crypto";
 import BaseModule, { ModuleStatus } from "@/BaseModule";
 import BaseModule, { ModuleStatus } from "@/BaseModule";
 import { UniqueMethods } from "@/types/Modules";
 import { UniqueMethods } from "@/types/Modules";
-import JobContext from "@/JobContext";
+import WebSocketModule from "./WebSocketModule";
 
 
 export class EventsModule extends BaseModule {
 export class EventsModule extends BaseModule {
 	private _pubClient?: RedisClientType;
 	private _pubClient?: RedisClientType;
@@ -12,6 +12,8 @@ export class EventsModule extends BaseModule {
 
 
 	private _subscriptions: Record<string, ((message: any) => Promise<void>)[]>;
 	private _subscriptions: Record<string, ((message: any) => Promise<void>)[]>;
 
 
+	private _socketSubscriptions: Record<string, Set<string>>;
+
 	private _scheduleCallbacks: Record<string, (() => Promise<void>)[]>;
 	private _scheduleCallbacks: Record<string, (() => Promise<void>)[]>;
 
 
 	/**
 	/**
@@ -21,8 +23,8 @@ export class EventsModule extends BaseModule {
 		super("events");
 		super("events");
 
 
 		this._subscriptions = {};
 		this._subscriptions = {};
+		this._socketSubscriptions = {};
 		this._scheduleCallbacks = {};
 		this._scheduleCallbacks = {};
-		this._jobConfigDefault = "disabled";
 	}
 	}
 
 
 	/**
 	/**
@@ -145,10 +147,7 @@ export class EventsModule extends BaseModule {
 		if (!channel || typeof channel !== "string")
 		if (!channel || typeof channel !== "string")
 			throw new Error("Invalid channel");
 			throw new Error("Invalid channel");
 
 
-		return crypto
-			.createHash("md5")
-			.update(`${type}:${channel}`)
-			.digest("hex");
+		return `${type}:${channel}`;
 	}
 	}
 
 
 	/**
 	/**
@@ -157,20 +156,20 @@ export class EventsModule extends BaseModule {
 	public async publish(channel: string, value: any) {
 	public async publish(channel: string, value: any) {
 		if (!this._pubClient) throw new Error("Redis pubClient unavailable.");
 		if (!this._pubClient) throw new Error("Redis pubClient unavailable.");
 
 
-		channel = this._createKey("event", channel);
-
 		if (!value) throw new Error("Invalid value");
 		if (!value) throw new Error("Invalid value");
 
 
 		if (["object", "array"].includes(typeof value))
 		if (["object", "array"].includes(typeof value))
 			value = JSON.stringify(value);
 			value = JSON.stringify(value);
 
 
-		await this._pubClient.publish(channel, value);
+		await this._pubClient.publish(this._createKey("event", channel), value);
 	}
 	}
 
 
 	/**
 	/**
 	 * subscriptionListener - Listener for event subscriptions
 	 * subscriptionListener - Listener for event subscriptions
 	 */
 	 */
-	private async _subscriptionListener(message: string, channel: string) {
+	private async _subscriptionListener(message: string, key: string) {
+		const [, channel] = key.split(":");
+
 		if (!this._subscriptions || !this._subscriptions[channel]) return;
 		if (!this._subscriptions || !this._subscriptions[channel]) return;
 
 
 		if (message.startsWith("[") || message.startsWith("{"))
 		if (message.startsWith("[") || message.startsWith("{"))
@@ -185,6 +184,14 @@ export class EventsModule extends BaseModule {
 		await Promise.all(
 		await Promise.all(
 			this._subscriptions[channel].map(async cb => cb(message))
 			this._subscriptions[channel].map(async cb => cb(message))
 		);
 		);
+
+		if (!this._socketSubscriptions[channel]) return;
+
+		for await (const socketId of this._socketSubscriptions[
+			channel
+		].values()) {
+			await WebSocketModule.dispatch(socketId, channel, message);
+		}
 	}
 	}
 
 
 	/**
 	/**
@@ -193,21 +200,11 @@ export class EventsModule extends BaseModule {
 	public async subscribe(
 	public async subscribe(
 		type: "event" | "schedule",
 		type: "event" | "schedule",
 		channel: string,
 		channel: string,
-		callback: (message?: any) => Promise<void>,
-		unique = false
+		callback: (message?: any) => Promise<void>
 	) {
 	) {
 		if (!this._subClient) throw new Error("Redis subClient unavailable.");
 		if (!this._subClient) throw new Error("Redis subClient unavailable.");
 
 
-		channel = this._createKey(type, channel);
-
 		if (type === "schedule") {
 		if (type === "schedule") {
-			if (
-				unique &&
-				this._scheduleCallbacks[channel] &&
-				this._scheduleCallbacks[channel].length > 0
-			)
-				return;
-
 			if (!this._scheduleCallbacks[channel])
 			if (!this._scheduleCallbacks[channel])
 				this._scheduleCallbacks[channel] = [];
 				this._scheduleCallbacks[channel] = [];
 
 
@@ -216,18 +213,12 @@ export class EventsModule extends BaseModule {
 			return;
 			return;
 		}
 		}
 
 
-		if (
-			unique &&
-			this._subscriptions[channel] &&
-			this._subscriptions[channel].length > 0
-		)
-			return;
-
 		if (!this._subscriptions[channel]) {
 		if (!this._subscriptions[channel]) {
 			this._subscriptions[channel] = [];
 			this._subscriptions[channel] = [];
 
 
-			await this._subClient.subscribe(channel, (...args) =>
-				this._subscriptionListener(...args)
+			await this._subClient.subscribe(
+				this._createKey(type, channel),
+				(...args) => this._subscriptionListener(...args)
 			);
 			);
 		}
 		}
 
 
@@ -244,8 +235,6 @@ export class EventsModule extends BaseModule {
 	) {
 	) {
 		if (!this._subClient) throw new Error("Redis subClient unavailable.");
 		if (!this._subClient) throw new Error("Redis subClient unavailable.");
 
 
-		channel = this._createKey(type, channel);
-
 		if (type === "schedule") {
 		if (type === "schedule") {
 			if (!this._scheduleCallbacks[channel]) return;
 			if (!this._scheduleCallbacks[channel]) return;
 
 
@@ -270,7 +259,7 @@ export class EventsModule extends BaseModule {
 
 
 		if (this._subscriptions[channel].length === 0) {
 		if (this._subscriptions[channel].length === 0) {
 			delete this._subscriptions[channel];
 			delete this._subscriptions[channel];
-			await this._subClient.unsubscribe(channel); // TODO: Provide callback when unsubscribing
+			await this._subClient.unsubscribe(this._createKey(type, channel)); // TODO: Provide callback when unsubscribing
 		}
 		}
 	}
 	}
 
 
@@ -286,9 +275,10 @@ export class EventsModule extends BaseModule {
 
 
 		if (time <= 0) throw new Error("Time must be greater than 0");
 		if (time <= 0) throw new Error("Time must be greater than 0");
 
 
-		channel = this._createKey("schedule", channel);
-
-		await this._pubClient.set(channel, "", { PX: time, NX: true });
+		await this._pubClient.set(this._createKey("schedule", channel), "", {
+			PX: time,
+			NX: true
+		});
 	}
 	}
 
 
 	/**
 	/**
@@ -297,9 +287,56 @@ export class EventsModule extends BaseModule {
 	public async unschedule(channel: string) {
 	public async unschedule(channel: string) {
 		if (!this._pubClient) throw new Error("Redis pubClient unavailable.");
 		if (!this._pubClient) throw new Error("Redis pubClient unavailable.");
 
 
-		channel = this._createKey("schedule", channel);
+		await this._pubClient.del(this._createKey("schedule", channel));
+	}
+
+	public async subscribeSocket(channel: string, socketId: string) {
+		if (!this._socketSubscriptions[channel]) {
+			await this.subscribe("event", channel, () => {});
+
+			this._socketSubscriptions[channel] = new Set();
+		}
+
+		if (this._socketSubscriptions[channel].has(socketId)) return;
 
 
-		await this._pubClient.del(channel);
+		this._socketSubscriptions[channel].add(socketId);
+	}
+
+	public async subscribeManySocket(channels: string[], socketId: string) {
+		await Promise.all(
+			channels.map(channel => this.subscribeSocket(channel, socketId))
+		);
+	}
+
+	public async unsubscribeSocket(channel: string, socketId: string) {
+		if (
+			!(
+				this._socketSubscriptions[channel] &&
+				this._socketSubscriptions[channel].has(socketId)
+			)
+		)
+			return;
+
+		this._socketSubscriptions[channel].delete(socketId);
+
+		if (this._socketSubscriptions[channel].size === 0)
+			delete this._socketSubscriptions[channel];
+	}
+
+	public async unsubscribeManySocket(channels: string[], socketId: string) {
+		await Promise.all(
+			channels.map(channel => this.unsubscribeSocket(channel, socketId))
+		);
+	}
+
+	public async unsubscribeAllSocket(socketId: string) {
+		await Promise.all(
+			Object.entries(this._socketSubscriptions)
+				.filter(([, socketIds]) => socketIds.has(socketId))
+				.map(async ([channel]) =>
+					this.unsubscribeSocket(channel, socketId)
+				)
+		);
 	}
 	}
 
 
 	/**
 	/**

+ 41 - 0
backend/src/modules/EventsModule/jobs/Subscribe.ts

@@ -0,0 +1,41 @@
+import Job from "@/Job";
+import EventsModule from "@/modules/EventsModule";
+import { JobOptions } from "@/types/JobOptions";
+
+export default class Subscribe extends Job {
+	public constructor(
+		payload?: any,
+		options?: Omit<JobOptions, "runDirectly">
+	) {
+		super(EventsModule, payload, options);
+	}
+
+	protected override async _authorize() {
+		const [, moduleName, modelName, event, modelId] =
+			/^([a-z]+)\.([a-z]+)\.([A-z]+)\.?([A-z0-9]+)?$/.exec(
+				this._payload?.channel
+			) ?? [];
+
+		let permission = `event.${this._payload?.channel}`;
+
+		if (
+			moduleName === "model" &&
+			modelName &&
+			(modelId || event === "created")
+		) {
+			if (event === "created")
+				permission = `event.model.${modelName}.created`;
+			else permission = `data.${modelName}.findById.${modelId}`;
+		}
+
+		await this._context.assertPermission(permission);
+	}
+
+	protected async _execute({ channel }: { channel: string }) {
+		const socketId = this._context.getSocketId();
+
+		if (!socketId) throw new Error("No socketId specified");
+
+		await EventsModule.subscribeSocket(channel, socketId);
+	}
+}

+ 45 - 0
backend/src/modules/EventsModule/jobs/SubscribeMany.ts

@@ -0,0 +1,45 @@
+import Job from "@/Job";
+import EventsModule from "@/modules/EventsModule";
+import { JobOptions } from "@/types/JobOptions";
+
+export default class SubscribeMany extends Job {
+	public constructor(
+		payload?: any,
+		options?: Omit<JobOptions, "runDirectly">
+	) {
+		super(EventsModule, payload, options);
+	}
+
+	protected override async _authorize() {
+		await Promise.all(
+			this._payload.channels.map(async channel => {
+				const [, moduleName, modelName, event, modelId] =
+					/^([a-z]+)\.([a-z]+)\.([A-z]+)\.?([A-z0-9]+)?$/.exec(
+						channel
+					) ?? [];
+
+				let permission = `event.${channel}`;
+
+				if (
+					moduleName === "model" &&
+					modelName &&
+					(modelId || event === "created")
+				) {
+					if (event === "created")
+						permission = `event.model.${modelName}.created`;
+					else permission = `data.${modelName}.findById.${modelId}`;
+				}
+
+				await this._context.assertPermission(permission);
+			})
+		);
+	}
+
+	protected async _execute({ channels }: { channels: string[] }) {
+		const socketId = this._context.getSocketId();
+
+		if (!socketId) throw new Error("No socketId specified");
+
+		await EventsModule.subscribeManySocket(channels, socketId);
+	}
+}

+ 22 - 0
backend/src/modules/EventsModule/jobs/Unsubscribe.ts

@@ -0,0 +1,22 @@
+import Job from "@/Job";
+import EventsModule from "@/modules/EventsModule";
+import { JobOptions } from "@/types/JobOptions";
+
+export default class Unsubscribe extends Job {
+	public constructor(
+		payload?: any,
+		options?: Omit<JobOptions, "runDirectly">
+	) {
+		super(EventsModule, payload, options);
+	}
+
+	protected override async _authorize() {}
+
+	protected async _execute({ channel }: { channel: string }) {
+		const socketId = this._context.getSocketId();
+
+		if (!socketId) throw new Error("No socketId specified");
+
+		await EventsModule.unsubscribeSocket(channel, socketId);
+	}
+}

+ 22 - 0
backend/src/modules/EventsModule/jobs/UnsubscribeAll.ts

@@ -0,0 +1,22 @@
+import Job from "@/Job";
+import EventsModule from "@/modules/EventsModule";
+import { JobOptions } from "@/types/JobOptions";
+
+export default class UnsubscribeAll extends Job {
+	public constructor(
+		payload?: any,
+		options?: Omit<JobOptions, "runDirectly">
+	) {
+		super(EventsModule, payload, options);
+	}
+
+	protected override async _authorize() {}
+
+	protected async _execute() {
+		const socketId = this._context.getSocketId();
+
+		if (!socketId) throw new Error("No socketId specified");
+
+		await EventsModule.unsubscribeAllSocket(socketId);
+	}
+}

+ 22 - 0
backend/src/modules/EventsModule/jobs/UnsubscribeMany.ts

@@ -0,0 +1,22 @@
+import Job from "@/Job";
+import EventsModule from "@/modules/EventsModule";
+import { JobOptions } from "@/types/JobOptions";
+
+export default class UnsubscribeMany extends Job {
+	public constructor(
+		payload?: any,
+		options?: Omit<JobOptions, "runDirectly">
+	) {
+		super(EventsModule, payload, options);
+	}
+
+	protected override async _authorize() {}
+
+	protected async _execute({ channels }: { channels: string[] }) {
+		const socketId = this._context.getSocketId();
+
+		if (!socketId) throw new Error("No socketId specified");
+
+		await EventsModule.unsubscribeManySocket(channels, socketId);
+	}
+}

+ 0 - 37
backend/src/modules/StationsModule.ts

@@ -1,7 +1,5 @@
-import JobContext from "@/JobContext";
 import { UniqueMethods } from "@/types/Modules";
 import { UniqueMethods } from "@/types/Modules";
 import BaseModule from "@/BaseModule";
 import BaseModule from "@/BaseModule";
-import JobQueue from "@/JobQueue";
 
 
 export class StationsModule extends BaseModule {
 export class StationsModule extends BaseModule {
 	/**
 	/**
@@ -29,41 +27,6 @@ export class StationsModule extends BaseModule {
 		await super.shutdown();
 		await super.shutdown();
 		await super._stopped();
 		await super._stopped();
 	}
 	}
-
-	/**
-	 * addToQueue - Add media to queue
-	 *
-	 * @param payload - Payload
-	 */
-	public async addToQueue(context: JobContext, payload: { songId: string }) {
-		const { songId } = payload;
-		context.log(`Adding song ${songId} to the queue.`);
-		await new Promise((resolve, reject) => {
-			setTimeout(() => {
-				if (Math.round(Math.random())) reject(new Error("Test321"));
-				else resolve(true);
-			}, Math.random() * 1000);
-		});
-	}
-
-	public async addA(context: JobContext) {
-		context.log("ADDA");
-		await JobQueue.runJob("stations", "addB", {}, { priority: 5 });
-		return { number: 123 };
-	}
-
-	public async addB(context: JobContext) {
-		context.log("ADDB");
-		await JobQueue.runJob("stations", "addToQueue", {
-			songId: "test"
-		});
-		await JobQueue.runJob("stations", "addC", {});
-	}
-
-	public async addC(context: JobContext) {
-		context.log("ADDC");
-		// await new Promise(() => {});
-	}
 }
 }
 
 
 export type StationsModuleJobs = {
 export type StationsModuleJobs = {

+ 4 - 10
backend/src/modules/WebSocketModule.ts

@@ -6,8 +6,6 @@ import { Types, isObjectIdOrHexString } from "mongoose";
 import BaseModule from "@/BaseModule";
 import BaseModule from "@/BaseModule";
 import { UniqueMethods } from "@/types/Modules";
 import { UniqueMethods } from "@/types/Modules";
 import WebSocket from "@/WebSocket";
 import WebSocket from "@/WebSocket";
-import JobContext from "@/JobContext";
-import Job from "@/Job";
 import ModuleManager from "@/ModuleManager";
 import ModuleManager from "@/ModuleManager";
 import JobQueue from "@/JobQueue";
 import JobQueue from "@/JobQueue";
 import DataModule from "./DataModule";
 import DataModule from "./DataModule";
@@ -24,11 +22,6 @@ export class WebSocketModule extends BaseModule {
 	 */
 	 */
 	public constructor() {
 	public constructor() {
 		super("websocket");
 		super("websocket");
-
-		this._jobConfigDefault = "disabled";
-		this._jobConfig = {
-			dispatch: true
-		};
 	}
 	}
 
 
 	/**
 	/**
@@ -181,7 +174,7 @@ export class WebSocketModule extends BaseModule {
 
 
 		socket.on("close", async () => {
 		socket.on("close", async () => {
 			await JobQueue.runJob(
 			await JobQueue.runJob(
-				"api",
+				"events",
 				"unsubscribeAll",
 				"unsubscribeAll",
 				{},
 				{},
 				{
 				{
@@ -231,8 +224,9 @@ export class WebSocketModule extends BaseModule {
 			const module = ModuleManager.getModule(moduleName);
 			const module = ModuleManager.getModule(moduleName);
 			if (!module) throw new Error(`Module "${moduleName}" not found`);
 			if (!module) throw new Error(`Module "${moduleName}" not found`);
 
 
-			const job = module.getJob(jobName);
-			if (!job.api) throw new Error(`Job "${jobName}" not found.`);
+			const Job = module.getJob(jobName);
+			if (!Job?.isApiEnabled())
+				throw new Error(`Job "${jobName}" not found.`);
 
 
 			let session;
 			let session;
 			if (socket.getSessionId()) {
 			if (socket.getSessionId()) {

+ 5 - 5
backend/src/types/Models.ts

@@ -1,8 +1,8 @@
-import { AbcModel } from "@models/schemas/abc/schema";
-import { NewsModel } from "@models/schemas/news/schema";
-import { SessionModel } from "@models/schemas/sessions/schema";
-import { StationModel } from "@models/schemas/stations/schema";
-import { UserModel } from "@models/schemas/users/schema";
+import { NewsModel } from "@models/news/schema";
+import { SessionModel } from "@models/sessions/schema";
+import { StationModel } from "@models/stations/schema";
+import { UserModel } from "@models/users/schema";
+import { AbcModel } from "@models/abc/schema";
 
 
 export type Models = {
 export type Models = {
 	abc: AbcModel;
 	abc: AbcModel;

+ 0 - 5
backend/src/types/Modules.ts

@@ -1,4 +1,3 @@
-import { APIModule, APIModuleJobs } from "@/modules/APIModule";
 import { CacheModule, CacheModuleJobs } from "@/modules/CacheModule";
 import { CacheModule, CacheModuleJobs } from "@/modules/CacheModule";
 import { DataModule, DataModuleJobs } from "@/modules/DataModule";
 import { DataModule, DataModuleJobs } from "@/modules/DataModule";
 import { EventsModule, EventsModuleJobs } from "@/modules/EventsModule";
 import { EventsModule, EventsModuleJobs } from "@/modules/EventsModule";
@@ -16,9 +15,6 @@ export type ModuleClass<Module extends typeof BaseModule> = {
 };
 };
 
 
 export type Jobs = {
 export type Jobs = {
-	api: {
-		[Property in keyof APIModuleJobs]: APIModuleJobs[Property];
-	};
 	cache: {
 	cache: {
 		[Property in keyof CacheModuleJobs]: CacheModuleJobs[Property];
 		[Property in keyof CacheModuleJobs]: CacheModuleJobs[Property];
 	};
 	};
@@ -37,7 +33,6 @@ export type Jobs = {
 };
 };
 
 
 export type Modules = {
 export type Modules = {
-	api: APIModule & typeof BaseModule;
 	cache: CacheModule & typeof BaseModule;
 	cache: CacheModule & typeof BaseModule;
 	data: DataModule & typeof BaseModule;
 	data: DataModule & typeof BaseModule;
 	events: EventsModule & typeof BaseModule;
 	events: EventsModule & typeof BaseModule;

+ 6 - 6
backend/src/types/Schemas.ts

@@ -1,10 +1,10 @@
 import { Types } from "mongoose";
 import { Types } from "mongoose";
-import { AbcSchemaType } from "@models/schemas/abc/schema";
-import { NewsSchemaType } from "@models/schemas/news/schema";
-import { SessionSchemaType } from "@models/schemas/session/schema";
-import { StationSchemaType } from "@models/schemas/station/schema";
-import { UserSchemaType } from "@models/schemas/user/schema";
-import { DocumentVersion } from "@models/plugins/documentVersion";
+import { NewsSchemaType } from "@models/news/schema";
+import { SessionSchemaType } from "@models/sessions/schema";
+import { StationSchemaType } from "@models/stations/schema";
+import { UserSchemaType } from "@models/users/schema";
+import { AbcSchemaType } from "@models/abc/schema";
+import { DocumentVersion } from "@/modules/DataModule/plugins/documentVersion";
 
 
 // eslint-disable-next-line
 // eslint-disable-next-line
 export interface BaseSchema extends DocumentVersion, TimestampsSchema {
 export interface BaseSchema extends DocumentVersion, TimestampsSchema {

+ 1 - 1
frontend/src/Model.ts

@@ -174,7 +174,7 @@ export default class Model {
 
 
 		const { runJob } = useWebsocketStore();
 		const { runJob } = useWebsocketStore();
 
 
-		this._permissions = await runJob("api.getUserModelPermissions", {
+		this._permissions = await runJob("data.users.getModelPermissions", {
 			modelName: this._name,
 			modelName: this._name,
 			modelId: this._id
 			modelId: this._id
 		});
 		});

+ 1 - 1
frontend/src/stores/model.ts

@@ -19,7 +19,7 @@ export const useModelStore = defineStore("model", () => {
 	const getUserModelPermissions = async (modelName: string) => {
 	const getUserModelPermissions = async (modelName: string) => {
 		if (permissions.value) return permissions.value;
 		if (permissions.value) return permissions.value;
 
 
-		const data = await runJob("api.getUserModelPermissions", {
+		const data = await runJob("data.users.getModelPermissions", {
 			modelName
 			modelName
 		});
 		});
 
 

+ 2 - 2
frontend/src/stores/userAuth.ts

@@ -173,7 +173,7 @@ export const useUserAuthStore = defineStore("userAuth", () => {
 			requestingUserId(userId);
 			requestingUserId(userId);
 
 
 			websocketStore
 			websocketStore
-				.runJob("users.getBasicUser", { _id: userId })
+				.runJob("data.users.findById", { _id: userId })
 				.then(user => {
 				.then(user => {
 					mapUserId({
 					mapUserId({
 						userId,
 						userId,
@@ -200,7 +200,7 @@ export const useUserAuthStore = defineStore("userAuth", () => {
 		!!(permissions.value && permissions.value[permission]);
 		!!(permissions.value && permissions.value[permission]);
 
 
 	const updatePermissions = () =>
 	const updatePermissions = () =>
-		websocketStore.runJob("api.getUserPermissions", {}).then(data => {
+		websocketStore.runJob("data.users.getPermissions", {}).then(data => {
 			permissions.value = data;
 			permissions.value = data;
 			gotPermissions.value = true;
 			gotPermissions.value = true;
 		});
 		});

+ 7 - 7
frontend/src/stores/websocket.ts

@@ -46,7 +46,7 @@ export const useWebsocketStore = defineStore("websocket", () => {
 			-1
 			-1
 		) {
 		) {
 			socketChannels[channel] = "subscribing";
 			socketChannels[channel] = "subscribing";
-			await runJob("api.subscribe", { channel });
+			await runJob("events.subscribe", { channel });
 			socketChannels[channel] = "subscribed";
 			socketChannels[channel] = "subscribed";
 		}
 		}
 
 
@@ -71,7 +71,7 @@ export const useWebsocketStore = defineStore("websocket", () => {
 		channelsToSubscribeTo.forEach(channel => {
 		channelsToSubscribeTo.forEach(channel => {
 			socketChannels[channel] = "subscribing";
 			socketChannels[channel] = "subscribing";
 		});
 		});
-		await runJob("api.subscribeMany", {
+		await runJob("events.subscribeMany", {
 			channels: channelsToSubscribeTo
 			channels: channelsToSubscribeTo
 		});
 		});
 		channelsToSubscribeTo.forEach(channel => {
 		channelsToSubscribeTo.forEach(channel => {
@@ -103,7 +103,7 @@ export const useWebsocketStore = defineStore("websocket", () => {
 			socketChannels[channel] === "subscribed" &&
 			socketChannels[channel] === "subscribed" &&
 			Object.keys(subscriptions.value[channel]).length <= 1
 			Object.keys(subscriptions.value[channel]).length <= 1
 		)
 		)
-			await runJob("api.unsubscribe", { channel });
+			await runJob("events.unsubscribe", { channel });
 
 
 		delete subscriptions.value[channel][uuid];
 		delete subscriptions.value[channel][uuid];
 
 
@@ -117,7 +117,7 @@ export const useWebsocketStore = defineStore("websocket", () => {
 				socketChannels[channel] === "subscribed" &&
 				socketChannels[channel] === "subscribed" &&
 				Object.keys(subscriptions.value[channel]).length <= 1
 				Object.keys(subscriptions.value[channel]).length <= 1
 		);
 		);
-		await runJob("api.unsubscribeMany", {
+		await runJob("events.unsubscribeMany", {
 			channels: channelsToUnsubscribeFrom
 			channels: channelsToUnsubscribeFrom
 		});
 		});
 		channelsToUnsubscribeFrom.forEach(channel => {
 		channelsToUnsubscribeFrom.forEach(channel => {
@@ -141,7 +141,7 @@ export const useWebsocketStore = defineStore("websocket", () => {
 	};
 	};
 
 
 	const unsubscribeAll = async () => {
 	const unsubscribeAll = async () => {
-		await runJob("api.unsubscribeAll");
+		await runJob("events.unsubscribeAll");
 
 
 		subscriptions.value = {};
 		subscriptions.value = {};
 		Object.keys(socketChannels).forEach(channel => {
 		Object.keys(socketChannels).forEach(channel => {
@@ -196,7 +196,7 @@ export const useWebsocketStore = defineStore("websocket", () => {
 							socketChannels[channel]
 							socketChannels[channel]
 						) === -1
 						) === -1
 				)
 				)
-				.map(channel => runJob("api.subscribe", { channel }))
+				.map(channel => runJob("events.subscribe", { channel }))
 		);
 		);
 
 
 		pendingJobs.value.forEach(message => socket.value.send(message));
 		pendingJobs.value.forEach(message => socket.value.send(message));
@@ -212,7 +212,7 @@ export const useWebsocketStore = defineStore("websocket", () => {
 		)
 		)
 			socket.value.close();
 			socket.value.close();
 
 
-		socket.value = new WebSocket(configStore.urls.ws + "?rewrite=1");
+		socket.value = new WebSocket(`${configStore.urls.ws}?rewrite=1`);
 
 
 		socket.value.addEventListener("message", async message => {
 		socket.value.addEventListener("message", async message => {
 			const data = JSON.parse(message.data);
 			const data = JSON.parse(message.data);