mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-16 23:40:32 +00:00
Remove the migrator utility
It's easier to migrate with a one-time script than bundle it with the codebase. To modify production, pull down the Docker volume there and run the modifications locally before pushing it back up.
This commit is contained in:
@@ -1,89 +0,0 @@
|
|||||||
import { afterEach, beforeEach, describe, expect, it } from "@jest/globals";
|
|
||||||
import { createClient, RedisClientType } from "redis";
|
|
||||||
import { migrateRedisKeysToSystemPrefix } from "../migrateRedisKeysToSystemPrefix";
|
|
||||||
|
|
||||||
describe("migrateRedisKeysToSystemPrefix", () => {
|
|
||||||
let redisClient: RedisClientType;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
redisClient = createClient({
|
|
||||||
url: process.env.REDIS_URL,
|
|
||||||
});
|
|
||||||
await redisClient.connect();
|
|
||||||
await redisClient.flushAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
if (redisClient) {
|
|
||||||
await redisClient.flushAll();
|
|
||||||
await redisClient.disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("migrates notification keys", async () => {
|
|
||||||
await redisClient.hSet("notification:shuttle1|stop1", "device1", "180");
|
|
||||||
|
|
||||||
const count = await migrateRedisKeysToSystemPrefix(redisClient, "1");
|
|
||||||
|
|
||||||
expect(count).toBe(1);
|
|
||||||
const oldKeyExists = await redisClient.exists("notification:shuttle1|stop1");
|
|
||||||
expect(oldKeyExists).toBe(0);
|
|
||||||
const newValue = await redisClient.hGet("1:notification:shuttle1|stop1", "device1");
|
|
||||||
expect(newValue).toBe("180");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("migrates shuttle keys", async () => {
|
|
||||||
await redisClient.hSet("shuttle:stop:stop1", { id: "stop1", name: "Test Stop" });
|
|
||||||
await redisClient.hSet("shuttle:route:route1", { id: "route1", name: "Test Route" });
|
|
||||||
|
|
||||||
const count = await migrateRedisKeysToSystemPrefix(redisClient, "1");
|
|
||||||
|
|
||||||
expect(count).toBe(2);
|
|
||||||
const newStop = await redisClient.hGetAll("1:shuttle:stop:stop1");
|
|
||||||
expect(newStop.name).toBe("Test Stop");
|
|
||||||
const newRoute = await redisClient.hGetAll("1:shuttle:route:route1");
|
|
||||||
expect(newRoute.name).toBe("Test Route");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("migrates parking keys", async () => {
|
|
||||||
await redisClient.hSet("parking:structure:struct1", { id: "struct1", name: "Lot A" });
|
|
||||||
|
|
||||||
const count = await migrateRedisKeysToSystemPrefix(redisClient, "1");
|
|
||||||
|
|
||||||
expect(count).toBe(1);
|
|
||||||
const newStructure = await redisClient.hGetAll("1:parking:structure:struct1");
|
|
||||||
expect(newStructure.name).toBe("Lot A");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips keys that already have the system prefix", async () => {
|
|
||||||
await redisClient.hSet("1:notification:shuttle1|stop1", "device1", "180");
|
|
||||||
|
|
||||||
const count = await migrateRedisKeysToSystemPrefix(redisClient, "1");
|
|
||||||
|
|
||||||
expect(count).toBe(0);
|
|
||||||
const value = await redisClient.hGet("1:notification:shuttle1|stop1", "device1");
|
|
||||||
expect(value).toBe("180");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not touch unrelated keys", async () => {
|
|
||||||
await redisClient.set("unrelated:key", "value");
|
|
||||||
await redisClient.hSet("notification:shuttle1|stop1", "device1", "180");
|
|
||||||
|
|
||||||
const count = await migrateRedisKeysToSystemPrefix(redisClient, "1");
|
|
||||||
|
|
||||||
expect(count).toBe(1);
|
|
||||||
const unrelatedValue = await redisClient.get("unrelated:key");
|
|
||||||
expect(unrelatedValue).toBe("value");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("throws if systemId is empty", async () => {
|
|
||||||
await expect(
|
|
||||||
migrateRedisKeysToSystemPrefix(redisClient, "")
|
|
||||||
).rejects.toThrow("systemId must be a non-empty string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("returns 0 when there are no keys to migrate", async () => {
|
|
||||||
const count = await migrateRedisKeysToSystemPrefix(redisClient, "1");
|
|
||||||
expect(count).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { RedisClientType } from 'redis';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates existing Redis keys to include a system ID prefix.
|
|
||||||
*
|
|
||||||
* This handles the transition from unprefixed keys (e.g., `notification:shuttle1|stop5`)
|
|
||||||
* to system-prefixed keys (e.g., `1:notification:shuttle1|stop5`), preventing ID clashes
|
|
||||||
* when multiple university systems share the same Redis instance.
|
|
||||||
*
|
|
||||||
* Uses SCAN instead of KEYS to avoid blocking Redis on large datasets.
|
|
||||||
*
|
|
||||||
* @param redisClient - A connected Redis client
|
|
||||||
* @param systemId - The system ID to prefix keys with
|
|
||||||
* @returns The number of keys migrated
|
|
||||||
*/
|
|
||||||
export const migrateRedisKeysToSystemPrefix = async (
|
|
||||||
redisClient: RedisClientType,
|
|
||||||
systemId: string,
|
|
||||||
): Promise<number> => {
|
|
||||||
if (!systemId) {
|
|
||||||
throw new Error('systemId must be a non-empty string');
|
|
||||||
}
|
|
||||||
|
|
||||||
const patterns = ['notification:*', 'shuttle:*', 'parking:*'];
|
|
||||||
let migratedCount = 0;
|
|
||||||
|
|
||||||
for (const pattern of patterns) {
|
|
||||||
for await (const key of redisClient.scanIterator({ MATCH: pattern })) {
|
|
||||||
// Skip keys that already have a system prefix
|
|
||||||
if (key.startsWith(`${systemId}:`)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newKey = `${systemId}:${key}`;
|
|
||||||
await redisClient.rename(key, newKey);
|
|
||||||
migratedCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return migratedCount;
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user