Sfoglia il codice sorgente

refactor: Added schema class and createAttribute function and other tweaks

Owen Diffey 2 anni fa
parent
commit
d097912f5d

+ 19 - 3
backend/src/LogBook.ts

@@ -124,8 +124,24 @@ export default class LogBook {
 			undefined;
 		if (!exclude.memory && this.outputs.memory.enabled)
 			this.logs.push(logObject);
-		if (!exclude.console)
-			console.log(this.formatMessage(logObject, title, "console"));
+		if (!exclude.console) {
+			const logArgs: any[] = [
+				this.formatMessage(logObject, title, "console")
+			];
+			if (this.outputs.console.data) logArgs.push(logObject.data);
+			switch (logObject.type) {
+				case "debug": {
+					console.debug(...logArgs);
+					break;
+				}
+				case "error": {
+					console.error(...logArgs);
+					break;
+				}
+				default:
+					console.log(...logArgs);
+			}
+		}
 		if (!exclude.file)
 			this.stream.write(
 				`${this.formatMessage(logObject, title, "file")}\n`
@@ -180,7 +196,7 @@ export default class LogBook {
 				10
 			);
 		if (this.outputs[destination].message) message += `| ${log.message} `;
-		if (this.outputs[destination].data)
+		if (destination !== "console" && this.outputs[destination].data)
 			message += `| ${JSON.stringify(log.data)} `;
 		if (this.outputs[destination].color) message += "\x1b[0m";
 		return message;

+ 65 - 0
backend/src/Schema.ts

@@ -0,0 +1,65 @@
+import { Attribute } from "./types/Attribute";
+import { Document } from "./types/Document";
+
+export enum Types {
+	String,
+	Number,
+	Date,
+	Boolean,
+	ObjectId,
+	Array,
+	// Map,
+	Schema
+}
+
+export const createAttribute = ({
+	type,
+	required,
+	restricted,
+	item,
+	schema
+}: Partial<Attribute> & { type: Attribute["type"] }) => ({
+	type,
+	required: required ?? true,
+	restricted: restricted ?? false,
+	item,
+	schema
+});
+
+export default class Schema {
+	private document: Document;
+
+	private timestamps: boolean;
+
+	private version: number;
+
+	public constructor(schema: {
+		document: Document;
+		timestamps?: boolean;
+		version?: number;
+	}) {
+		this.document = {
+			_id: createAttribute({ type: Types.ObjectId }),
+			...schema.document
+		};
+		this.timestamps = schema.timestamps ?? true;
+		this.version = schema.version ?? 1;
+
+		if (this.timestamps) {
+			this.document.createdAt = createAttribute({
+				type: Types.Date
+			});
+			this.document.updatedAt = createAttribute({
+				type: Types.Date
+			});
+		}
+	}
+
+	public getDocument() {
+		return this.document;
+	}
+
+	public getVersion() {
+		return this.version;
+	}
+}

+ 17 - 72
backend/src/collections/abc.ts

@@ -1,90 +1,35 @@
-// @ts-nocheck
-import mongoose from "mongoose";
-import { Types, DocumentAttribute, DefaultSchema } from "../types/Collections";
+import Schema, { createAttribute, Types } from "../Schema";
 
-export type AbcCollection = DefaultSchema & {
+export default new Schema({
 	document: {
-		name: DocumentAttribute<{ type: Types.String }>;
-		autofill: {
-			enabled: DocumentAttribute<{
-				type: BooleanConstructor;
-				required: false;
-				restricted: true;
-			}>;
-		};
-	};
-};
-
-// validate: async (value: any) => {
-// 	if (!mongoose.Types.ObjectId.isValid(value))
-// 		throw new Error("Value is not a valid ObjectId");
-// }
-
-// validate: async (value: any) => {
-// 	if (value.length < 1 || value.length > 64)
-// 		throw new Error("Name must be 1-64 characters");
-// 	if (!/^[\p{Letter}0-9 .'_-]+$/gu.test(value))
-// 		throw new Error("Invalid name provided");
-// 	if (value.replaceAll(/[ .'_-]/g, "").length === 0)
-// 		throw new Error(
-// 			"Name must contain at least 1 letter or number"
-// 		);
-// }
-
-export const schema: AbcCollection = {
-	document: {
-		_id: {
-			type: Types.ObjectId,
-			required: true,
-			cacheKey: true
-		},
-		createdAt: {
-			type: Types.Date,
-			required: true
-		},
-		updatedAt: {
-			type: Types.Date,
-			required: true
-		},
-		name: {
+		name: createAttribute({
 			type: Types.String,
-			required: true,
 			restricted: true
-		},
-		autofill: {
+		}),
+		autofill: createAttribute({
 			type: Types.Schema,
 			schema: {
-				enabled: {
+				enabled: createAttribute({
 					type: Types.Boolean,
-					required: false,
-					restricted: false
-				}
+					required: false
+				})
 			}
-		},
-		someNumbers: {
+		}),
+		someNumbers: createAttribute({
 			type: Types.Array,
 			item: {
 				type: Types.Number
 			}
-		},
-		songs: {
+		}),
+		songs: createAttribute({
 			type: Types.Array,
 			item: {
 				type: Types.Schema,
 				schema: {
-					_id: {
-						type: Types.ObjectId,
-						required: true
-					}
+					_id: createAttribute({ type: Types.ObjectId })
 				}
 			}
-		},
-		aNumber: {
-			type: Types.Number,
-			required: true,
-			restricted: false
-		}
-	},
-	timestamps: true,
-	version: 1
-};
+		}),
+		aNumber: createAttribute({ type: Types.Number })
+	}
+});

+ 101 - 0
backend/src/collections/station.ts

@@ -0,0 +1,101 @@
+import Schema, { createAttribute, Types } from "../Schema";
+
+export default new Schema({
+	document: {
+		type: createAttribute({
+			type: Types.String
+		}),
+		name: createAttribute({
+			type: Types.String
+		}),
+		displayName: createAttribute({
+			type: Types.String
+		}),
+		description: createAttribute({
+			type: Types.String
+		}),
+		privacy: createAttribute({
+			type: Types.String
+		}),
+		theme: createAttribute({
+			type: Types.String
+		}),
+		owner: createAttribute({
+			type: Types.ObjectId
+		}),
+		djs: createAttribute({
+			type: Types.Array,
+			item: {
+				type: Types.ObjectId
+			}
+		}),
+		currentSong: createAttribute({
+			type: Types.ObjectId
+		}),
+		currentSongIndex: createAttribute({
+			type: Types.Number
+		}),
+		startedAt: createAttribute({
+			type: Types.Date
+		}),
+		paused: createAttribute({
+			type: Types.Boolean
+		}),
+		timePaused: createAttribute({
+			type: Types.Number
+		}),
+		pausedAt: createAttribute({
+			type: Types.Date
+		}),
+		playlist: createAttribute({
+			type: Types.ObjectId
+		}),
+		queue: createAttribute({
+			type: Types.Array,
+			item: {
+				type: Types.ObjectId
+			}
+		}),
+		blacklist: createAttribute({
+			type: Types.Array,
+			item: {
+				type: Types.ObjectId
+			}
+		}),
+		requests: createAttribute({
+			type: Types.Schema,
+			schema: {
+				enabled: createAttribute({
+					type: Types.Boolean
+				}),
+				access: createAttribute({
+					type: Types.String
+				}),
+				limit: createAttribute({
+					type: Types.Number
+				})
+			}
+		}),
+		autofill: createAttribute({
+			type: Types.Schema,
+			schema: {
+				enabled: createAttribute({
+					type: Types.Boolean
+				}),
+				playlists: createAttribute({
+					type: Types.Array,
+					item: {
+						type: Types.ObjectId
+					}
+				}),
+				limit: createAttribute({
+					type: Types.Number
+				}),
+				mode: createAttribute({
+					type: Types.String
+				})
+			}
+		})
+	},
+	version: 9
+});

+ 23 - 19
backend/src/modules/DataModule.ts

@@ -4,11 +4,12 @@ import config from "config";
 import { Db, MongoClient, ObjectId } from "mongodb";
 import hash from "object-hash";
 import { createClient, RedisClientType } from "redis";
-import JobContext from "src/JobContext";
+import JobContext from "../JobContext";
 import BaseModule from "../BaseModule";
 import ModuleManager from "../ModuleManager";
+import Schema, { Types } from "../Schema";
 import { UniqueMethods } from "../types/Modules";
-import { Collections, Types } from "../types/Collections";
+import { Collections } from "../types/Collections";
 
 export default class DataModule extends BaseModule {
 	private collections?: Collections;
@@ -182,7 +183,7 @@ export default class DataModule extends BaseModule {
 	): Promise<Collections[T]> {
 		return new Promise(resolve => {
 			import(`../collections/${collectionName.toString()}`).then(
-				({ schema }: { schema: Collections[T]["schema"] }) => {
+				({ default: schema }: { default: Schema }) => {
 					resolve({
 						schema,
 						collection: this.mongoDb?.collection(
@@ -202,7 +203,8 @@ export default class DataModule extends BaseModule {
 	private loadCollections(): Promise<void> {
 		return new Promise((resolve, reject) => {
 			const fetchCollections = async () => ({
-				abc: await this.loadCollection("abc")
+				abc: await this.loadCollection("abc"),
+				station: await this.loadCollection("station")
 			});
 			fetchCollections()
 				.then(collections => {
@@ -218,8 +220,8 @@ export default class DataModule extends BaseModule {
 	/**
 	 * Returns the projection array/object that is one level deeper based on the property key
 	 *
-	 * @param projection The projection object/array
-	 * @param key The property key
+	 * @param projection - The projection object/array
+	 * @param key - The property key
 	 * @returns Array or Object
 	 */
 	private getDeeperProjection(projection: any, key: string) {
@@ -248,8 +250,8 @@ export default class DataModule extends BaseModule {
 	/**
 	 * Whether a property is allowed in a projection array/object
 	 *
-	 * @param projection
-	 * @param property
+	 * @param projection - The projection object/array
+	 * @param property - Property name
 	 * @returns
 	 */
 	private allowedByProjection(projection: any, property: string) {
@@ -282,9 +284,9 @@ export default class DataModule extends BaseModule {
 	 * Strip a document object from any unneeded properties, or of any restricted properties
 	 * If a projection is given
 	 *
-	 * @param document The document object
-	 * @param schema The schema object
-	 * @param projection The projection, which can be null
+	 * @param document - The document object
+	 * @param schema - The schema object
+	 * @param projection - The projection, which can be null
 	 * @returns
 	 */
 	private async stripDocument(document: any, schema: any, projection: any) {
@@ -431,8 +433,8 @@ export default class DataModule extends BaseModule {
 	 * If a projection is given, it will exclude restricted properties that are not explicitly allowed in a projection
 	 * It will return a projection used in Mongo, and if any restricted property is explicitly allowed, return that we can't use the cache
 	 *
-	 * @param schema The schema object
-	 * @param projection The project, which can be null
+	 * @param schema - The schema object
+	 * @param projection - The project, which can be null
 	 * @returns
 	 */
 	private async parseFindProjection(projection: any, schema: any) {
@@ -470,7 +472,7 @@ export default class DataModule extends BaseModule {
 					// Parse projection for the current value, so one level deeper
 					const parsedProjection = await this.parseFindProjection(
 						deeperProjection,
-						value
+						value.schema
 					);
 
 					// If the parsed projection mongo projection contains anything, update our own mongo projection
@@ -490,7 +492,7 @@ export default class DataModule extends BaseModule {
 				// Pass the nested schema object recursively into the parseFindProjection function
 				const parsedProjection = await this.parseFindProjection(
 					null,
-					value
+					value.schema
 				);
 
 				// If the returned mongo projection includes anything special, include it in the mongo projection we're returning
@@ -836,7 +838,7 @@ export default class DataModule extends BaseModule {
 					async () => {
 						const parsedFilter = await this.parseFindFilter(
 							filter,
-							this.collections![collection].schema.document
+							this.collections![collection].schema.getDocument()
 						);
 
 						cacheable = cacheable && parsedFilter.canCache;
@@ -847,7 +849,7 @@ export default class DataModule extends BaseModule {
 					async () => {
 						const parsedProjection = await this.parseFindProjection(
 							projection,
-							this.collections![collection].schema.document
+							this.collections![collection].schema.getDocument()
 						);
 
 						cacheable = cacheable && parsedProjection.canCache;
@@ -915,7 +917,7 @@ export default class DataModule extends BaseModule {
 						// 	return find;
 						// };
 						// const find: any = await getFindValues(
-						// 	this.collections![collection].schema.document
+						// 	this.collections![collection].schema.getDocument()
 						// );
 
 						// TODO, add mongo projection. Make sure to keep in mind caching with queryHash.
@@ -952,7 +954,9 @@ export default class DataModule extends BaseModule {
 						async.map(documents, async (document: any) =>
 							this.stripDocument(
 								document,
-								this.collections![collection].schema.document,
+								this.collections![
+									collection
+								].schema.getDocument(),
 								projection
 							)
 						)

+ 10 - 0
backend/src/types/Attribute.ts

@@ -0,0 +1,10 @@
+import { Types } from "../Schema";
+import { Document } from "./Document";
+
+export type Attribute = {
+	type: Types;
+	required: boolean;
+	restricted: boolean;
+	item?: Pick<Attribute, "type" | "item" | "schema">;
+	schema?: Document;
+};

+ 7 - 50
backend/src/types/Collections.ts

@@ -1,53 +1,10 @@
-// @ts-nocheck
 import { Collection } from "mongodb";
-import { AbcCollection } from "../collections/abc";
+import Schema from "../Schema";
 
-export enum Types {
-	String,
-	Number,
-	Date,
-	Boolean,
-	ObjectId,
-	Array,
-	// Map,
-	Schema
-}
-
-export type DocumentAttribute<
-	T extends {
-		type: Types;
-		required?: boolean;
-		cacheKey?: boolean;
-		restricted?: boolean;
-		validate?: (value: any) => Promise<void>;
+export type Collections = Record<
+	"abc" | "station",
+	{
+		schema: Schema;
+		collection: Collection;
 	}
-> = {
-	type: T["type"];
-	required?: T["required"]; // TODO fix default unknown
-	cacheKey?: T["cacheKey"]; // TODO fix default unknown
-	restricted?: T["restricted"]; // TODO fix default unknown
-	validate?: T["validate"]; // TODO fix default unknown
-};
-
-export type DefaultSchema = {
-	document: Record<
-		string,
-		| DocumentAttribute<{ type: Types }>
-		| Record<string, DocumentAttribute<{ type: unknown }>>
-	> & {
-		_id: DocumentAttribute<{
-			type: Types.ObjectId;
-		}>;
-		createdAt: DocumentAttribute<{ type: Types.Date }>;
-		updatedAt: DocumentAttribute<{ type: Types.Date }>;
-	};
-	timestamps: boolean;
-	version: number;
-};
-
-export type Collections = {
-	abc: {
-		schema: AbcCollection;
-		collection: Collection<AbcCollection["document"]>;
-	};
-};
+>;

+ 3 - 0
backend/src/types/Document.ts

@@ -0,0 +1,3 @@
+import { Attribute } from "./Attribute";
+
+export type Document = Record<string, Attribute>;