浏览代码

refactor: change schema format to use our own types and support arrays

Kristian Vos 2 年之前
父节点
当前提交
8b9d3261fa
共有 4 个文件被更改,包括 131 次插入35 次删除
  1. 2 1
      backend/src/ModuleManager.ts
  2. 49 26
      backend/src/collections/abc.ts
  3. 64 3
      backend/src/modules/DataModule.ts
  4. 16 5
      backend/src/types/Collections.ts

+ 2 - 1
backend/src/ModuleManager.ts

@@ -196,7 +196,8 @@ export default class ModuleManager {
 								this.logBook,
 								job
 							);
-							jobFunction.apply(module, [jobContext, payload])
+							jobFunction
+								.apply(module, [jobContext, payload])
 								.then((response: ReturnType) => {
 									this.logBook.log({
 										message: "Job completed successfully",

+ 49 - 26
backend/src/collections/abc.ts

@@ -1,9 +1,9 @@
 import mongoose from "mongoose";
-import { DocumentAttribute, DefaultSchema } from "../types/Collections";
+import { Types, DocumentAttribute, DefaultSchema } from "../types/Collections";
 
 export type AbcCollection = DefaultSchema & {
 	document: {
-		name: DocumentAttribute<{ type: StringConstructor }>;
+		name: DocumentAttribute<{ type: Types.String }>;
 		autofill: {
 			enabled: DocumentAttribute<{
 				type: BooleanConstructor;
@@ -14,45 +14,68 @@ export type AbcCollection = DefaultSchema & {
 	};
 };
 
+// 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: mongoose.Types.ObjectId,
+			type: Types.ObjectId,
 			required: true,
-			cacheKey: true,
-			validate: async (value: any) => {
-				if (!mongoose.Types.ObjectId.isValid(value))
-					throw new Error("Value is not a valid ObjectId");
-			}
+			cacheKey: true
 		},
 		createdAt: {
-			type: Date,
+			type: Types.Date,
 			required: true
 		},
 		updatedAt: {
-			type: Date,
+			type: Types.Date,
 			required: true
 		},
 		name: {
-			type: String,
+			type: Types.String,
 			required: true,
-			restricted: true,
-			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"
-					);
-			}
+			restricted: true
 		},
 		autofill: {
-			enabled: {
-				type: Boolean,
-				required: false,
-				restricted: true // TODO: Set to false when empty 2nd layer object fixed
+			type: Types.Schema,
+			schema: {
+				enabled: {
+					type: Types.Boolean,
+					required: false,
+					restricted: true // TODO: Set to false when empty 2nd layer object fixed
+				}
+			}
+		},
+		someNumbers: {
+			type: Types.Array,
+			item: {
+				type: Types.Number
+			}
+		},
+		songs: {
+			type: Types.Array,
+			item: {
+				type: Types.Schema,
+				schema: {
+					_id: {
+						type: Types.ObjectId,
+						required: true
+					}
+				}
 			}
 		}
 	},

+ 64 - 3
backend/src/modules/DataModule.ts

@@ -1,13 +1,13 @@
 import async from "async";
 import config from "config";
-import mongoose, { Schema } from "mongoose";
+import mongoose, { Schema, Types as MongooseTypes } from "mongoose";
 import hash from "object-hash";
 import { createClient, RedisClientType } from "redis";
 import JobContext from "src/JobContext";
 import BaseModule from "../BaseModule";
 import ModuleManager from "../ModuleManager";
 import { UniqueMethods } from "../types/Modules";
-import { Collections } from "../types/Collections";
+import { Collections, Types } from "../types/Collections";
 
 export default class DataModule extends BaseModule {
 	collections?: Collections;
@@ -118,6 +118,63 @@ export default class DataModule extends BaseModule {
 		});
 	}
 
+	/**
+	 *
+	 * @param schema Our own schema format
+	 * @returns A Mongoose-compatible schema format
+	 */
+	private convertSchemaToMongooseSchema(schema: any) {
+		// Convert basic types from our own schema types to Mongoose schema types
+		const typeToMongooseType = (type: Types) => {
+			switch (type) {
+				case Types.String:
+					return String;
+				case Types.Number:
+					return Number;
+				case Types.Date:
+					return Date;
+				case Types.Boolean:
+					return Boolean;
+				case Types.ObjectId:
+					return MongooseTypes.ObjectId;
+				default:
+					return null;
+			}
+		};
+
+		const schemaEntries = Object.entries(schema);
+		const mongooseSchemaEntries = schemaEntries.map(([key, value]) => {
+			let mongooseEntry = {};
+
+			// Handle arrays
+			if (value.type === Types.Array) {
+				// Handle schemas in arrays
+				if (value.item.type === Types.Schema)
+					mongooseEntry = [
+						this.convertSchemaToMongooseSchema(value.item.schema)
+					];
+				// We don't support nested arrays
+				else if (value.item.type === Types.Array)
+					throw new Error("Nested arrays are not supported.");
+				// Handle regular types in array
+				else mongooseEntry.type = [typeToMongooseType(value.item.type)];
+			}
+			// Handle schemas
+			else if (value.type === Types.Schema)
+				mongooseEntry = this.convertSchemaToMongooseSchema(
+					value.schema
+				);
+			// Handle regular types
+			else mongooseEntry.type = typeToMongooseType(value.type);
+
+			return [key, mongooseEntry];
+		});
+
+		const mongooseSchema = Object.fromEntries(mongooseSchemaEntries);
+
+		return mongooseSchema;
+	}
+
 	/**
 	 * loadColllection - Import and load collection schema
 	 *
@@ -130,9 +187,13 @@ export default class DataModule extends BaseModule {
 		return new Promise(resolve => {
 			import(`../collections/${collectionName.toString()}`).then(
 				({ schema }: { schema: Collections[T]["schema"] }) => {
+					// Here we create a Mongoose schema and model based on our schema
+					const mongooseSchemaDocument =
+						this.convertSchemaToMongooseSchema(schema.document);
+
 					const mongoSchema = new Schema<
 						Collections[T]["schema"]["document"]
-					>(schema.document, {
+					>(mongooseSchemaDocument, {
 						timestamps: schema.timestamps
 					});
 					const model = mongoose.model(

+ 16 - 5
backend/src/types/Collections.ts

@@ -1,9 +1,20 @@
 import mongoose, { Model } from "mongoose";
 import { AbcCollection } from "../collections/abc";
 
+export enum Types {
+	String,
+	Number,
+	Date,
+	Boolean,
+	ObjectId,
+	Array,
+	// Map,
+	Schema
+}
+
 export type DocumentAttribute<
 	T extends {
-		type: unknown;
+		type: Types;
 		required?: boolean;
 		cacheKey?: boolean;
 		restricted?: boolean;
@@ -20,14 +31,14 @@ export type DocumentAttribute<
 export type DefaultSchema = {
 	document: Record<
 		string,
-		| DocumentAttribute<{ type: unknown }>
+		| DocumentAttribute<{ type: Types }>
 		| Record<string, DocumentAttribute<{ type: unknown }>>
 	> & {
 		_id: DocumentAttribute<{
-			type: typeof mongoose.Types.ObjectId;
+			type: Types.ObjectId;
 		}>;
-		createdAt: DocumentAttribute<{ type: DateConstructor }>;
-		updatedAt: DocumentAttribute<{ type: DateConstructor }>;
+		createdAt: DocumentAttribute<{ type: Types.Date }>;
+		updatedAt: DocumentAttribute<{ type: Types.Date }>;
 	};
 	timestamps: boolean;
 	version: number;