From f954deaf222cc3828338c84a09c87a6c4d79b75c Mon Sep 17 00:00:00 2001 From: Linus Flood Date: Fri, 14 Mar 2025 12:50:34 +0000 Subject: [PATCH] Merged in fix/delete-fuzzy (pull request #1538) Support for delete keys fuzzy * Support for delete keys fuzzy * Added some logs Approved-by: Anton Gunnarsson --- apps/redis-api/src/routes/api/cache.ts | 163 ++++++++++++++----------- 1 file changed, 92 insertions(+), 71 deletions(-) diff --git a/apps/redis-api/src/routes/api/cache.ts b/apps/redis-api/src/routes/api/cache.ts index a50dbfc65..e71b045fd 100644 --- a/apps/redis-api/src/routes/api/cache.ts +++ b/apps/redis-api/src/routes/api/cache.ts @@ -7,87 +7,108 @@ const MIN_LENGTH = 1; const QUERY_TYPE = t.Object({ key: t.String({ minLength: MIN_LENGTH }) }); export const cacheRoutes = new Elysia({ prefix: "/cache" }) - .get( - "/", - async ({ query: { key }, error }) => { - key = validateKey(key); - console.log("GET /cache", key); + .get( + "/", + async ({ query: { key }, error }) => { + key = validateKey(key); + console.log("GET /cache", key); - const value = await redis.get(key); - if (!value) { - return error("Not Found", "Not Found"); - } + const value = await redis.get(key); + if (!value) { + return error("Not Found", "Not Found"); + } - try { - const output = JSON.parse(value); - return { data: output }; - } catch (e) { - redis.del(key); - throw e; - } - }, - { - query: QUERY_TYPE, - response: { 200: t.Object({ data: t.Any() }), 404: t.String() }, - } - ) - .put( - "/", - async ({ query: { key }, body, error, set }) => { - key = validateKey(key); - console.log("PUT /cache", key); + try { + const output = JSON.parse(value); + return { data: output }; + } catch (e) { + redis.del(key); + throw e; + } + }, + { + query: QUERY_TYPE, + response: { 200: t.Object({ data: t.Any() }), 404: t.String() }, + } + ) + .put( + "/", + async ({ query: { key }, body, error, set }) => { + key = validateKey(key); + console.log("PUT /cache", key); - if (!body.ttl || body.ttl < 0) { - return error("Bad Request", "ttl is required"); - } + if (!body.ttl || body.ttl < 0) { + return error("Bad Request", "ttl is required"); + } - await redis.set(key, JSON.stringify(body.data), "EX", body.ttl); + await redis.set(key, JSON.stringify(body.data), "EX", body.ttl); - set.status = 204; - return; - }, - { - body: t.Object({ data: t.Any(), ttl: t.Number() }), - query: QUERY_TYPE, - response: { 204: t.Void(), 400: t.String() }, - } - ) - .delete( - "/", - async ({ query: { key, fuzzy }, set }) => { - key = validateKey(key); - console.log("DELETE /cache", key); + set.status = 204; + return; + }, + { + body: t.Object({ data: t.Any(), ttl: t.Number() }), + query: QUERY_TYPE, + response: { 204: t.Void(), 400: t.String() }, + } + ) + .delete( + "/", + async ({ query: { key, fuzzy }, set }) => { + key = validateKey(key); + console.log("DELETE /cache", key, { fuzzy }); - if (fuzzy) { - key = `*${key}*`; - } + if (fuzzy) { + await deleteWithPattern(`*${key}*`); + } else { + await redis.del(key); + console.log("Deleted key: ", key); + } - await redis.del(key); - - set.status = 204; - return; - }, - { - query: t.Object({ - ...QUERY_TYPE.properties, - ...t.Object({ fuzzy: t.Optional(t.Boolean()) }).properties, - }), - response: { 204: t.Void(), 400: t.String() }, - } - ); + set.status = 204; + return; + }, + { + query: t.Object({ + ...QUERY_TYPE.properties, + ...t.Object({ fuzzy: t.Optional(t.Boolean()) }).properties, + }), + response: { 204: t.Void(), 400: t.String() }, + } + ); function validateKey(key: string) { - const parsedKey = decodeURIComponent(key); + const parsedKey = decodeURIComponent(key); - if (parsedKey.length < MIN_LENGTH) { - throw new ModelValidationError( - "Key has to be atleast 1 character long" - ); - } + if (parsedKey.length < MIN_LENGTH) { + throw new ModelValidationError("Key has to be atleast 1 character long"); + } - if (parsedKey.includes("*")) { - throw new ModelValidationError("Key cannot contain wildcards"); - } + if (parsedKey.includes("*")) { + throw new ModelValidationError("Key cannot contain wildcards"); + } - return parsedKey; + return parsedKey; +} + +async function deleteWithPattern(pattern: string) { + let cursor = "0"; + let keys: string[] = []; + + do { + const [newCursor, foundKeys] = await redis.scan( + cursor, + "MATCH", + pattern, + "COUNT", + 5000 + ); + cursor = newCursor; + keys.push(...foundKeys); + } while (cursor !== "0"); + + if (keys.length > 0) { + await redis.del(...keys); + } + console.log("Deleted number of keys: ", keys.length); }