File size: 3,521 Bytes
0e5ff83
2dae031
 
31daf3d
bb6f8cb
31daf3d
dae4e82
2dae031
 
 
 
 
 
 
aa54575
0e5ff83
 
 
2dae031
06e807a
2dae031
 
aa54575
2dae031
31daf3d
2dae031
dae4e82
2dae031
06e807a
2dae031
 
 
dae4e82
2dae031
31daf3d
2dae031
 
 
 
 
 
 
 
31daf3d
2dae031
 
 
 
 
e9406a3
 
 
2dae031
 
e9406a3
06e807a
2dae031
 
 
31daf3d
 
2dae031
06e807a
2dae031
 
 
 
 
e9406a3
06e807a
e9406a3
 
 
 
2dae031
aa54575
 
 
 
 
 
2dae031
aa54575
 
 
2dae031
 
 
 
 
 
aa54575
2dae031
 
06e807a
bb6f8cb
2dae031
 
 
 
aa54575
 
 
 
 
 
2dae031
aa54575
 
 
2dae031
 
 
06e807a
2dae031
 
31daf3d
2dae031
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import { Database } from "$lib/server/database";
import { migrations } from "./routines";
import { acquireLock, releaseLock, isDBLocked, refreshLock } from "./lock";
import { Semaphores } from "$lib/types/Semaphore";
import { logger } from "$lib/server/logger";
import { config } from "$lib/server/config";

export async function checkAndRunMigrations() {
	// make sure all GUIDs are unique
	if (new Set(migrations.map((m) => m._id.toString())).size !== migrations.length) {
		throw new Error("Duplicate migration GUIDs found.");
	}

	// check if all migrations have already been run
	const migrationResults = await (await Database.getInstance())
		.getCollections()
		.migrationResults.find()
		.toArray();

	logger.debug("[MIGRATIONS] Begin check...");

	// connect to the database
	const connectedClient = await (await Database.getInstance()).getClient().connect();

	const lockId = await acquireLock(Semaphores.MIGRATION);

	if (!lockId) {
		// another instance already has the lock, so we exit early
		logger.debug(
			"[MIGRATIONS] Another instance already has the lock. Waiting for DB to be unlocked."
		);

		// Todo: is this necessary? Can we just return?
		// block until the lock is released
		while (await isDBLocked(Semaphores.MIGRATION)) {
			await new Promise((resolve) => setTimeout(resolve, 1000));
		}
		return;
	}

	// once here, we have the lock
	// make sure to refresh it regularly while it's running
	const refreshInterval = setInterval(async () => {
		await refreshLock(Semaphores.MIGRATION, lockId);
	}, 1000 * 10);

	// iterate over all migrations
	for (const migration of migrations) {
		// check if the migration has already been applied
		const shouldRun =
			migration.runEveryTime ||
			!migrationResults.find((m) => m._id.toString() === migration._id.toString());

		// check if the migration has already been applied
		if (!shouldRun) {
			logger.debug(`[MIGRATIONS] "${migration.name}" already applied. Skipping...`);
		} else {
			// check the modifiers to see if some cases match
			if (
				(migration.runForHuggingChat === "only" && !config.isHuggingChat) ||
				(migration.runForHuggingChat === "never" && config.isHuggingChat)
			) {
				logger.debug(
					`[MIGRATIONS] "${migration.name}" should not be applied for this run. Skipping...`
				);
				continue;
			}

			// otherwise all is good and we can run the migration
			logger.debug(
				`[MIGRATIONS] "${migration.name}" ${
					migration.runEveryTime ? "should run every time" : "not applied yet"
				}. Applying...`
			);

			await (await Database.getInstance()).getCollections().migrationResults.updateOne(
				{ _id: migration._id },
				{
					$set: {
						name: migration.name,
						status: "ongoing",
					},
				},
				{ upsert: true }
			);

			const session = connectedClient.startSession();
			let result = false;

			try {
				await session.withTransaction(async () => {
					result = await migration.up(await Database.getInstance());
				});
			} catch (e) {
				logger.debug(`[MIGRATIONS]  "${migration.name}" failed!`);
				logger.error(e);
			} finally {
				await session.endSession();
			}

			await (await Database.getInstance()).getCollections().migrationResults.updateOne(
				{ _id: migration._id },
				{
					$set: {
						name: migration.name,
						status: result ? "success" : "failure",
					},
				},
				{ upsert: true }
			);
		}
	}

	logger.debug("[MIGRATIONS] All migrations applied. Releasing lock");

	clearInterval(refreshInterval);
	await releaseLock(Semaphores.MIGRATION, lockId);
}