From 9eeac3cffadc5cffeb112f2ab13bcbe0876d7694 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Fri, 27 Mar 2026 18:34:40 -0700 Subject: [PATCH] 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. --- .../migrateRedisKeysToSystemPrefix.test.ts | 89 ------------------- .../migrateRedisKeysToSystemPrefix.ts | 41 --------- 2 files changed, 130 deletions(-) delete mode 100644 src/repositories/__tests__/migrateRedisKeysToSystemPrefix.test.ts delete mode 100644 src/repositories/migrateRedisKeysToSystemPrefix.ts diff --git a/src/repositories/__tests__/migrateRedisKeysToSystemPrefix.test.ts b/src/repositories/__tests__/migrateRedisKeysToSystemPrefix.test.ts deleted file mode 100644 index de1bd54..0000000 --- a/src/repositories/__tests__/migrateRedisKeysToSystemPrefix.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/src/repositories/migrateRedisKeysToSystemPrefix.ts b/src/repositories/migrateRedisKeysToSystemPrefix.ts deleted file mode 100644 index c8328af..0000000 --- a/src/repositories/migrateRedisKeysToSystemPrefix.ts +++ /dev/null @@ -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 => { - 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; -};