removed tiles redis, now using pg
This commit is contained in:
parent
b0f39f5f11
commit
92779d8b4e
23 changed files with 202 additions and 344 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
|
@ -36,10 +36,7 @@ env:
|
||||||
ARUBA_PASS: "string"
|
ARUBA_PASS: "string"
|
||||||
EXP_API_USER: "string"
|
EXP_API_USER: "string"
|
||||||
EXP_API_PASS: "string"
|
EXP_API_PASS: "string"
|
||||||
TILES_URL: "keydb://string"
|
|
||||||
KEYDB_URL: "keydb://string"
|
|
||||||
JWT_SECRET: "335922ae"
|
JWT_SECRET: "335922ae"
|
||||||
SKIP_REDIS: "false"
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
||||||
17
apps/db/migrations/54_kvcache.up.sql
Normal file
17
apps/db/migrations/54_kvcache.up.sql
Normal file
|
|
@ -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);
|
||||||
|
|
@ -7,7 +7,7 @@ node_modules
|
||||||
.github
|
.github
|
||||||
Dockerfile
|
Dockerfile
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
keydb-docker-compose.yml
|
dev-docker-compose.yml
|
||||||
pgadmin-docker-compose.yml
|
pgadmin-docker-compose.yml
|
||||||
dev_utils_compose.sh
|
dev_utils_compose.sh
|
||||||
.env
|
.env
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
NEXT_TELEMETRY_DISABLED=1 \
|
NEXT_TELEMETRY_DISABLED=1
|
||||||
SKIP_REDIS="true"
|
|
||||||
|
|
||||||
ARG BASE_URL NEXT_PUBLIC_BASE_URL NEXT_PUBLIC_STRIPE_PUBLIC_KEY
|
ARG BASE_URL NEXT_PUBLIC_BASE_URL NEXT_PUBLIC_STRIPE_PUBLIC_KEY
|
||||||
ENV BASE_URL=$BASE_URL \
|
ENV BASE_URL=$BASE_URL \
|
||||||
|
|
@ -29,8 +28,7 @@ FROM base AS runner
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
NEXT_TELEMETRY_DISABLED=1 \
|
NEXT_TELEMETRY_DISABLED=1
|
||||||
SKIP_REDIS="false"
|
|
||||||
|
|
||||||
ARG BASE_URL
|
ARG BASE_URL
|
||||||
ENV BASE_URL=$BASE_URL
|
ENV BASE_URL=$BASE_URL
|
||||||
|
|
@ -72,8 +70,6 @@ ARG EXP_API_USER
|
||||||
ENV EXP_API_USER=$EXP_API_USER
|
ENV EXP_API_USER=$EXP_API_USER
|
||||||
ARG EXP_API_PASS
|
ARG EXP_API_PASS
|
||||||
ENV EXP_API_PASS=$EXP_API_PASS
|
ENV EXP_API_PASS=$EXP_API_PASS
|
||||||
ARG KEYDB_URL
|
|
||||||
ENV KEYDB_URL=$KEYDB_URL
|
|
||||||
ARG TILES_URL
|
ARG TILES_URL
|
||||||
ENV TILES_URL=$TILES_URL
|
ENV TILES_URL=$TILES_URL
|
||||||
ARG SKEBBY_USER
|
ARG SKEBBY_USER
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ TODOS:
|
||||||
- pre attivazione se admin mostrare tastino contatti in preview
|
- pre attivazione se admin mostrare tastino contatti in preview
|
||||||
- onboarding submit (toast e email che invitano a pagare)
|
- onboarding submit (toast e email che invitano a pagare)
|
||||||
- migliorare onboarding e pag guida
|
- migliorare onboarding e pag guida
|
||||||
|
- "fatto_nulla" status in servizio
|
||||||
AFTER MVP:
|
AFTER MVP:
|
||||||
- TODO migrazione app router https://nextjs.org/docs/pages/building-your-application/upgrading/app-router-migration#migrating-from-pages-to-app
|
- 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
|
- Sezione "La mia locazione" con i dati della locazione attuale dell'utente
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,4 @@
|
||||||
services:
|
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:
|
storage:
|
||||||
image: "ghcr.io/marcopedone/go_fs:latest"
|
image: "ghcr.io/marcopedone/go_fs:latest"
|
||||||
ports:
|
ports:
|
||||||
|
|
@ -34,6 +18,4 @@ services:
|
||||||
start_period: 10s
|
start_period: 10s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
tiles-data:
|
|
||||||
keydb-data:
|
|
||||||
storage_data:
|
storage_data:
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,8 @@ module.exports = {
|
||||||
"pg_catalog.bpchar": "string",
|
"pg_catalog.bpchar": "string",
|
||||||
'pg_catalog.numeric': 'number',
|
'pg_catalog.numeric': 'number',
|
||||||
//"pg_catalog.json": "string",
|
//"pg_catalog.json": "string",
|
||||||
|
'pg_catalog.bytea': "Buffer"
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
preRenderHooks: [
|
preRenderHooks: [
|
||||||
|
|
@ -177,6 +179,7 @@ module.exports = {
|
||||||
zodTypeMap: {
|
zodTypeMap: {
|
||||||
...defaultZodTypeMap,
|
...defaultZodTypeMap,
|
||||||
"pg_catalog.numeric": "z.number()",
|
"pg_catalog.numeric": "z.number()",
|
||||||
|
"pg_catalog.bytea": "z.instanceof(Buffer)"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
70
apps/infoalloggi/package-lock.json
generated
70
apps/infoalloggi/package-lock.json
generated
|
|
@ -49,7 +49,6 @@
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
"frimousse": "^0.3.0",
|
"frimousse": "^0.3.0",
|
||||||
"ioredis": "^5.9.2",
|
|
||||||
"jose": "^6.0.12",
|
"jose": "^6.0.12",
|
||||||
"kysely": "^0.28.17",
|
"kysely": "^0.28.17",
|
||||||
"kysely-plugin-serialize": "^0.8.2",
|
"kysely-plugin-serialize": "^0.8.2",
|
||||||
|
|
@ -1818,10 +1817,6 @@
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ioredis/commands": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@jridgewell/gen-mapping": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -7420,13 +7415,6 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cluster-key-slot": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cmdk": {
|
"node_modules/cmdk": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -7843,13 +7831,6 @@
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/denque": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/dequal": {
|
"node_modules/dequal": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -9041,28 +9022,6 @@
|
||||||
"node": ">= 0.10"
|
"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": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
@ -10120,14 +10079,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/loose-envify": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -12228,23 +12179,6 @@
|
||||||
"node": ">= 10.13.0"
|
"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": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
@ -12871,10 +12805,6 @@
|
||||||
"stacktrace-gps": "^3.0.4"
|
"stacktrace-gps": "^3.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/standard-as-callback": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/std-env": {
|
"node_modules/std-env": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,6 @@
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"framer-motion": "^12.38.0",
|
"framer-motion": "^12.38.0",
|
||||||
"frimousse": "^0.3.0",
|
"frimousse": "^0.3.0",
|
||||||
"ioredis": "^5.9.2",
|
|
||||||
"jose": "^6.0.12",
|
"jose": "^6.0.12",
|
||||||
"kysely": "^0.28.17",
|
"kysely": "^0.28.17",
|
||||||
"kysely-plugin-serialize": "^0.8.2",
|
"kysely-plugin-serialize": "^0.8.2",
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,12 @@ export const env = createEnv({
|
||||||
FIC_COMPANY_ID: z.string(),
|
FIC_COMPANY_ID: z.string(),
|
||||||
INTERNAL_BASE_URL: z.string(),
|
INTERNAL_BASE_URL: z.string(),
|
||||||
JWT_SECRET: z.string(),
|
JWT_SECRET: z.string(),
|
||||||
KEYDB_URL: z.string(),
|
|
||||||
NODE_ENV: z.enum(["development", "test", "production"]),
|
NODE_ENV: z.enum(["development", "test", "production"]),
|
||||||
PGHOST: z.string(),
|
PGHOST: z.string(),
|
||||||
PGPORT: z.string(),
|
PGPORT: z.string(),
|
||||||
POSTGRES_DB: z.string(),
|
POSTGRES_DB: z.string(),
|
||||||
POSTGRES_PASSWORD: z.string(),
|
POSTGRES_PASSWORD: z.string(),
|
||||||
POSTGRES_USER: z.string(),
|
POSTGRES_USER: z.string(),
|
||||||
SKIP_REDIS: z.string(),
|
|
||||||
STRIPE_SECRET_KEY: z.string(),
|
STRIPE_SECRET_KEY: z.string(),
|
||||||
STRIPE_WEBHOOK_SECRET: z.string(),
|
STRIPE_WEBHOOK_SECRET: z.string(),
|
||||||
TILES_URL: z.string(),
|
TILES_URL: z.string(),
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,10 @@
|
||||||
import Redis from "ioredis";
|
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
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]]
|
* /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 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 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.
|
* 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 cacheKey = `tile:${z}:${x}:${y}`;
|
||||||
|
|
||||||
const tiles = getTilesClient();
|
const ctx = await createTRPCContext({ req: req, res: res });
|
||||||
if (!tiles) {
|
|
||||||
return res.status(500).json({
|
const createCaller = t.createCallerFactory(appRouter);
|
||||||
error: "KeyDB client is not initialized",
|
const caller = createCaller(ctx);
|
||||||
});
|
|
||||||
}
|
const cachedTile = await caller.tiles.get(cacheKey);
|
||||||
|
|
||||||
const cachedTile = await tiles.getBuffer(cacheKey);
|
|
||||||
if (cachedTile) {
|
if (cachedTile) {
|
||||||
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
||||||
res.setHeader("Content-Type", "image/png");
|
res.setHeader("Content-Type", "image/png");
|
||||||
|
|
@ -80,8 +68,9 @@ export default async function handler(
|
||||||
}
|
}
|
||||||
const tileData = await response.arrayBuffer();
|
const tileData = await response.arrayBuffer();
|
||||||
const data = Buffer.from(tileData);
|
const data = Buffer.from(tileData);
|
||||||
|
|
||||||
// Cache for 30 days
|
// 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("Cache-Control", "public, max-age=31536000, immutable");
|
||||||
res.setHeader("Content-Type", "image/png");
|
res.setHeader("Content-Type", "image/png");
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import toast from "react-hot-toast";
|
|
||||||
import { AreaRiservataLayout } from "~/components/Layout";
|
import { AreaRiservataLayout } from "~/components/Layout";
|
||||||
import { LoadingPage } from "~/components/loading";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
|
|
@ -23,7 +20,7 @@ const Impostazioni: NextPageWithLayout = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<StripeSection />
|
<StripeSection />
|
||||||
<RedisCacheSection />
|
{/* <RedisCacheSection /> */}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -75,44 +72,44 @@ const StripeSection = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RedisCacheSection = () => {
|
// const RedisCacheSection = () => {
|
||||||
const { data, isLoading } = api.revalidation.getRedisStats.useQuery();
|
// const { data, isLoading } = api.revalidation.getRedisStats.useQuery();
|
||||||
const utils = api.useUtils();
|
// const utils = api.useUtils();
|
||||||
const { mutate } = api.revalidation.invalidateRedisCache.useMutation({
|
// const { mutate } = api.revalidation.invalidateRedisCache.useMutation({
|
||||||
onMutate() {
|
// onMutate() {
|
||||||
toast.loading("Invalidating cache...", {
|
// toast.loading("Invalidating cache...", {
|
||||||
id: "invalidate-cache",
|
// id: "invalidate-cache",
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
onSuccess: async () => {
|
// onSuccess: async () => {
|
||||||
toast.success("Cache invalidated successfully!", {
|
// toast.success("Cache invalidated successfully!", {
|
||||||
id: "invalidate-cache",
|
// id: "invalidate-cache",
|
||||||
});
|
// });
|
||||||
await utils.revalidation.getRedisStats.invalidate();
|
// await utils.revalidation.getRedisStats.invalidate();
|
||||||
},
|
// },
|
||||||
onError() {
|
// onError() {
|
||||||
toast.error("Failed to invalidate cache.", {
|
// toast.error("Failed to invalidate cache.", {
|
||||||
id: "invalidate-cache",
|
// id: "invalidate-cache",
|
||||||
});
|
// });
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
if (isLoading) {
|
// if (isLoading) {
|
||||||
return <LoadingPage />;
|
// return <LoadingPage />;
|
||||||
}
|
// }
|
||||||
return (
|
// return (
|
||||||
<div className="space-y-4 rounded-lg border p-4">
|
// <div className="space-y-4 rounded-lg border p-4">
|
||||||
<h4 className="font-semibold text-lg">Invalidate Redis Cache</h4>
|
// <h4 className="font-semibold text-lg">Invalidate Redis Cache</h4>
|
||||||
<p>Current Cache Stats:</p>
|
// <p>Current Cache Stats:</p>
|
||||||
{data?.success ? (
|
// {data?.success ? (
|
||||||
<ul className="list-inside list-disc">
|
// <ul className="list-inside list-disc">
|
||||||
<li>Pdf Schede Annuncio: {data?.schedaCount}</li>
|
// <li>Pdf Schede Annuncio: {data?.schedaCount}</li>
|
||||||
<li>Pdf Condizioni: {data?.condizioniCount}</li>
|
// <li>Pdf Condizioni: {data?.condizioniCount}</li>
|
||||||
</ul>
|
// </ul>
|
||||||
) : (
|
// ) : (
|
||||||
<div className="text-red-500">Error fetching cache stats</div>
|
// <div className="text-red-500">Error fetching cache stats</div>
|
||||||
)}
|
// )}
|
||||||
|
|
||||||
<Button onClick={() => mutate()}>Invalidate Cache</Button>
|
// <Button onClick={() => mutate()}>Invalidate Cache</Button>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import type { default as UsersStorageTable } from './UsersStorage';
|
||||||
import type { default as ServizioAnnunciTable } from './ServizioAnnunci';
|
import type { default as ServizioAnnunciTable } from './ServizioAnnunci';
|
||||||
import type { default as UserInvitesTable } from './UserInvites';
|
import type { default as UserInvitesTable } from './UserInvites';
|
||||||
import type { default as EmailsTable } from './Emails';
|
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 ComuniTable } from './Comuni';
|
||||||
import type { default as RinnoviTable } from './Rinnovi';
|
import type { default as RinnoviTable } from './Rinnovi';
|
||||||
import type { default as EventQueueTable } from './EventQueue';
|
import type { default as EventQueueTable } from './EventQueue';
|
||||||
|
|
@ -58,6 +59,8 @@ export default interface PublicSchema {
|
||||||
|
|
||||||
emails: EmailsTable;
|
emails: EmailsTable;
|
||||||
|
|
||||||
|
tiles_cache: TilesCacheTable;
|
||||||
|
|
||||||
comuni: ComuniTable;
|
comuni: ComuniTable;
|
||||||
|
|
||||||
rinnovi: RinnoviTable;
|
rinnovi: RinnoviTable;
|
||||||
|
|
|
||||||
40
apps/infoalloggi/src/schemas/public/TilesCache.ts
Normal file
40
apps/infoalloggi/src/schemas/public/TilesCache.ts
Normal file
|
|
@ -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<TilesCacheKey, TilesCacheKey, TilesCacheKey>;
|
||||||
|
|
||||||
|
value: ColumnType<Buffer, Buffer, Buffer>;
|
||||||
|
|
||||||
|
expires_at: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TilesCache = Selectable<TilesCacheTable>;
|
||||||
|
|
||||||
|
export type NewTilesCache = Insertable<TilesCacheTable>;
|
||||||
|
|
||||||
|
export type TilesCacheUpdate = Updateable<TilesCacheTable>;
|
||||||
|
|
||||||
|
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(),
|
||||||
|
});
|
||||||
|
|
@ -15,6 +15,7 @@ import { storageRouter } from "~/server/api/routers/storage";
|
||||||
import { stripeRouter } from "~/server/api/routers/stripe";
|
import { stripeRouter } from "~/server/api/routers/stripe";
|
||||||
import { stripeReportsRouter } from "~/server/api/routers/stripe_reports";
|
import { stripeReportsRouter } from "~/server/api/routers/stripe_reports";
|
||||||
import { testRouter } from "~/server/api/routers/test";
|
import { testRouter } from "~/server/api/routers/test";
|
||||||
|
import { tilesRouter } from "~/server/api/routers/tiles";
|
||||||
import { usersRouter } from "~/server/api/routers/users";
|
import { usersRouter } from "~/server/api/routers/users";
|
||||||
import { createTRPCRouter } from "~/server/api/trpc";
|
import { createTRPCRouter } from "~/server/api/trpc";
|
||||||
import { appuntiRouter } from "./routers/appunti";
|
import { appuntiRouter } from "./routers/appunti";
|
||||||
|
|
@ -48,6 +49,7 @@ export const appRouter = createTRPCRouter({
|
||||||
test: testRouter,
|
test: testRouter,
|
||||||
users: usersRouter,
|
users: usersRouter,
|
||||||
//sync: syncRouter,
|
//sync: syncRouter,
|
||||||
|
tiles: tilesRouter,
|
||||||
catasto: catastoRouter,
|
catasto: catastoRouter,
|
||||||
banners: bannersRouter,
|
banners: bannersRouter,
|
||||||
flags: flagsRouter,
|
flags: flagsRouter,
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,6 @@ import {
|
||||||
} from "~/server/controllers/annunci.controller";
|
} from "~/server/controllers/annunci.controller";
|
||||||
import { getIncrociAnnuncio } from "~/server/controllers/servizio.controller";
|
import { getIncrociAnnuncio } from "~/server/controllers/servizio.controller";
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import {
|
|
||||||
invalidateCache,
|
|
||||||
SCHEDA_ANNUNCIO_CACHE_PREFIX,
|
|
||||||
} from "~/server/services/cache.service";
|
|
||||||
import { NewMail } from "~/server/services/mailer";
|
import { NewMail } from "~/server/services/mailer";
|
||||||
import { TypstGenerate } from "~/server/services/typst.service";
|
import { TypstGenerate } from "~/server/services/typst.service";
|
||||||
|
|
||||||
|
|
@ -36,10 +32,6 @@ export const annunciRouter = createTRPCRouter({
|
||||||
editAnnuncio: adminProcedure
|
editAnnuncio: adminProcedure
|
||||||
.input(z.object({ annuncioId: annunciId, data: AnnunciUpdateSchema }))
|
.input(z.object({ annuncioId: annunciId, data: AnnunciUpdateSchema }))
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
await invalidateCache(
|
|
||||||
`${SCHEDA_ANNUNCIO_CACHE_PREFIX}${input.annuncioId}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return await editAnnuncioHandler({
|
return await editAnnuncioHandler({
|
||||||
...input,
|
...input,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,6 @@ import { z } from "zod/v4";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import { adminProcedure, createTRPCRouter } from "~/server/api/trpc";
|
import { adminProcedure, createTRPCRouter } from "~/server/api/trpc";
|
||||||
import { getCodici_AnnunciHandler } from "~/server/controllers/annunci.controller";
|
import { getCodici_AnnunciHandler } from "~/server/controllers/annunci.controller";
|
||||||
import {
|
|
||||||
getCacheStats,
|
|
||||||
invalidateAllCaches,
|
|
||||||
} from "~/server/services/cache.service";
|
|
||||||
import {
|
import {
|
||||||
revalidate,
|
revalidate,
|
||||||
revalidateMultiple,
|
revalidateMultiple,
|
||||||
|
|
@ -135,11 +131,10 @@ export const revalidationRouter = createTRPCRouter({
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
getRedisStats: adminProcedure.query(async () => {
|
getRedisStats: adminProcedure.query(async () => {
|
||||||
return await getCacheStats();
|
return "await getCacheStats();";
|
||||||
}),
|
}),
|
||||||
invalidateRedisCache: adminProcedure.mutation(async () => {
|
invalidateRedisCache: adminProcedure.mutation(async () => {
|
||||||
try {
|
try {
|
||||||
await invalidateAllCaches();
|
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
20
apps/infoalloggi/src/server/api/routers/tiles.ts
Normal file
20
apps/infoalloggi/src/server/api/routers/tiles.ts
Normal file
|
|
@ -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);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
@ -24,11 +24,6 @@ import {
|
||||||
convertHtmlToTypst,
|
convertHtmlToTypst,
|
||||||
TypstGenerate,
|
TypstGenerate,
|
||||||
} from "~/server/services/typst.service";
|
} 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 GeneratedAttachmentTypes =
|
||||||
| { type: "scheda_annuncio"; annuncioId: AnnunciId }
|
| { type: "scheda_annuncio"; annuncioId: AnnunciId }
|
||||||
|
|
@ -57,14 +52,6 @@ const generatePdfContent = async (params: GeneratedAttachmentTypes) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
case "condizioni": {
|
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
|
const data = await db
|
||||||
.selectFrom("testi_e_stringhe")
|
.selectFrom("testi_e_stringhe")
|
||||||
.selectAll()
|
.selectAll()
|
||||||
|
|
@ -83,30 +70,17 @@ const generatePdfContent = async (params: GeneratedAttachmentTypes) => {
|
||||||
data: { stringa },
|
data: { stringa },
|
||||||
});
|
});
|
||||||
const pdfBase64 = pdf.toString("base64");
|
const pdfBase64 = pdf.toString("base64");
|
||||||
if (keybd) {
|
|
||||||
await keybd.set(cacheKey, pdfBase64, "EX", 3600 * 24 * 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pdfBase64;
|
return pdfBase64;
|
||||||
}
|
}
|
||||||
case "scheda_annuncio": {
|
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 data = await getAnnunciSchedaData(params.annuncioId);
|
||||||
const pdf = await TypstGenerate({
|
const pdf = await TypstGenerate({
|
||||||
templateId: "annuncio.typ",
|
templateId: "annuncio.typ",
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
const pdfBase64 = pdf.toString("base64");
|
const pdfBase64 = pdf.toString("base64");
|
||||||
if (keybd) {
|
|
||||||
await keybd.set(cacheKey, pdfBase64, "EX", 3600 * 5);
|
|
||||||
}
|
|
||||||
return pdfBase64;
|
return pdfBase64;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
58
apps/infoalloggi/src/server/services/tiles.service.ts
Normal file
58
apps/infoalloggi/src/server/services/tiles.service.ts
Normal file
|
|
@ -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<void> {
|
||||||
|
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}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 });
|
|
||||||
}
|
|
||||||
|
|
@ -57,45 +57,6 @@ services:
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 10s
|
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:
|
backend:
|
||||||
image: "${IMAGE_REGISTRY:-ghcr.io}/${OWNER:-marcopedone}/${PREFIX:-infoalloggi}-backend:${TAG:-latest}"
|
image: "${IMAGE_REGISTRY:-ghcr.io}/${OWNER:-marcopedone}/${PREFIX:-infoalloggi}-backend:${TAG:-latest}"
|
||||||
networks:
|
networks:
|
||||||
|
|
@ -124,10 +85,6 @@ services:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
keydb:
|
|
||||||
condition: service_healthy
|
|
||||||
tiles:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: "${IMAGE_REGISTRY:-ghcr.io}/${OWNER:-marcopedone}/${PREFIX:-infoalloggi}-web:${TAG:-latest}"
|
image: "${IMAGE_REGISTRY:-ghcr.io}/${OWNER:-marcopedone}/${PREFIX:-infoalloggi}-web:${TAG:-latest}"
|
||||||
|
|
@ -142,8 +99,6 @@ services:
|
||||||
PGHOST: db
|
PGHOST: db
|
||||||
PGPORT: "5432"
|
PGPORT: "5432"
|
||||||
BACKENDSERVER_URL: http://backend:1323
|
BACKENDSERVER_URL: http://backend:1323
|
||||||
KEYDB_URL: keydb:6379
|
|
||||||
TILES_URL: tiles:6379
|
|
||||||
BASE_URL: ${BASE_URL}
|
BASE_URL: ${BASE_URL}
|
||||||
NEXT_PUBLIC_BASE_URL: ${BASE_URL}
|
NEXT_PUBLIC_BASE_URL: ${BASE_URL}
|
||||||
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
|
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY}
|
||||||
|
|
@ -196,10 +151,6 @@ services:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
keydb:
|
|
||||||
condition: service_healthy
|
|
||||||
tiles:
|
|
||||||
condition: service_healthy
|
|
||||||
maintenance-page:
|
maintenance-page:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
restart: always
|
restart: always
|
||||||
|
|
@ -224,6 +175,4 @@ networks:
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
storage_data:
|
storage_data:
|
||||||
tiles-data:
|
|
||||||
keydb-data:
|
|
||||||
dbdata_v18:
|
dbdata_v18:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue