From 92779d8b4e37f15fbe09b91b1177ff8bdc689689 Mon Sep 17 00:00:00 2001 From: Marco Pedone Date: Wed, 6 May 2026 17:19:40 +0200 Subject: [PATCH] removed tiles redis, now using pg --- .github/workflows/ci.yml | 3 - apps/db/migrations/54_kvcache.up.sql | 17 ++++ apps/infoalloggi/.dockerignore | 2 +- apps/infoalloggi/Dockerfile | 8 +- apps/infoalloggi/TODO | 2 +- apps/infoalloggi/dev-docker-compose.yml | 18 ---- apps/infoalloggi/kanel.config.js | 3 + apps/infoalloggi/package-lock.json | 70 --------------- apps/infoalloggi/package.json | 3 +- apps/infoalloggi/src/env.ts | 2 - .../src/pages/api/tiles/[[...slug]].ts | 33 +++---- .../area-riservata/admin/impostazioni.tsx | 85 +++++++++---------- .../src/schemas/public/PublicSchema.ts | 3 + .../src/schemas/public/TilesCache.ts | 40 +++++++++ apps/infoalloggi/src/server/api/root.ts | 2 + .../src/server/api/routers/annunci.ts | 8 -- .../src/server/api/routers/revalidation.ts | 7 +- .../src/server/api/routers/tiles.ts | 20 +++++ .../controllers/event_queue.controller.ts | 28 +----- .../src/server/services/cache.service.ts | 64 -------------- .../src/server/services/tiles.service.ts | 58 +++++++++++++ apps/infoalloggi/src/utils/keydb.ts | 19 ----- docker-compose.yml | 51 ----------- 23 files changed, 202 insertions(+), 344 deletions(-) create mode 100644 apps/db/migrations/54_kvcache.up.sql create mode 100644 apps/infoalloggi/src/schemas/public/TilesCache.ts create mode 100644 apps/infoalloggi/src/server/api/routers/tiles.ts delete mode 100644 apps/infoalloggi/src/server/services/cache.service.ts create mode 100644 apps/infoalloggi/src/server/services/tiles.service.ts delete mode 100644 apps/infoalloggi/src/utils/keydb.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89eb78f..9cb3be8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,10 +36,7 @@ env: ARUBA_PASS: "string" EXP_API_USER: "string" EXP_API_PASS: "string" - TILES_URL: "keydb://string" - KEYDB_URL: "keydb://string" JWT_SECRET: "335922ae" - SKIP_REDIS: "false" jobs: diff --git a/apps/db/migrations/54_kvcache.up.sql b/apps/db/migrations/54_kvcache.up.sql new file mode 100644 index 0000000..46923ec --- /dev/null +++ b/apps/db/migrations/54_kvcache.up.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS tiles_cache ( + key TEXT NOT NULL, + value BYTEA NOT NULL, + expires_at TIMESTAMPTZ -- NULL = no expiry +); +DO $$ BEGIN IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'tiles_cache_pkey' + AND conrelid = 'public.tiles_cache'::regclass +) THEN +ALTER TABLE public.tiles_cache +ADD CONSTRAINT tiles_cache_pkey PRIMARY KEY (key); +END IF; +END $$; + +CREATE INDEX IF NOT EXISTS tiles_cache_key_idx ON public.tiles_cache (key); \ No newline at end of file diff --git a/apps/infoalloggi/.dockerignore b/apps/infoalloggi/.dockerignore index 7086742..1b23685 100644 --- a/apps/infoalloggi/.dockerignore +++ b/apps/infoalloggi/.dockerignore @@ -7,7 +7,7 @@ node_modules .github Dockerfile docker-compose.yml -keydb-docker-compose.yml +dev-docker-compose.yml pgadmin-docker-compose.yml dev_utils_compose.sh .env diff --git a/apps/infoalloggi/Dockerfile b/apps/infoalloggi/Dockerfile index 42cacb9..fd4d48a 100644 --- a/apps/infoalloggi/Dockerfile +++ b/apps/infoalloggi/Dockerfile @@ -13,8 +13,7 @@ COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NODE_ENV=production \ - NEXT_TELEMETRY_DISABLED=1 \ - SKIP_REDIS="true" + NEXT_TELEMETRY_DISABLED=1 ARG BASE_URL NEXT_PUBLIC_BASE_URL NEXT_PUBLIC_STRIPE_PUBLIC_KEY ENV BASE_URL=$BASE_URL \ @@ -29,8 +28,7 @@ FROM base AS runner WORKDIR /app ENV NODE_ENV=production \ - NEXT_TELEMETRY_DISABLED=1 \ - SKIP_REDIS="false" + NEXT_TELEMETRY_DISABLED=1 ARG BASE_URL ENV BASE_URL=$BASE_URL @@ -72,8 +70,6 @@ ARG EXP_API_USER ENV EXP_API_USER=$EXP_API_USER ARG EXP_API_PASS ENV EXP_API_PASS=$EXP_API_PASS -ARG KEYDB_URL -ENV KEYDB_URL=$KEYDB_URL ARG TILES_URL ENV TILES_URL=$TILES_URL ARG SKEBBY_USER diff --git a/apps/infoalloggi/TODO b/apps/infoalloggi/TODO index aafb233..8c8d41e 100644 --- a/apps/infoalloggi/TODO +++ b/apps/infoalloggi/TODO @@ -13,7 +13,7 @@ TODOS: - pre attivazione se admin mostrare tastino contatti in preview - onboarding submit (toast e email che invitano a pagare) - migliorare onboarding e pag guida - + - "fatto_nulla" status in servizio AFTER MVP: - TODO migrazione app router https://nextjs.org/docs/pages/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app - Sezione "La mia locazione" con i dati della locazione attuale dell'utente diff --git a/apps/infoalloggi/dev-docker-compose.yml b/apps/infoalloggi/dev-docker-compose.yml index 8b893d9..22baa4c 100644 --- a/apps/infoalloggi/dev-docker-compose.yml +++ b/apps/infoalloggi/dev-docker-compose.yml @@ -1,20 +1,4 @@ services: - tiles: - image: eqalpha/keydb:latest - volumes: - - tiles-data:/data - ports: - - "6379:6379" - command: keydb-server /etc/keydb/keydb.conf --maxmemory 100mb --maxmemory-policy volatile-lru --maxmemory-samples 5 - restart: unless-stopped - keydb: - image: eqalpha/keydb:latest - volumes: - - keydb-data:/data - ports: - - "6380:6379" - - restart: unless-stopped storage: image: "ghcr.io/marcopedone/go_fs:latest" ports: @@ -34,6 +18,4 @@ services: start_period: 10s volumes: - tiles-data: - keydb-data: storage_data: diff --git a/apps/infoalloggi/kanel.config.js b/apps/infoalloggi/kanel.config.js index a9988af..5742720 100644 --- a/apps/infoalloggi/kanel.config.js +++ b/apps/infoalloggi/kanel.config.js @@ -161,6 +161,8 @@ module.exports = { "pg_catalog.bpchar": "string", 'pg_catalog.numeric': 'number', //"pg_catalog.json": "string", + 'pg_catalog.bytea': "Buffer" + }, preRenderHooks: [ @@ -177,6 +179,7 @@ module.exports = { zodTypeMap: { ...defaultZodTypeMap, "pg_catalog.numeric": "z.number()", + "pg_catalog.bytea": "z.instanceof(Buffer)" } }), ], diff --git a/apps/infoalloggi/package-lock.json b/apps/infoalloggi/package-lock.json index ae2b659..e575279 100644 --- a/apps/infoalloggi/package-lock.json +++ b/apps/infoalloggi/package-lock.json @@ -49,7 +49,6 @@ "embla-carousel-react": "^8.6.0", "framer-motion": "^12.38.0", "frimousse": "^0.3.0", - "ioredis": "^5.9.2", "jose": "^6.0.12", "kysely": "^0.28.17", "kysely-plugin-serialize": "^0.8.2", @@ -1818,10 +1817,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@ioredis/commands": { - "version": "1.5.0", - "license": "MIT" - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "license": "MIT", @@ -7420,13 +7415,6 @@ "node": ">=6" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cmdk": { "version": "1.1.1", "license": "MIT", @@ -7843,13 +7831,6 @@ "node": ">=0.4.0" } }, - "node_modules/denque": { - "version": "2.1.0", - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/dequal": { "version": "2.0.3", "license": "MIT", @@ -9041,28 +9022,6 @@ "node": ">= 0.10" } }, - "node_modules/ioredis": { - "version": "5.9.2", - "license": "MIT", - "dependencies": { - "@ioredis/commands": "1.5.0", - "cluster-key-slot": "^1.1.0", - "debug": "^4.3.4", - "denque": "^2.1.0", - "lodash.defaults": "^4.2.0", - "lodash.isarguments": "^3.1.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0", - "standard-as-callback": "^2.1.0" - }, - "engines": { - "node": ">=12.22.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ioredis" - } - }, "node_modules/is-array-buffer": { "version": "3.0.5", "dev": true, @@ -10120,14 +10079,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "license": "MIT" - }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "license": "MIT" - }, "node_modules/loose-envify": { "version": "1.4.0", "license": "MIT", @@ -12228,23 +12179,6 @@ "node": ">= 10.13.0" } }, - "node_modules/redis-errors": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "dev": true, @@ -12871,10 +12805,6 @@ "stacktrace-gps": "^3.0.4" } }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "license": "MIT" - }, "node_modules/std-env": { "version": "4.0.0", "dev": true, diff --git a/apps/infoalloggi/package.json b/apps/infoalloggi/package.json index b825200..843d38d 100644 --- a/apps/infoalloggi/package.json +++ b/apps/infoalloggi/package.json @@ -64,7 +64,6 @@ "embla-carousel-react": "^8.6.0", "framer-motion": "^12.38.0", "frimousse": "^0.3.0", - "ioredis": "^5.9.2", "jose": "^6.0.12", "kysely": "^0.28.17", "kysely-plugin-serialize": "^0.8.2", @@ -159,4 +158,4 @@ "axios": "^1.16.0" } } -} \ No newline at end of file +} diff --git a/apps/infoalloggi/src/env.ts b/apps/infoalloggi/src/env.ts index 5ddcff4..cda98e3 100644 --- a/apps/infoalloggi/src/env.ts +++ b/apps/infoalloggi/src/env.ts @@ -14,14 +14,12 @@ export const env = createEnv({ FIC_COMPANY_ID: z.string(), INTERNAL_BASE_URL: z.string(), JWT_SECRET: z.string(), - KEYDB_URL: z.string(), NODE_ENV: z.enum(["development", "test", "production"]), PGHOST: z.string(), PGPORT: z.string(), POSTGRES_DB: z.string(), POSTGRES_PASSWORD: z.string(), POSTGRES_USER: z.string(), - SKIP_REDIS: z.string(), STRIPE_SECRET_KEY: z.string(), STRIPE_WEBHOOK_SECRET: z.string(), TILES_URL: z.string(), diff --git a/apps/infoalloggi/src/pages/api/tiles/[[...slug]].ts b/apps/infoalloggi/src/pages/api/tiles/[[...slug]].ts index 78cf0f0..5422375 100644 --- a/apps/infoalloggi/src/pages/api/tiles/[[...slug]].ts +++ b/apps/infoalloggi/src/pages/api/tiles/[[...slug]].ts @@ -1,21 +1,10 @@ -import Redis from "ioredis"; import type { NextApiRequest, NextApiResponse } from "next"; -import { env } from "~/env"; +import { appRouter } from "~/server/api/root"; +import { createTRPCContext, t } from "~/server/api/trpc"; -let tiles: Redis | null = null; - -export function getTilesClient() { - if (env.SKIP_REDIS === "true") { - return null; - } - if (!tiles) { - tiles = new Redis(`keydb://${env.TILES_URL}`); - } - return tiles; -} /* * /api/tiles/[[...slug]] - * This API route is used to fetch tiles from OpenStreetMap and cache them in KeyDb. + * This API route is used to fetch tiles from OpenStreetMap and cache them. * The tile URL is constructed using the provided slug parameters: /api/tiles/s/z/x/y * The slug should be an array of 4 elements: [s, z, x, y]. * The API caches the tile for 30 days and sets appropriate headers for caching. @@ -53,14 +42,13 @@ export default async function handler( const cacheKey = `tile:${z}:${x}:${y}`; - const tiles = getTilesClient(); - if (!tiles) { - return res.status(500).json({ - error: "KeyDB client is not initialized", - }); - } + const ctx = await createTRPCContext({ req: req, res: res }); + + const createCaller = t.createCallerFactory(appRouter); + const caller = createCaller(ctx); + + const cachedTile = await caller.tiles.get(cacheKey); - const cachedTile = await tiles.getBuffer(cacheKey); if (cachedTile) { res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); res.setHeader("Content-Type", "image/png"); @@ -80,8 +68,9 @@ export default async function handler( } const tileData = await response.arrayBuffer(); const data = Buffer.from(tileData); + // Cache for 30 days - await tiles.setex(cacheKey, 2592000, data); + await caller.tiles.set({ key: cacheKey, value: data, ttlSec: 2592000 }); res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); res.setHeader("Content-Type", "image/png"); diff --git a/apps/infoalloggi/src/pages/area-riservata/admin/impostazioni.tsx b/apps/infoalloggi/src/pages/area-riservata/admin/impostazioni.tsx index 3a5ae50..d353238 100644 --- a/apps/infoalloggi/src/pages/area-riservata/admin/impostazioni.tsx +++ b/apps/infoalloggi/src/pages/area-riservata/admin/impostazioni.tsx @@ -1,8 +1,5 @@ import Head from "next/head"; -import toast from "react-hot-toast"; import { AreaRiservataLayout } from "~/components/Layout"; -import { LoadingPage } from "~/components/loading"; -import { Button } from "~/components/ui/button"; import type { NextPageWithLayout } from "~/pages/_app"; import { api } from "~/utils/api"; @@ -23,7 +20,7 @@ const Impostazioni: NextPageWithLayout = () => { - + {/* */} ); @@ -75,44 +72,44 @@ const StripeSection = () => { ); }; -const RedisCacheSection = () => { - const { data, isLoading } = api.revalidation.getRedisStats.useQuery(); - const utils = api.useUtils(); - const { mutate } = api.revalidation.invalidateRedisCache.useMutation({ - onMutate() { - toast.loading("Invalidating cache...", { - id: "invalidate-cache", - }); - }, - onSuccess: async () => { - toast.success("Cache invalidated successfully!", { - id: "invalidate-cache", - }); - await utils.revalidation.getRedisStats.invalidate(); - }, - onError() { - toast.error("Failed to invalidate cache.", { - id: "invalidate-cache", - }); - }, - }); - if (isLoading) { - return ; - } - return ( -
-

