File size: 3,446 Bytes
98b1c51
4dbcbb6
 
 
ddbe7d2
4dbcbb6
a4c3fca
 
4dbcbb6
 
 
 
 
 
 
98b1c51
 
 
 
4dbcbb6
ddbe7d2
4dbcbb6
 
98b1c51
4dbcbb6
a4c3fca
4dbcbb6
a4c3fca
4dbcbb6
ddbe7d2
4dbcbb6
 
 
a4c3fca
4dbcbb6
a4c3fca
4dbcbb6
 
 
 
 
 
 
 
a4c3fca
4dbcbb6
 
 
 
 
29b7d2a
 
 
4dbcbb6
 
29b7d2a
ddbe7d2
4dbcbb6
 
 
 
 
 
ddbe7d2
4dbcbb6
 
 
 
 
29b7d2a
ddbe7d2
29b7d2a
 
 
 
4dbcbb6
98b1c51
 
 
 
 
 
 
 
 
4dbcbb6
98b1c51
 
4dbcbb6
 
 
 
 
 
98b1c51
4dbcbb6
 
ddbe7d2
 
4dbcbb6
 
 
 
98b1c51
 
 
 
 
 
 
 
 
4dbcbb6
98b1c51
 
4dbcbb6
 
 
ddbe7d2
4dbcbb6
 
a4c3fca
4dbcbb6
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
120
121
122
123
124
import { Database } from "$lib/server/database";
import { migrations } from "./routines";
import { acquireLock, releaseLock, isDBLocked, refreshLock } from "./lock";
import { isHuggingChat } from "$lib/utils/isHuggingChat";
import { logger } from "$lib/server/logger";

const LOCK_KEY = "migrations";

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 Database.getInstance()
		.getCollections()
		.migrationResults.find()
		.toArray();

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

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

	const lockId = await acquireLock(LOCK_KEY);

	if (!lockId) {
		// another instance already has the lock, so we exit early
		logger.info(
			"[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(LOCK_KEY)) {
			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(LOCK_KEY, 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.info(`[MIGRATIONS] "${migration.name}" already applied. Skipping...`);
		} else {
			// check the modifiers to see if some cases match
			if (
				(migration.runForHuggingChat === "only" && !isHuggingChat) ||
				(migration.runForHuggingChat === "never" && isHuggingChat)
			) {
				logger.info(
					`[MIGRATIONS] "${migration.name}" should not be applied for this run. Skipping...`
				);
				continue;
			}

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

			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(Database.getInstance());
				});
			} catch (e) {
				logger.info(`[MIGRATIONS]  "${migration.name}" failed!`);
				logger.error(e);
			} finally {
				await session.endSession();
			}

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

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

	clearInterval(refreshInterval);
	await releaseLock(LOCK_KEY, lockId);
}