Browse Source

feat: Add migration functionality

Owen Diffey 1 year ago
parent
commit
68a60bb156

+ 15 - 0
backend/.eslintrc

@@ -52,6 +52,21 @@
 			{
 				"devDependencies": ["**/*.test.ts", "**/*.spec.ts"]
 			}
+		],
+		"no-restricted-syntax": [
+			"error",
+			{
+				"selector": "ForInStatement",
+				"message": "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array."
+			},
+			{
+				"selector": "LabeledStatement",
+				"message": "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand."
+			},
+			{
+				"selector": "WithStatement",
+				"message": "`with` is disallowed in strict mode because it makes code impossible to predict and optimize."
+			}
 		]
 	},
 	"overrides": [

+ 17 - 0
backend/src/Migration.ts

@@ -0,0 +1,17 @@
+import { Connection } from "mongoose";
+
+export default class Migration {
+	private mongoConnection: Connection;
+
+	constructor(mongoConnection: Connection) {
+		this.mongoConnection = mongoConnection;
+	}
+
+	protected getDb() {
+		return this.mongoConnection.db;
+	}
+
+	public async up() {}
+
+	public async down() {}
+}

+ 31 - 0
backend/src/modules/DataModule.ts

@@ -6,11 +6,14 @@ import mongoose, {
 	MongooseDistinctQueryMiddleware,
 	MongooseQueryOrDocumentMiddleware
 } from "mongoose";
+import { readdir } from "fs/promises";
+import path from "path";
 import JobContext from "../JobContext";
 import BaseModule, { ModuleStatus } from "../BaseModule";
 import { UniqueMethods } from "../types/Modules";
 import { Models, Schemas } from "../types/Models";
 import getDataPlugin from "../schemas/plugins/getData";
+import Migration from "../Migration";
 
 export default class DataModule extends BaseModule {
 	private models?: Models;
@@ -56,6 +59,8 @@ export default class DataModule extends BaseModule {
 			tags: ["useGetDataPlugin"]
 		});
 
+		await this.runMigrations();
+
 		await this.loadModels();
 
 		await this.syncModelIndexes();
@@ -188,6 +193,32 @@ export default class DataModule extends BaseModule {
 
 		return this.models[name];
 	}
+
+	private async loadMigrations() {
+		if (!this.mongoConnection) throw new Error("Mongo is not available");
+
+		const migrations = await readdir(
+			path.resolve(__dirname, "../schemas/migrations/")
+		);
+
+		return Promise.all(
+			migrations.map(async migrationFile => {
+				const { default: Migrate }: { default: typeof Migration } =
+					await import(`../schemas/migrations/${migrationFile}`);
+				return new Migrate(this.mongoConnection as Connection);
+			})
+		);
+	}
+
+	private async runMigrations() {
+		const migrations = await this.loadMigrations();
+
+		for (let i = 0; i < migrations.length; i += 1) {
+			const migration = migrations[i];
+			// eslint-disable-next-line no-await-in-loop
+			await migration.up();
+		}
+	}
 }
 
 export type DataModuleJobs = {

+ 58 - 0
backend/src/schemas/migrations/1620330161000-news-markdown.ts

@@ -0,0 +1,58 @@
+import Migration from "../../Migration";
+
+export default class Migration1620330161000 extends Migration {
+	async up() {
+		const News = this.getDb().collection("news");
+
+		const newsItems = News.find({ documentVersion: 1 }).stream();
+
+		for await (const newsItem of newsItems) {
+			newsItem.markdown = `# ${newsItem.title}\n\n`;
+			newsItem.markdown += `## ${newsItem.description}\n\n`;
+
+			if (newsItem.bugs) {
+				newsItem.markdown += `**Bugs:**\n\n${newsItem.bugs.join(
+					", "
+				)}\n\n`;
+			}
+
+			if (newsItem.features) {
+				newsItem.markdown += `**Features:**\n\n${newsItem.features.join(
+					", "
+				)}\n\n`;
+			}
+
+			if (newsItem.improvements) {
+				newsItem.markdown += `**Improvements:**\n\n${newsItem.improvements.join(
+					", "
+				)}\n\n`;
+			}
+
+			if (newsItem.upcoming) {
+				newsItem.markdown += `**Upcoming:**\n\n${newsItem.upcoming.join(
+					", "
+				)}\n`;
+			}
+
+			await News.updateOne(
+				{ _id: newsItem._id },
+				{
+					$set: {
+						markdown: newsItem.markdown,
+						status: "published",
+						documentVersion: 2
+					},
+					$unset: {
+						description: "",
+						bugs: "",
+						features: "",
+						improvements: "",
+						upcoming: ""
+					}
+				}
+			);
+		}
+	}
+
+	async down() {}
+}