feat(api): add redis cache for public page
This commit is contained in:
parent
52829bd127
commit
8657228e02
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 })
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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',
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
9
apps/api/src/utils/redis.ts
Normal file
9
apps/api/src/utils/redis.ts
Normal file
@ -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()
|
@ -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,
|
||||
|
@ -8,4 +8,51 @@ services:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
|
||||
|
||||
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 <<EOF > /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"
|
Loading…
Reference in New Issue
Block a user