From 8657228e02380b43a17c6cb748c74a5841cada4b Mon Sep 17 00:00:00 2001 From: Lars Hampe Date: Tue, 5 Nov 2024 15:33:27 +0100 Subject: [PATCH] feat(api): add redis cache for public page --- apps/api/package.json | 1 + apps/api/src/changelog/update.ts | 4 ++ apps/api/src/changelog/version/create.ts | 5 ++ apps/api/src/changelog/version/createAuto.ts | 9 ++++ apps/api/src/changelog/version/delete.ts | 5 ++ apps/api/src/changelog/version/update.ts | 5 ++ apps/api/src/page/index.ts | 3 +- apps/api/src/page/public.ts | 15 ++++++ apps/api/src/page/update.ts | 3 ++ apps/api/src/utils/redis.ts | 9 ++++ apps/api/src/utils/startup.ts | 4 +- bun.lockb | Bin 546152 -> 561304 bytes docker-compose.yaml | 49 ++++++++++++++++++- 13 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/utils/redis.ts diff --git a/apps/api/package.json b/apps/api/package.json index 2c71e46..6925744 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -15,6 +15,7 @@ "@scalar/hono-api-reference": "^0.5.149", "convert-gitmoji": "^0.1.5", "hono": "^4.6.3", + "redis": "^4.7.0", "semver": "^7.6.3", "svix": "^1.36.0", "ts-pattern": "^5.5.0" diff --git a/apps/api/src/changelog/update.ts b/apps/api/src/changelog/update.ts index 74c7212..c757fee 100644 --- a/apps/api/src/changelog/update.ts +++ b/apps/api/src/changelog/update.ts @@ -6,6 +6,7 @@ import { import { createRoute, type z } from '@hono/zod-openapi' import { and, eq } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' +import { redis } from '../utils/redis' export const route = createRoute({ method: 'put', @@ -55,6 +56,9 @@ export const func = async ({ throw new HTTPException(404, { message: 'Not found' }) } + if (result.pageId) { + redis.del(result.pageId) + } return result } diff --git a/apps/api/src/changelog/version/create.ts b/apps/api/src/changelog/version/create.ts index ef96cde..36f15cd 100644 --- a/apps/api/src/changelog/version/create.ts +++ b/apps/api/src/changelog/version/create.ts @@ -9,6 +9,7 @@ import { createRoute, type z } from '@hono/zod-openapi' import { and, eq, inArray } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' import semver from 'semver' +import { redis } from '../../utils/redis' export const create = createRoute({ method: 'post', @@ -90,6 +91,10 @@ export const createFunc = async ({ }) .returning() + if (changelogResult.pageId) { + redis.del(changelogResult.pageId) + } + await db .update(changelog_commit) .set({ versionId: versionCreateResult.id }) diff --git a/apps/api/src/changelog/version/createAuto.ts b/apps/api/src/changelog/version/createAuto.ts index 4b3b0b1..592c74d 100644 --- a/apps/api/src/changelog/version/createAuto.ts +++ b/apps/api/src/changelog/version/createAuto.ts @@ -16,6 +16,7 @@ import semver from 'semver' import type { changelogVersionApi } from '.' import { verifyAuthentication } from '../../utils/authentication' import { commitsToMarkdown } from '../../utils/git/commitsToMarkdown' +import { redis } from '../../utils/redis' export const route = createRoute({ method: 'post', @@ -141,6 +142,10 @@ export const registerVersionCreateAuto = (api: typeof changelogVersionApi) => { .set({ versionId: versionCreateResult.id }) .where(isNull(changelog_commit.versionId)) + if (changelogResult.pageId) { + redis.del(changelogResult.pageId) + } + return c.json(versionCreateResult, 201) } @@ -164,6 +169,10 @@ export const registerVersionCreateAuto = (api: typeof changelogVersionApi) => { .set({ versionId: versionCreateResult.id }) .where(isNull(changelog_commit.versionId)) + if (changelogResult.pageId) { + redis.del(changelogResult.pageId) + } + return c.json(versionCreateResult, 201) }) } diff --git a/apps/api/src/changelog/version/delete.ts b/apps/api/src/changelog/version/delete.ts index 73d1850..a7cbad8 100644 --- a/apps/api/src/changelog/version/delete.ts +++ b/apps/api/src/changelog/version/delete.ts @@ -8,6 +8,7 @@ import { GeneralOutput } from '@boring.tools/schema' import { createRoute } from '@hono/zod-openapi' import { and, eq } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' +import { redis } from '../../utils/redis' export const remove = createRoute({ method: 'delete', @@ -67,6 +68,10 @@ export const removeFunc = async ({ .set({ versionId: null }) .where(eq(changelog_commit.versionId, id)) + if (findChangelog.pageId) { + redis.del(findChangelog.pageId) + } + return db .delete(changelog_version) .where(and(eq(changelog_version.id, id))) diff --git a/apps/api/src/changelog/version/update.ts b/apps/api/src/changelog/version/update.ts index e7f7d73..4d3501d 100644 --- a/apps/api/src/changelog/version/update.ts +++ b/apps/api/src/changelog/version/update.ts @@ -3,6 +3,7 @@ import { VersionUpdateInput, VersionUpdateOutput } from '@boring.tools/schema' import { createRoute, type z } from '@hono/zod-openapi' import { and, eq } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' +import { redis } from '../../utils/redis' export const update = createRoute({ method: 'put', @@ -70,5 +71,9 @@ export const updateFunc = async ({ .where(and(eq(changelog_version.id, id))) .returning() + if (findChangelog.pageId) { + redis.del(findChangelog.pageId) + } + return versionUpdateResult } diff --git a/apps/api/src/page/index.ts b/apps/api/src/page/index.ts index d22fda4..2b7c135 100644 --- a/apps/api/src/page/index.ts +++ b/apps/api/src/page/index.ts @@ -1,4 +1,5 @@ import { OpenAPIHono } from '@hono/zod-openapi' +import { timing } from 'hono/timing' import type { Variables } from '..' import type { ContextModule } from '../utils/sentry' import { registerPageById } from './byId' @@ -9,7 +10,7 @@ import { registerPagePublic } from './public' import { registerPageUpdate } from './update' export const pageApi = new OpenAPIHono<{ Variables: Variables }>() - +pageApi.use('*', timing()) const module: ContextModule = { name: 'page', } diff --git a/apps/api/src/page/public.ts b/apps/api/src/page/public.ts index acf7a2d..761abc1 100644 --- a/apps/api/src/page/public.ts +++ b/apps/api/src/page/public.ts @@ -1,9 +1,12 @@ import { changelog_version, db, page } from '@boring.tools/database' import { createRoute } from '@hono/zod-openapi' import { eq } from 'drizzle-orm' +import { endTime, setMetric, startTime } from 'hono/timing' import { PagePublicOutput, PagePublicParams } from '@boring.tools/schema' import { HTTPException } from 'hono/http-exception' + +import { redis } from '../utils/redis' import type { pageApi } from './index' const route = createRoute({ @@ -35,6 +38,15 @@ const route = createRoute({ export const registerPagePublic = (api: typeof pageApi) => { return api.openapi(route, async (c) => { const { id } = c.req.valid('param') + const cache = await redis.get(id) + + if (cache) { + c.header('Cache-Control', 'public, max-age=86400') + c.header('X-Cache', 'HIT') + return c.json(JSON.parse(cache), 200) + } + + startTime(c, 'database') const result = await db.query.page.findFirst({ where: eq(page.id, id), @@ -70,6 +82,8 @@ export const registerPagePublic = (api: typeof pageApi) => { }, }) + endTime(c, 'database') + if (!result) { throw new HTTPException(404, { message: 'Not Found' }) } @@ -81,6 +95,7 @@ export const registerPagePublic = (api: typeof pageApi) => { changelogs: changelogs.map((log) => log.changelog), } + redis.set(id, JSON.stringify(mappedResult), { EX: 60 }) return c.json(mappedResult, 200) }) } diff --git a/apps/api/src/page/update.ts b/apps/api/src/page/update.ts index 8f8920e..8a467b8 100644 --- a/apps/api/src/page/update.ts +++ b/apps/api/src/page/update.ts @@ -9,6 +9,7 @@ import { import { and, eq } from 'drizzle-orm' import { HTTPException } from 'hono/http-exception' import { verifyAuthentication } from '../utils/authentication' +import { redis } from '../utils/redis' import type { pageApi } from './index' const route = createRoute({ @@ -83,6 +84,8 @@ export const registerPageUpdate = (api: typeof pageApi) => { throw new HTTPException(404, { message: 'Not Found' }) } + redis.del(id) + return c.json(result, 200) }) } diff --git a/apps/api/src/utils/redis.ts b/apps/api/src/utils/redis.ts new file mode 100644 index 0000000..6bb2c2e --- /dev/null +++ b/apps/api/src/utils/redis.ts @@ -0,0 +1,9 @@ +import { createClient } from 'redis' + +export const redis = createClient({ + password: import.meta.env.REDIS_PASSWORD, + url: import.meta.env.REDIS_URL, +}) + +redis.on('error', (err) => console.log('Redis Client Error', err)) +await redis.connect() diff --git a/apps/api/src/utils/startup.ts b/apps/api/src/utils/startup.ts index 49f901e..84de84d 100644 --- a/apps/api/src/utils/startup.ts +++ b/apps/api/src/utils/startup.ts @@ -5,6 +5,8 @@ import { logger } from '@boring.tools/logger' declare module 'bun' { interface Env { POSTGRES_URL: string + REDIS_PASSWORD: string + REDIS_URL: string CLERK_WEBHOOK_SECRET: string CLERK_SECRET_KEY: string CLERK_PUBLISHABLE_KEY: string @@ -12,7 +14,7 @@ declare module 'bun' { } } -const TEST_VARIABLES = ['POSTGRES_URL'] +const TEST_VARIABLES = ['POSTGRES_URL', 'REDIS_URL', 'REDIS_PASSWORD'] const DEVELOPMENT_VARIABLES = [ ...TEST_VARIABLES, diff --git a/bun.lockb b/bun.lockb index 2e7f3dcbf3add344c544b0188467ae5f4e6363aa..3edda301404db1f2299dbff4de4e6d7794033368 100755 GIT binary patch delta 47091 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 KH!QdP_J061!1_-B diff --git a/docker-compose.yaml b/docker-compose.yaml index 78faec2..e763897 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -8,4 +8,51 @@ services: - POSTGRES_PASSWORD=postgres - POSTGRES_USER=postgres - POSTGRES_DB=postgres - \ No newline at end of file + + cache: + container_name: boring_redis + image: redis:7.4.1-alpine + restart: always + ports: + - '6379:6379' + command: redis-server --save 20 1 --loglevel warning --requirepass development + + loki: + image: grafana/loki:latest + ports: + - "9100:3100" + command: -config.file=/etc/loki/local-config.yaml + + promtail: + image: grafana/promtail:latest + volumes: + - /var/log:/var/log + command: -config.file=/etc/promtail/config.yml + + grafana: + environment: + - GF_PATHS_PROVISIONING=/etc/grafana/provisioning + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + entrypoint: + - sh + - -euc + - | + mkdir -p /etc/grafana/provisioning/datasources + cat < /etc/grafana/provisioning/datasources/ds.yaml + apiVersion: 1 + datasources: + - name: Loki + type: loki + access: proxy + orgId: 1 + url: http://loki:3100 + basicAuth: false + isDefault: true + version: 1 + editable: false + EOF + /run.sh + image: grafana/grafana:latest + ports: + - "9000:3000" \ No newline at end of file