Invalidate Redis Cache

-

Current Cache Stats:

- {data?.success ? ( -
    -
  • Pdf Schede Annuncio: {data?.schedaCount}
  • -
  • Pdf Condizioni: {data?.condizioniCount}
  • -
- ) : ( -
Error fetching cache stats
- )} +// const RedisCacheSection = () => { +// const { data, isLoading } = api.revalidation.getRedisStats.useQuery(); +// const utils = api.useUtils(); +// const { mutate } = api.revalidation.invalidateRedisCache.useMutation({ +// onMutate() { +// toast.loading("Invalidating cache...", { +// id: "invalidate-cache", +// }); +// }, +// onSuccess: async () => { +// toast.success("Cache invalidated successfully!", { +// id: "invalidate-cache", +// }); +// await utils.revalidation.getRedisStats.invalidate(); +// }, +// onError() { +// toast.error("Failed to invalidate cache.", { +// id: "invalidate-cache", +// }); +// }, +// }); +// if (isLoading) { +// return ; +// } +// return ( +//
+//

Invalidate Redis Cache

+//

Current Cache Stats:

+// {data?.success ? ( +//
    +//
  • Pdf Schede Annuncio: {data?.schedaCount}
  • +//
  • Pdf Condizioni: {data?.condizioniCount}
  • +//
+// ) : ( +//
Error fetching cache stats
+// )} - -
- ); -}; +// +//
+// ); +// }; diff --git a/apps/infoalloggi/src/schemas/public/PublicSchema.ts b/apps/infoalloggi/src/schemas/public/PublicSchema.ts index c11ed33..e633cfb 100644 --- a/apps/infoalloggi/src/schemas/public/PublicSchema.ts +++ b/apps/infoalloggi/src/schemas/public/PublicSchema.ts @@ -11,6 +11,7 @@ import type { default as UsersStorageTable } from './UsersStorage'; import type { default as ServizioAnnunciTable } from './ServizioAnnunci'; import type { default as UserInvitesTable } from './UserInvites'; import type { default as EmailsTable } from './Emails'; +import type { default as TilesCacheTable } from './TilesCache'; import type { default as ComuniTable } from './Comuni'; import type { default as RinnoviTable } from './Rinnovi'; import type { default as EventQueueTable } from './EventQueue'; @@ -58,6 +59,8 @@ export default interface PublicSchema { emails: EmailsTable; + tiles_cache: TilesCacheTable; + comuni: ComuniTable; rinnovi: RinnoviTable; diff --git a/apps/infoalloggi/src/schemas/public/TilesCache.ts b/apps/infoalloggi/src/schemas/public/TilesCache.ts new file mode 100644 index 0000000..7f480b1 --- /dev/null +++ b/apps/infoalloggi/src/schemas/public/TilesCache.ts @@ -0,0 +1,40 @@ +import type { ColumnType, Selectable, Insertable, Updateable } from 'kysely'; +import { z } from 'zod'; + +/** Identifier type for public.tiles_cache */ +export type TilesCacheKey = string & { __brand: 'public.tiles_cache' }; + +/** Represents the table public.tiles_cache */ +export default interface TilesCacheTable { + key: ColumnType; + + value: ColumnType; + + expires_at: ColumnType; +} + +export type TilesCache = Selectable; + +export type NewTilesCache = Insertable; + +export type TilesCacheUpdate = Updateable; + +export const tilesCacheKey = z.string().transform(value => value as TilesCacheKey); + +export const TilesCacheSchema = z.object({ + key: tilesCacheKey, + value: z.instanceof(Buffer), + expires_at: z.date().nullable(), +}); + +export const NewTilesCacheSchema = z.object({ + key: tilesCacheKey, + value: z.instanceof(Buffer), + expires_at: z.union([z.date(), z.string()]).pipe(z.coerce.date()).optional().nullable(), +}); + +export const TilesCacheUpdateSchema = z.object({ + key: tilesCacheKey.optional(), + value: z.instanceof(Buffer).optional(), + expires_at: z.union([z.date(), z.string()]).pipe(z.coerce.date()).optional().nullable(), +}); \ No newline at end of file diff --git a/apps/infoalloggi/src/server/api/root.ts b/apps/infoalloggi/src/server/api/root.ts index 0dcc35b..3147fdb 100644 --- a/apps/infoalloggi/src/server/api/root.ts +++ b/apps/infoalloggi/src/server/api/root.ts @@ -15,6 +15,7 @@ import { storageRouter } from "~/server/api/routers/storage"; import { stripeRouter } from "~/server/api/routers/stripe"; import { stripeReportsRouter } from "~/server/api/routers/stripe_reports"; import { testRouter } from "~/server/api/routers/test"; +import { tilesRouter } from "~/server/api/routers/tiles"; import { usersRouter } from "~/server/api/routers/users"; import { createTRPCRouter } from "~/server/api/trpc"; import { appuntiRouter } from "./routers/appunti"; @@ -48,6 +49,7 @@ export const appRouter = createTRPCRouter({ test: testRouter, users: usersRouter, //sync: syncRouter, + tiles: tilesRouter, catasto: catastoRouter, banners: bannersRouter, flags: flagsRouter, diff --git a/apps/infoalloggi/src/server/api/routers/annunci.ts b/apps/infoalloggi/src/server/api/routers/annunci.ts index a92d985..a3dfc1b 100644 --- a/apps/infoalloggi/src/server/api/routers/annunci.ts +++ b/apps/infoalloggi/src/server/api/routers/annunci.ts @@ -25,10 +25,6 @@ import { } from "~/server/controllers/annunci.controller"; import { getIncrociAnnuncio } from "~/server/controllers/servizio.controller"; import { db } from "~/server/db"; -import { - invalidateCache, - SCHEDA_ANNUNCIO_CACHE_PREFIX, -} from "~/server/services/cache.service"; import { NewMail } from "~/server/services/mailer"; import { TypstGenerate } from "~/server/services/typst.service"; @@ -36,10 +32,6 @@ export const annunciRouter = createTRPCRouter({ editAnnuncio: adminProcedure .input(z.object({ annuncioId: annunciId, data: AnnunciUpdateSchema })) .mutation(async ({ input }) => { - await invalidateCache( - `${SCHEDA_ANNUNCIO_CACHE_PREFIX}${input.annuncioId}`, - ); - return await editAnnuncioHandler({ ...input, }); diff --git a/apps/infoalloggi/src/server/api/routers/revalidation.ts b/apps/infoalloggi/src/server/api/routers/revalidation.ts index 7b3537f..c4c5b36 100644 --- a/apps/infoalloggi/src/server/api/routers/revalidation.ts +++ b/apps/infoalloggi/src/server/api/routers/revalidation.ts @@ -3,10 +3,6 @@ import { z } from "zod/v4"; import { env } from "~/env"; import { adminProcedure, createTRPCRouter } from "~/server/api/trpc"; import { getCodici_AnnunciHandler } from "~/server/controllers/annunci.controller"; -import { - getCacheStats, - invalidateAllCaches, -} from "~/server/services/cache.service"; import { revalidate, revalidateMultiple, @@ -135,11 +131,10 @@ export const revalidationRouter = createTRPCRouter({ } }), getRedisStats: adminProcedure.query(async () => { - return await getCacheStats(); + return "await getCacheStats();"; }), invalidateRedisCache: adminProcedure.mutation(async () => { try { - await invalidateAllCaches(); return { status: "success", }; diff --git a/apps/infoalloggi/src/server/api/routers/tiles.ts b/apps/infoalloggi/src/server/api/routers/tiles.ts new file mode 100644 index 0000000..dae9b3e --- /dev/null +++ b/apps/infoalloggi/src/server/api/routers/tiles.ts @@ -0,0 +1,20 @@ +import z from "zod"; +import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; +import { get, set } from "~/server/services/tiles.service"; + +export const tilesRouter = createTRPCRouter({ + get: publicProcedure.input(z.string()).query(async ({ input }) => { + return await get(input); + }), + set: publicProcedure + .input( + z.object({ + key: z.string(), + value: z.instanceof(Buffer), + ttlSec: z.number().optional(), + }), + ) + .mutation(async ({ input }) => { + return await set(input.key, input.value, input.ttlSec); + }), +}); diff --git a/apps/infoalloggi/src/server/controllers/event_queue.controller.ts b/apps/infoalloggi/src/server/controllers/event_queue.controller.ts index 50b2382..e8120c8 100644 --- a/apps/infoalloggi/src/server/controllers/event_queue.controller.ts +++ b/apps/infoalloggi/src/server/controllers/event_queue.controller.ts @@ -24,11 +24,6 @@ import { convertHtmlToTypst, TypstGenerate, } from "~/server/services/typst.service"; -import { getKeydbClient } from "~/utils/keydb"; -import { - CONDIZIONI_CACHE_PREFIX, - SCHEDA_ANNUNCIO_CACHE_PREFIX, -} from "../services/cache.service"; type GeneratedAttachmentTypes = | { type: "scheda_annuncio"; annuncioId: AnnunciId } @@ -57,14 +52,6 @@ const generatePdfContent = async (params: GeneratedAttachmentTypes) => { } case "condizioni": { - const keybd = getKeydbClient(); - const cacheKey = `${CONDIZIONI_CACHE_PREFIX}${params.stringId}`; - if (keybd) { - const cached = await keybd.get(cacheKey); - if (cached) { - return cached; - } - } const data = await db .selectFrom("testi_e_stringhe") .selectAll() @@ -83,30 +70,17 @@ const generatePdfContent = async (params: GeneratedAttachmentTypes) => { data: { stringa }, }); const pdfBase64 = pdf.toString("base64"); - if (keybd) { - await keybd.set(cacheKey, pdfBase64, "EX", 3600 * 24 * 5); - } return pdfBase64; } case "scheda_annuncio": { - const keybd = getKeydbClient(); - const cacheKey = `${SCHEDA_ANNUNCIO_CACHE_PREFIX}${params.annuncioId}`; - if (keybd) { - const cached = await keybd.get(cacheKey); - if (cached) { - return cached; - } - } const data = await getAnnunciSchedaData(params.annuncioId); const pdf = await TypstGenerate({ templateId: "annuncio.typ", data, }); const pdfBase64 = pdf.toString("base64"); - if (keybd) { - await keybd.set(cacheKey, pdfBase64, "EX", 3600 * 5); - } + return pdfBase64; } default: diff --git a/apps/infoalloggi/src/server/services/cache.service.ts b/apps/infoalloggi/src/server/services/cache.service.ts deleted file mode 100644 index 8a95328..0000000 --- a/apps/infoalloggi/src/server/services/cache.service.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type Redis from "ioredis"; -import { getKeydbClient } from "~/utils/keydb"; - -export const SCHEDA_ANNUNCIO_CACHE_PREFIX = "scheda-annuncio-"; -export const CONDIZIONI_CACHE_PREFIX = "condizioni-"; -export const invalidateCache = async (cacheKey: string) => { - const keybd = getKeydbClient(); - if (keybd) { - await keybd.del(cacheKey); - } -}; - -async function deleteByPrefix(redis: Redis, prefix: string) { - // Create a stream to find keys matching the pattern - const stream = redis.scanStream({ - match: `${prefix}*`, - count: 100, // Process 100 keys per iteration - }); - - stream.on("data", async (keys: string[]) => { - if (keys.length > 0) { - // Create a pipeline to delete keys in a single network round-trip - const pipeline = redis.pipeline(); - - // Use UNLINK instead of DEL for better performance on large keys - keys.forEach((key) => pipeline.unlink(key)); - - await pipeline.exec(); - console.log(`Deleted ${keys.length} keys`); - } - }); - - stream.on("error", (err) => { - console.error( - `Error scanning/unlinking keys with prefix ${prefix}: ${err.message}`, - ); - }); -} - -export const invalidateAllCaches = async () => { - const keybd = getKeydbClient(); - if (keybd) { - await deleteByPrefix(keybd, SCHEDA_ANNUNCIO_CACHE_PREFIX); - await deleteByPrefix(keybd, CONDIZIONI_CACHE_PREFIX); - } -}; - -export const getCacheStats = async () => { - const keybd = getKeydbClient(); - if (keybd) { - const schedaKeys = await keybd.keys(`${SCHEDA_ANNUNCIO_CACHE_PREFIX}*`); - const condizioniKeys = await keybd.keys(`${CONDIZIONI_CACHE_PREFIX}*`); - return { - success: true, - schedaCount: schedaKeys.length, - condizioniCount: condizioniKeys.length, - }; - } - return { - success: false, - schedaCount: 0, - condizioniCount: 0, - }; -}; diff --git a/apps/infoalloggi/src/server/services/tiles.service.ts b/apps/infoalloggi/src/server/services/tiles.service.ts new file mode 100644 index 0000000..a914b50 --- /dev/null +++ b/apps/infoalloggi/src/server/services/tiles.service.ts @@ -0,0 +1,58 @@ +import { TRPCError } from "@trpc/server"; +import type { TilesCacheKey } from "~/schemas/public/TilesCache"; + +import { db } from "~/server/db"; + +export async function get(key: string) { + try { + const row = await db + .selectFrom("tiles_cache") + .select(["value", "expires_at"]) + .where("key", "=", key as TilesCacheKey) + // If the row is expired we treat it as missing + .where((eb) => + eb.or([ + eb("expires_at", "is", null), + eb("expires_at", ">", new Date()), + ]), + ) + .executeTakeFirst(); + + return row?.value; + } catch (e) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: `Failed get kv value: ${(e as Error).message}`, + }); + } +} + +export async function set( + key: string, + value: Buffer, + ttlSec?: number, +): Promise { + const expiresAt = ttlSec ? new Date(Date.now() + ttlSec * 1_000) : null; + + try { + await db + .insertInto("tiles_cache") + .values({ + key: key as TilesCacheKey, + value: value, + expires_at: expiresAt, + }) + .onConflict((oc) => + oc.column("key").doUpdateSet({ + value: value, + expires_at: expiresAt, + }), + ) + .execute(); + } catch (e) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: `Error setting KV: ${(e as Error).message}`, + }); + } +} diff --git a/apps/infoalloggi/src/utils/keydb.ts b/apps/infoalloggi/src/utils/keydb.ts deleted file mode 100644 index 7598642..0000000 --- a/apps/infoalloggi/src/utils/keydb.ts +++ /dev/null @@ -1,19 +0,0 @@ -import Redis from "ioredis"; -import { env } from "~/env"; - -const keydbConfig = `keydb://${env.KEYDB_URL}`; - -let keydb: Redis | null = null; - -export function getKeydbClient() { - if (env.SKIP_REDIS === "true") return null; - if (!keydb) { - keydb = new Redis(keydbConfig); - } - return keydb; -} - -export function getSubClient() { - if (env.SKIP_REDIS === "true") return null; - return new Redis(keydbConfig, { enableReadyCheck: false }); -} diff --git a/docker-compose.yml b/docker-compose.yml index 71a0276..d74c455 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,45 +57,6 @@ services: retries: 5 start_period: 10s - tiles: - image: eqalpha/keydb:latest - volumes: - - tiles-data:/data - networks: - - dokploy-network - expose: - - 6379 - command: keydb-server /etc/keydb/keydb.conf --maxmemory 100mb --maxmemory-policy volatile-lru --maxmemory-samples 5 - healthcheck: - test: ["CMD", "keydb-cli", "-h", "localhost", "ping"] - interval: 5s - timeout: 3s - retries: 5 - restart: unless-stopped - deploy: - resources: - limits: - cpus: "0.5" - - keydb: - image: eqalpha/keydb:latest - volumes: - - keydb-data:/data - networks: - - dokploy-network - expose: - - 6379 - healthcheck: - test: ["CMD", "keydb-cli", "-h", "localhost", "ping"] - interval: 5s - timeout: 3s - retries: 5 - restart: unless-stopped - deploy: - resources: - limits: - cpus: "0.5" - backend: image: "${IMAGE_REGISTRY:-ghcr.io}/${OWNER:-marcopedone}/${PREFIX:-infoalloggi}-backend:${TAG:-latest}" networks: @@ -124,10 +85,6 @@ services: condition: service_healthy db: condition: service_healthy - keydb: - condition: service_healthy - tiles: - condition: service_healthy web: image: "${IMAGE_REGISTRY:-ghcr.io}/${OWNER:-marcopedone}/${PREFIX:-infoalloggi}-web:${TAG:-latest}" @@ -142,8 +99,6 @@ services: PGHOST: db PGPORT: "5432" BACKENDSERVER_URL: http://backend:1323 - KEYDB_URL: keydb:6379 - TILES_URL: tiles:6379 BASE_URL: ${BASE_URL} NEXT_PUBLIC_BASE_URL: ${BASE_URL} STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY} @@ -196,10 +151,6 @@ services: condition: service_healthy db: condition: service_healthy - keydb: - condition: service_healthy - tiles: - condition: service_healthy maintenance-page: image: nginx:alpine restart: always @@ -224,6 +175,4 @@ networks: volumes: storage_data: - tiles-data: - keydb-data: dbdata_v18: