From 2c106613e62e8f06454c6306731d1f5fca30420c Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Tue, 26 May 2026 16:05:31 +0800 Subject: [PATCH] feat: refactor authentication and caching logic with new context utilities --- app/composables/useAuthSession.ts | 21 ++----- app/plugins/auth-session.server.ts | 8 ++- packages/cache/readme.md | 36 ----------- packages/drizzle-pkg/db.sqlite | Bin 282624 -> 282624 bytes server/api/auth/me.get.ts | 7 ++- server/api/auth/profile.put.ts | 5 +- server/api/auth/register.post.ts | 3 +- server/api/auth/session.get.ts | 3 +- server/api/cards.get.ts | 6 +- server/api/config/global.get.ts | 11 ++-- server/api/config/global.put.ts | 7 ++- server/api/config/me.get.ts | 9 +-- server/api/config/me.put.ts | 7 ++- server/api/scheduler/executions.get.ts | 5 +- server/api/scheduler/executions/[id].delete.ts | 5 +- server/api/scheduler/executions/delete-all.post.ts | 5 +- server/api/scheduler/stats.get.ts | 5 +- server/api/scheduler/tasks/[id].delete.ts | 5 +- server/api/scheduler/tasks/[id].get.ts | 5 +- server/api/scheduler/tasks/[id].put.ts | 7 ++- server/api/scheduler/tasks/[id]/toggle.post.ts | 7 ++- server/api/scheduler/tasks/index.get.ts | 5 +- server/api/scheduler/tasks/index.post.ts | 5 +- server/middleware/02.auth-guard.ts | 3 +- server/plugins/00.cache.ts | 22 ------- server/plugins/01.context.ts | 68 --------------------- server/service/auth/context.ts | 3 +- server/service/auth/cookie.ts | 1 + server/service/config/me/[key].delete.ts | 5 +- server/utils/admin-guard.ts | 3 +- server/utils/context.ts | 56 +++++++++++++++++ 31 files changed, 143 insertions(+), 195 deletions(-) delete mode 100644 server/plugins/00.cache.ts delete mode 100644 server/plugins/01.context.ts create mode 100644 server/utils/context.ts diff --git a/app/composables/useAuthSession.ts b/app/composables/useAuthSession.ts index 0777f49..5ca53b3 100644 --- a/app/composables/useAuthSession.ts +++ b/app/composables/useAuthSession.ts @@ -1,28 +1,19 @@ import { request, unwrapApiBody, type ApiResponse } from "../utils/http/factory"; - -export type AuthUser = { - id: number; - username: string; - email: string | null; - role: string; - publicSlug: string | null; - nickname: string | null; - avatar: string | null; -}; +import type { MinimalUser } from "~~/server/service/auth"; type MeResult = { - user: AuthUser; + user: MinimalUser; }; type SessionResult = { - user: AuthUser | null; + user: MinimalUser | null; }; export type AuthSessionState = { initialized: boolean; pending: boolean; loggedIn: boolean; - user: AuthUser | null; + user: MinimalUser | null; }; export const AUTH_SESSION_STATE_KEY = "auth:session"; @@ -49,7 +40,7 @@ export function useAuthSession() { })); const clientMeSynced = useState(AUTH_CLIENT_ME_SYNCED_KEY, () => false); - const applyUser = (user: AuthUser | null) => { + const applyUser = (user: MinimalUser | null) => { state.value.user = user; state.value.loggedIn = Boolean(user); state.value.initialized = true; @@ -88,7 +79,7 @@ export function useAuthSession() { }; const updateProfile = async (data: { username?: string; email?: string; nickname?: string }) => { - const payload = await request>("/api/auth/profile", { + const payload = await request>("/api/auth/profile", { method: "put", body: data, }); diff --git a/app/plugins/auth-session.server.ts b/app/plugins/auth-session.server.ts index 0f3a2c3..f8c7504 100644 --- a/app/plugins/auth-session.server.ts +++ b/app/plugins/auth-session.server.ts @@ -3,6 +3,7 @@ import { DEFAULT_AUTH_SESSION_STATE, type AuthSessionState, } from "../composables/useAuthSession"; +import { getCurrentUser } from "#server/utils/context"; export default defineNuxtPlugin(async () => { const event = useRequestEvent(); @@ -17,12 +18,13 @@ export default defineNuxtPlugin(async () => { if (state.value.initialized) { return; } - - const user = await event.context.auth.getCurrent(); + console.log(useCookie); + + const user = await getCurrentUser(event); state.value = { initialized: true, pending: false, loggedIn: Boolean(user), - user: user ?? null, + user: user, }; }); diff --git a/packages/cache/readme.md b/packages/cache/readme.md index 410edc8..a38ea7d 100644 --- a/packages/cache/readme.md +++ b/packages/cache/readme.md @@ -135,42 +135,6 @@ const cache = createCache({ }) ``` -### 场景四:Nitro Plugin 全局注入 - -`server/plugins/cache.ts`: - -```typescript -import { createCache } from 'cache' - -export default defineNitroPlugin(() => { - const cache = createCache({ - redis: { - host: '127.0.0.1', - port: 6379, - }, - defaultTtl: 300, - }) - - // 注入到 H3 event context - event.context.cache = cache -}) -``` - -`server/api/users.get.ts`: - -```typescript -export default defineEventHandler(async (event) => { - const cache = event.context.cache - - const cached = await cache.get('users:list') - if (cached) return cached - - const data = await fetchUsers() - await cache.set('users:list', data) - return data -}) -``` - ## 数据结构 所有值以 JSON 序列化存储到 Redis: diff --git a/packages/drizzle-pkg/db.sqlite b/packages/drizzle-pkg/db.sqlite index 7a6d687f3327bd7f0775935f618ef88fb0c1f782..90aa2eadfe3d09b399d5f77e43332cbe7608b8a3 100644 GIT binary patch delta 152 zcmZozAlR@#aDp_W@Q_YM`&A1uowa1D^GR{j5m>9Wz!#So1Mn3*nes2Et{3iT2_#W}MZ59+b Y!>6Ia9Lh+HdF@>HnYMG?XZ}_I0Q2=LcK`qY delta 152 zcmZozAlR@#aDp_W`9v9KM)Ssm)&$0_2~6_m3=NW!Ese~Ql5`U-Qj>H|%#%`eEfdqs zbS(^%OcIUK5={-vjkp=-wf#D`o^f8%JFoTIH=JXNVC3U_#2?Gg&A*=Cg#QMA+h#$5 YGkh8v%%P0LnAgsApJ_YSedcck0B(seRsaA1 diff --git a/server/api/auth/me.get.ts b/server/api/auth/me.get.ts index 7aa13cf..49f0d32 100644 --- a/server/api/auth/me.get.ts +++ b/server/api/auth/me.get.ts @@ -4,10 +4,11 @@ import { eq } from "drizzle-orm"; import { UNAUTHORIZED_MESSAGE } from "#server/constants/auth"; import { clearSessionCookie } from "#server/service/auth/cookie"; import { toPublicAuthError } from "#server/service/auth/errors"; +import { getCache, setCache, delCache, requireUser } from "#server/utils/context"; export default defineWrappedResponseHandler(async (event) => { try { - const user = await event.context.auth.requireUser(); + const user = await requireUser(event); if (!user) { clearSessionCookie(event); throw createError({ @@ -17,7 +18,7 @@ export default defineWrappedResponseHandler(async (event) => { } const cacheKey = `auth:me:${user.id}`; - const cached = await event.context.cache.get<{ user: { id: number, username: string, role: string, nickname: string | null, avatar: string | null } }>(cacheKey); + const cached = await getCache<{ user: { id: number, username: string, role: string, nickname: string | null, avatar: string | null } }>(cacheKey); if (cached) return R.success(cached); const [row] = await dbGlobal @@ -36,7 +37,7 @@ export default defineWrappedResponseHandler(async (event) => { avatar: row?.avatar ?? null, }, }; - await event.context.cache.set(cacheKey, result, 60); + await setCache(cacheKey, result, 60); return R.success(result); } catch (err) { throw toPublicAuthError(err); diff --git a/server/api/auth/profile.put.ts b/server/api/auth/profile.put.ts index f00464b..f017615 100644 --- a/server/api/auth/profile.put.ts +++ b/server/api/auth/profile.put.ts @@ -3,10 +3,11 @@ import { users } from "drizzle-pkg/lib/schema/auth"; import { eq } from "drizzle-orm"; import { UNAUTHORIZED_MESSAGE } from "#server/constants/auth"; import { toPublicAuthError } from "#server/service/auth/errors"; +import { delCache, requireUser } from "#server/utils/context"; export default defineWrappedResponseHandler(async (event) => { try { - const user = await event.context.auth.requireUser(); + const user = await requireUser(event); if (!user) { throw createError({ statusCode: 401, @@ -61,7 +62,7 @@ export default defineWrappedResponseHandler(async (event) => { .where(eq(users.id, user.id)); // Invalidate me cache - await event.context.cache.del(`auth:me:${user.id}`); + await delCache(`auth:me:${user.id}`); } // Fetch updated user data diff --git a/server/api/auth/register.post.ts b/server/api/auth/register.post.ts index 9af48b8..daadf85 100644 --- a/server/api/auth/register.post.ts +++ b/server/api/auth/register.post.ts @@ -4,6 +4,7 @@ import { toPublicAuthError } from "#server/service/auth/errors"; import { captchaConsume } from "#server/service/captcha/store"; import { assertLoginRegisterCaptchaFieldsPresent } from "#server/service/captcha/validate-body"; import { assertUnderRateLimit } from "#server/utils/simple-rate-limit"; +import { getConfigGlobal } from "#server/utils/context"; export default defineWrappedResponseHandler(async (event) => { const ip = getRequestIP(event, { xForwardedFor: true }) ?? "unknown"; @@ -18,7 +19,7 @@ export default defineWrappedResponseHandler(async (event) => { }); } - const allowRegister = await event.context.config.getGlobal("allowRegister"); + const allowRegister = await getConfigGlobal("allowRegister"); if (!allowRegister) { throw createError({ statusCode: 403, diff --git a/server/api/auth/session.get.ts b/server/api/auth/session.get.ts index cbd3d74..452ff91 100644 --- a/server/api/auth/session.get.ts +++ b/server/api/auth/session.get.ts @@ -1,4 +1,5 @@ import { toPublicAuthError } from "#server/service/auth/errors"; +import { getCurrentUser } from "#server/utils/context"; /** * 客户端对齐用:无会话 Cookie 时不访问数据库;有 Cookie 时与 SSR `auth.getCurrent()` 同路径。 @@ -6,7 +7,7 @@ import { toPublicAuthError } from "#server/service/auth/errors"; */ export default defineWrappedResponseHandler(async (event) => { try { - const user = await event.context.auth.getCurrent(); + const user = await getCurrentUser(event); return R.success({ user: user ?? null }); } catch (err) { throw toPublicAuthError(err); diff --git a/server/api/cards.get.ts b/server/api/cards.get.ts index 5f1242f..ad4851e 100644 --- a/server/api/cards.get.ts +++ b/server/api/cards.get.ts @@ -1,3 +1,5 @@ +import { getCache, setCache } from "#server/utils/context"; + type CardType = 'text' | 'image' | 'image-text' | 'portfolio' | 'project' interface CardData { @@ -131,7 +133,7 @@ export default defineWrappedResponseHandler(async (event) => { const pageSize = Math.min(30, Math.max(1, parseInt(String(query.pageSize || '12')))) const cacheKey = `cards:list:${page}:${pageSize}` - const cached = await event.context.cache.get<{ items: CardData[], hasMore: boolean, page: number }>(cacheKey) + const cached = await getCache<{ items: CardData[], hasMore: boolean, page: number }>(cacheKey) if (cached) return cached const start = (page - 1) * pageSize @@ -141,6 +143,6 @@ export default defineWrappedResponseHandler(async (event) => { const hasMore = start + pageSize < total const result = { items, hasMore, page } - await event.context.cache.set(cacheKey, result, 300) + await setCache(cacheKey, result, 300) return result }) diff --git a/server/api/config/global.get.ts b/server/api/config/global.get.ts index e722067..e8ab28c 100644 --- a/server/api/config/global.get.ts +++ b/server/api/config/global.get.ts @@ -1,3 +1,4 @@ +import { getCache, setCache, getCurrentUser, getConfigGlobal } from "#server/utils/context"; import { KNOWN_CONFIG_KEYS } from "#server/service/config/registry"; import type { KnownConfigKey, KnownConfigValue } from "#server/service/config/registry"; @@ -8,18 +9,18 @@ const PUBLIC_GLOBAL_CONFIG_KEYS = [ const SECRET_MASKED_GLOBAL_CONFIG_KEYS = new Set([]); export default defineWrappedResponseHandler(async (event) => { - const user = await event.context.auth.getCurrent(); + const user = await getCurrentUser(event); const isAdmin = user?.role === "admin"; const cacheKey = isAdmin ? "config:global:admin" : "config:global:public"; - const cached = await event.context.cache.get<{ config: Record }>(cacheKey); + const cached = await getCache<{ config: Record }>(cacheKey); if (cached) return R.success(cached); const keys: readonly KnownConfigKey[] = isAdmin ? KNOWN_CONFIG_KEYS : PUBLIC_GLOBAL_CONFIG_KEYS; const entries = await Promise.all( keys.map(async (key) => { - const value = await event.context.config.getGlobal(key); + const value = await getConfigGlobal(event, key); const safeValue = SECRET_MASKED_GLOBAL_CONFIG_KEYS.has(key) ? "" @@ -30,11 +31,11 @@ export default defineWrappedResponseHandler(async (event) => { const config = Object.fromEntries(entries); if (!isAdmin) { - await event.context.cache.set(cacheKey, { config }, 300); + await setCache(cacheKey, { config }, 300); return R.success({ config }); } const result = { config: { ...config } }; - await event.context.cache.set(cacheKey, result, 300); + await setCache(cacheKey, result, 300); return R.success(result); }); diff --git a/server/api/config/global.put.ts b/server/api/config/global.put.ts index 10309a7..f5f05d0 100644 --- a/server/api/config/global.put.ts +++ b/server/api/config/global.put.ts @@ -6,6 +6,7 @@ import { toPublicConfigError } from "#server/service/config/errors"; import { requireAdmin } from "#server/utils/admin-guard"; import { assertUnderRateLimit } from "#server/utils/simple-rate-limit"; import { getRequestIP } from "h3"; +import { delCache, getConfigGlobal } from "#server/utils/context"; type UpdateGlobalConfigBody = { key: string; @@ -28,10 +29,10 @@ export default defineWrappedResponseHandler(async (event) => { const key = ensureKnownConfigKey(body.key); await setGlobalConfigValue(key, body.value); - await event.context.cache.del("config:global:public"); - await event.context.cache.del("config:global:admin"); + await delCache("config:global:public"); + await delCache("config:global:admin"); - const value = await event.context.config.getGlobal(key); + const value = await getConfigGlobal(event, key); return R.success({ key, value: toSafeResponseValue(key, value), diff --git a/server/api/config/me.get.ts b/server/api/config/me.get.ts index 8e3c4a8..21cbfd4 100644 --- a/server/api/config/me.get.ts +++ b/server/api/config/me.get.ts @@ -1,18 +1,19 @@ +import { getCache, setCache, requireUser, getConfig } from "#server/utils/context"; import { dbGlobal } from "drizzle-pkg/lib/db"; import { users } from "drizzle-pkg/lib/schema/auth"; import { eq } from "drizzle-orm"; import { KNOWN_CONFIG_KEYS } from "#server/service/config/registry"; export default defineWrappedResponseHandler(async (event) => { - const user = await event.context.auth.requireUser(); + const user = await requireUser(event); const cacheKey = `config:me:${user.id}`; - const cached = await event.context.cache.get<{ user: unknown, config: unknown }>(cacheKey); + const cached = await getCache<{ user: unknown, config: unknown }>(cacheKey); if (cached) return R.success(cached); const entries = await Promise.all( KNOWN_CONFIG_KEYS.map(async (key) => { - const value = await event.context.config.get(key); + const value = await getConfig(event, key); return [key, value] as const; }), ); @@ -36,6 +37,6 @@ export default defineWrappedResponseHandler(async (event) => { }, config: Object.fromEntries(entries), }; - await event.context.cache.set(cacheKey, result, 60); + await setCache(cacheKey, result, 60); return R.success(result); }); diff --git a/server/api/config/me.put.ts b/server/api/config/me.put.ts index 0219365..55e58d6 100644 --- a/server/api/config/me.put.ts +++ b/server/api/config/me.put.ts @@ -3,6 +3,7 @@ import { setCurrentUserConfigValue, } from "#server/service/config"; import { toPublicConfigError } from "#server/service/config/errors"; +import { delCache, requireUser, getConfig } from "#server/utils/context"; type UpdateMyConfigBody = { key: string; @@ -11,15 +12,15 @@ type UpdateMyConfigBody = { export default defineWrappedResponseHandler(async (event) => { try { - const user = await event.context.auth.requireUser(); + const user = await requireUser(event); const body = await readBody(event); const key = ensureKnownConfigKey(body.key); await setCurrentUserConfigValue(user.id, key, body.value); - await event.context.cache.del(`config:me:${user.id}`); + await delCache(`config:me:${user.id}`); - const value = await event.context.config.get(key); + const value = await getConfig(event, key); return R.success({ key, value, diff --git a/server/api/scheduler/executions.get.ts b/server/api/scheduler/executions.get.ts index a5a98ab..3e54d5a 100644 --- a/server/api/scheduler/executions.get.ts +++ b/server/api/scheduler/executions.get.ts @@ -1,3 +1,4 @@ +import { getCache, setCache } from "#server/utils/context"; import { listExecutions } from "../../service/scheduler"; export default defineWrappedResponseHandler(async (event) => { @@ -6,7 +7,7 @@ export default defineWrappedResponseHandler(async (event) => { const pageSize = query.pageSize ? Number(query.pageSize) : 20; const cacheKey = `scheduler:executions:${page}:${pageSize}:${query.taskId ?? 'all'}:${query.status ?? 'all'}` - const cached = await event.context.cache.get<{ list: unknown[], total: number, page: number, pageSize: number }>(cacheKey) + const cached = await getCache<{ list: unknown[], total: number, page: number, pageSize: number }>(cacheKey) if (cached) return R.success(cached) const result = await listExecutions({ @@ -16,6 +17,6 @@ export default defineWrappedResponseHandler(async (event) => { status: query.status as string | undefined, }); - await event.context.cache.set(cacheKey, result, 60) + await setCache(cacheKey, result, 60) return R.success(result); }); \ No newline at end of file diff --git a/server/api/scheduler/executions/[id].delete.ts b/server/api/scheduler/executions/[id].delete.ts index 0e5e108..965d50b 100644 --- a/server/api/scheduler/executions/[id].delete.ts +++ b/server/api/scheduler/executions/[id].delete.ts @@ -1,3 +1,4 @@ +import { delCache } from "#server/utils/context"; import { deleteExecution } from "#server/service/scheduler"; export default defineWrappedResponseHandler(async (event) => { @@ -6,8 +7,8 @@ export default defineWrappedResponseHandler(async (event) => { const deleted = await deleteExecution(id); - await event.context.cache.del('scheduler:executions:1:20:all:all') - await event.context.cache.del('scheduler:stats') + await delCache('scheduler:executions:1:20:all:all') + await delCache('scheduler:stats') return R.success({ deleted }); }); \ No newline at end of file diff --git a/server/api/scheduler/executions/delete-all.post.ts b/server/api/scheduler/executions/delete-all.post.ts index c9d3c65..812b8f3 100644 --- a/server/api/scheduler/executions/delete-all.post.ts +++ b/server/api/scheduler/executions/delete-all.post.ts @@ -1,3 +1,4 @@ +import { delCache } from "#server/utils/context"; import { deleteAllExecutions } from "#server/service/scheduler"; export default defineWrappedResponseHandler(async (event) => { @@ -6,8 +7,8 @@ export default defineWrappedResponseHandler(async (event) => { const deleted = await deleteAllExecutions(taskId); - await event.context.cache.del('scheduler:executions:1:20:all:all') - await event.context.cache.del('scheduler:stats') + await delCache('scheduler:executions:1:20:all:all') + await delCache('scheduler:stats') return R.success({ deleted }); }); \ No newline at end of file diff --git a/server/api/scheduler/stats.get.ts b/server/api/scheduler/stats.get.ts index 5e56234..3ebe720 100644 --- a/server/api/scheduler/stats.get.ts +++ b/server/api/scheduler/stats.get.ts @@ -1,13 +1,14 @@ +import { getCache, setCache } from "#server/utils/context"; import { getStats } from "../../service/scheduler"; import { getJobCount } from "../../scheduler/engine"; export default defineWrappedResponseHandler(async (event) => { const cacheKey = 'scheduler:stats' - const cached = await event.context.cache.get<{ totalTasks: number, enabledTasks: number, last24hExecutions: number, activeJobs: number }>(cacheKey) + const cached = await getCache<{ totalTasks: number, enabledTasks: number, last24hExecutions: number, activeJobs: number }>(cacheKey) if (cached) return R.success({ ...cached, activeJobs: getJobCount() }) const stats = await getStats(); const result = { ...stats, activeJobs: getJobCount() } - await event.context.cache.set(cacheKey, result, 60) + await setCache(cacheKey, result, 60) return R.success(result); }); \ No newline at end of file diff --git a/server/api/scheduler/tasks/[id].delete.ts b/server/api/scheduler/tasks/[id].delete.ts index 7e3cd7c..d81e044 100644 --- a/server/api/scheduler/tasks/[id].delete.ts +++ b/server/api/scheduler/tasks/[id].delete.ts @@ -1,3 +1,4 @@ +import { delCache } from "#server/utils/context"; import { deleteTask } from "../../../service/scheduler"; import { removeTask } from "../../../scheduler/engine"; @@ -8,8 +9,8 @@ export default defineWrappedResponseHandler(async (event) => { removeTask(id); await deleteTask(id); - await event.context.cache.del(`scheduler:task:${id}`) - await event.context.cache.del('scheduler:tasks:1:20:all:all') + await delCache(`scheduler:task:${id}`) + await delCache('scheduler:tasks:1:20:all:all') return R.success(null); }); \ No newline at end of file diff --git a/server/api/scheduler/tasks/[id].get.ts b/server/api/scheduler/tasks/[id].get.ts index 99a5661..21fc519 100644 --- a/server/api/scheduler/tasks/[id].get.ts +++ b/server/api/scheduler/tasks/[id].get.ts @@ -1,3 +1,4 @@ +import { getCache, setCache } from "#server/utils/context"; import { getTaskById, getRecentExecutions } from "../../../service/scheduler"; export default defineWrappedResponseHandler(async (event) => { @@ -5,7 +6,7 @@ export default defineWrappedResponseHandler(async (event) => { if (!id) return R.throwError(400, "Missing id", null); const cacheKey = `scheduler:task:${id}` - const cached = await event.context.cache.get<{ task: unknown, recentExecutions: unknown[] }>(cacheKey) + const cached = await getCache<{ task: unknown, recentExecutions: unknown[] }>(cacheKey) if (cached) return R.success(cached) const task = await getTaskById(id); @@ -14,6 +15,6 @@ export default defineWrappedResponseHandler(async (event) => { const recentExecutions = await getRecentExecutions(id, 20); const result = { task, recentExecutions } - await event.context.cache.set(cacheKey, result, 60) + await setCache(cacheKey, result, 60) return R.success(result); }); \ No newline at end of file diff --git a/server/api/scheduler/tasks/[id].put.ts b/server/api/scheduler/tasks/[id].put.ts index 5041689..537c949 100644 --- a/server/api/scheduler/tasks/[id].put.ts +++ b/server/api/scheduler/tasks/[id].put.ts @@ -1,3 +1,4 @@ +import { delCache } from "#server/utils/context"; import { z } from "zod"; import { updateTask, getTaskById } from "../../../service/scheduler"; import { reloadTask } from "../../../scheduler/engine"; @@ -54,9 +55,9 @@ export default defineWrappedResponseHandler(async (event) => { reloadTask(task.id); } - await event.context.cache.del(`scheduler:task:${id}`) - await event.context.cache.del('scheduler:tasks:1:20:all:all') - await event.context.cache.del('scheduler:stats') + await delCache(`scheduler:task:${id}`) + await delCache('scheduler:tasks:1:20:all:all') + await delCache('scheduler:stats') return R.success(task); }); \ No newline at end of file diff --git a/server/api/scheduler/tasks/[id]/toggle.post.ts b/server/api/scheduler/tasks/[id]/toggle.post.ts index fad1318..c3f3990 100644 --- a/server/api/scheduler/tasks/[id]/toggle.post.ts +++ b/server/api/scheduler/tasks/[id]/toggle.post.ts @@ -1,3 +1,4 @@ +import { delCache } from "#server/utils/context"; import { toggleTask } from "../../../../service/scheduler"; import { reloadTask, removeTask } from "../../../../scheduler/engine"; @@ -16,9 +17,9 @@ export default defineWrappedResponseHandler(async (event) => { removeTask(id); } - await event.context.cache.del(`scheduler:task:${id}`) - await event.context.cache.del('scheduler:tasks:1:20:all:all') - await event.context.cache.del('scheduler:stats') + await delCache(`scheduler:task:${id}`) + await delCache('scheduler:tasks:1:20:all:all') + await delCache('scheduler:stats') return R.success(task); }); \ No newline at end of file diff --git a/server/api/scheduler/tasks/index.get.ts b/server/api/scheduler/tasks/index.get.ts index 849d210..1616b3d 100644 --- a/server/api/scheduler/tasks/index.get.ts +++ b/server/api/scheduler/tasks/index.get.ts @@ -1,3 +1,4 @@ +import { getCache, setCache } from "#server/utils/context"; import { listTasks } from "../../../service/scheduler"; import { listRegisteredTasks } from "../../../scheduler/registry"; @@ -7,7 +8,7 @@ export default defineWrappedResponseHandler(async (event) => { const pageSize = query.pageSize ? Number(query.pageSize) : 20; const cacheKey = `scheduler:tasks:${page}:${pageSize}:${query.type ?? 'all'}:${query.enabled ?? 'all'}` - const cached = await event.context.cache.get<{ list: unknown[], total: number, page: number, pageSize: number, registeredFunctions: unknown[] }>(cacheKey) + const cached = await getCache<{ list: unknown[], total: number, page: number, pageSize: number, registeredFunctions: unknown[] }>(cacheKey) if (cached) return R.success(cached) const result = await listTasks({ @@ -21,6 +22,6 @@ export default defineWrappedResponseHandler(async (event) => { ...result, registeredFunctions: listRegisteredTasks(), } - await event.context.cache.set(cacheKey, fullResult, 120) + await setCache(cacheKey, fullResult, 120) return R.success(fullResult); }); \ No newline at end of file diff --git a/server/api/scheduler/tasks/index.post.ts b/server/api/scheduler/tasks/index.post.ts index 754e3fc..76c5d03 100644 --- a/server/api/scheduler/tasks/index.post.ts +++ b/server/api/scheduler/tasks/index.post.ts @@ -1,3 +1,4 @@ +import { delCache } from "#server/utils/context"; import { z } from "zod"; import { createTask } from "../../../service/scheduler"; import { addTask } from "../../../scheduler/engine"; @@ -55,8 +56,8 @@ export default defineWrappedResponseHandler(async (event) => { addTask(task.id); } - await event.context.cache.del('scheduler:tasks:1:20:all:all') - await event.context.cache.del('scheduler:stats') + await delCache('scheduler:tasks:1:20:all:all') + await delCache('scheduler:stats') return R.success(task); }); \ No newline at end of file diff --git a/server/middleware/02.auth-guard.ts b/server/middleware/02.auth-guard.ts index 680eebb..7eca276 100644 --- a/server/middleware/02.auth-guard.ts +++ b/server/middleware/02.auth-guard.ts @@ -1,5 +1,6 @@ import { UNAUTHORIZED_MESSAGE } from "#server/constants/auth"; import { isAllowlistedApiPath } from "#server/utils/auth-api-routes"; +import { getCurrentUser } from "#server/utils/context"; export default eventHandler(async (event) => { const path = event.path; @@ -14,7 +15,7 @@ export default eventHandler(async (event) => { return; } - const user = await event.context.auth.getCurrent(); + const user = await getCurrentUser(event); if (user) { return; } diff --git a/server/plugins/00.cache.ts b/server/plugins/00.cache.ts deleted file mode 100644 index d98d60d..0000000 --- a/server/plugins/00.cache.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createCache } from "cache"; - -const cache = createCache({ - // redis: { - // host: process.env.REDIS_HOST ?? '127.0.0.1', - // port: Number(process.env.REDIS_PORT ?? 6379), - // password: process.env.REDIS_PASSWORD, - // db: Number(process.env.REDIS_DB ?? 0), - // }, - // defaultTtl: 300, - memory: true, -}); - -if (import.meta.dev) { - console.log("plugin: 00.cache"); -} - -export default defineNitroPlugin((nitroApp) => { - nitroApp.hooks.hook("request", (event) => { - event.context.cache = cache; - }); -}); \ No newline at end of file diff --git a/server/plugins/01.context.ts b/server/plugins/01.context.ts deleted file mode 100644 index 76fc081..0000000 --- a/server/plugins/01.context.ts +++ /dev/null @@ -1,68 +0,0 @@ - -import { createCache } from "cache"; -import { createAuthContext } from "../service/auth/context"; -import { getGlobalConfigValue, getMergedConfigValue, getUserConfigValue } from "../service/config"; -import type { KnownConfigKey, KnownConfigValue } from "../service/config/registry"; - -if (import.meta.dev) { - console.log("plugin: 01.context"); -} - -type ConfigContext = { - getGlobal: (key: K) => Promise>; - getUser: (key: K) => Promise | undefined>; - get: (key: K) => Promise>; -}; - -interface CachedConfigCache { - get(key: string): Promise; - set(key: string, value: T, ttl?: number): Promise; - del(key: string): Promise; -} - -declare module "h3" { - interface H3EventContext { - auth: ReturnType; - config: ConfigContext; - cache: ReturnType; - } -} - -export default defineNitroPlugin((nitroApp) => { - nitroApp.hooks.hook("request", async (event) => { - event.context.auth = createAuthContext(event); - - const cache = event.context.cache; - - event.context.config = { - getGlobal: async (key: K) => { - const cacheKey = `config:global:${key}`; - const cached = await cache.get>(cacheKey); - if (cached !== null) return cached; - const value = await getGlobalConfigValue(key); - await cache.set(cacheKey, value); - return value; - }, - getUser: async (key: K) => { - const user = await event.context.auth.getCurrent(); - const cacheKey = `config:user:${user?.id ?? "anonymous"}:${key}`; - const cached = await cache.get>(cacheKey); - if (cached !== null) return cached; - const value = await getUserConfigValue(user?.id, key); - if (value !== undefined) { - await cache.set(cacheKey, value); - } - return value; - }, - get: async (key: K) => { - const user = await event.context.auth.getCurrent(); - const cacheKey = `config:merged:${user?.id ?? "anonymous"}:${key}`; - const cached = await cache.get>(cacheKey); - if (cached !== null) return cached; - const value = await getMergedConfigValue(user?.id, key); - await cache.set(cacheKey, value); - return value; - }, - }; - }); -}) diff --git a/server/service/auth/context.ts b/server/service/auth/context.ts index 2587791..3fd9aa7 100644 --- a/server/service/auth/context.ts +++ b/server/service/auth/context.ts @@ -1,6 +1,7 @@ import type { H3Event } from "h3"; import { SESSION_COOKIE_NAME, UNAUTHORIZED_MESSAGE } from "#server/constants/auth"; import { getCurrentUser, type MinimalUser } from "."; +import { getSessionId } from "./cookie"; export function createAuthContext(event: H3Event) { let currentUserPromise: Promise | undefined; @@ -8,7 +9,7 @@ export function createAuthContext(event: H3Event) { const getCurrent = async () => { if (!currentUserPromise) { currentUserPromise = (async () => { - const sessionId = getCookie(event, SESSION_COOKIE_NAME); + const sessionId = getSessionId(event); if (!sessionId) { return null; } diff --git a/server/service/auth/cookie.ts b/server/service/auth/cookie.ts index b787b41..56d63fa 100644 --- a/server/service/auth/cookie.ts +++ b/server/service/auth/cookie.ts @@ -1,4 +1,5 @@ import type { H3Event } from "h3"; +import { getCookie } from "h3"; import { SESSION_COOKIE_NAME, SESSION_COOKIE_PATH, diff --git a/server/service/config/me/[key].delete.ts b/server/service/config/me/[key].delete.ts index 5cc68ed..1f6caed 100644 --- a/server/service/config/me/[key].delete.ts +++ b/server/service/config/me/[key].delete.ts @@ -3,13 +3,14 @@ import { resetCurrentUserConfigValue, } from "#server/service/config"; import { toPublicConfigError } from "#server/service/config/errors"; +import { delCache, requireUser, getConfig } from "#server/utils/context"; export default defineWrappedResponseHandler(async (event) => { try { - const user = await event.context.auth.requireUser(); + const user = await requireUser(event); const key = ensureKnownConfigKey(getRouterParam(event, "key") ?? ""); await resetCurrentUserConfigValue(user.id, key); - const value = await event.context.config.get(key); + const value = await getConfig(event, key); return R.success({ key, value, diff --git a/server/utils/admin-guard.ts b/server/utils/admin-guard.ts index 4e876aa..cddbce0 100644 --- a/server/utils/admin-guard.ts +++ b/server/utils/admin-guard.ts @@ -1,8 +1,9 @@ import type { H3Event } from "h3"; import type { MinimalUser } from "#server/service/auth"; +import { requireUser } from "#server/utils/context"; export async function requireAdmin(event: H3Event): Promise { - const user = await event.context.auth.requireUser(); + const user = await requireUser(event); if (user.role !== "admin") { throw createError({ statusCode: 403, diff --git a/server/utils/context.ts b/server/utils/context.ts new file mode 100644 index 0000000..2ec0d3d --- /dev/null +++ b/server/utils/context.ts @@ -0,0 +1,56 @@ +import type { H3Event } from "h3"; +import { createCache } from "cache"; +import { createAuthContext } from "../service/auth/context"; +import { getGlobalConfigValue, getMergedConfigValue, getUserConfigValue } from "../service/config"; +import type { KnownConfigKey, KnownConfigValue } from "../service/config/registry"; + +// ============= Cache ============= +const _cache = createCache({ + memory: true, +}); + +export const cache = _cache; + +export async function getCache(key: string): Promise { + return _cache.get(key); +} + +export async function setCache(key: string, value: T, ttl?: number): Promise { + return _cache.set(key, value, ttl); +} + +export async function delCache(key: string): Promise { + return _cache.del(key); +} + +// ============= Auth ============= +export function getAuth(event: H3Event) { + return createAuthContext(event); +} + +export async function getCurrentUser(event: H3Event) { + const auth = getAuth(event); + return auth.getCurrent(); +} + +export async function requireUser(event: H3Event) { + const auth = getAuth(event); + return auth.requireUser(); +} + +// ============= Config ============= +export async function getConfigGlobal(key: K): Promise> { + return getGlobalConfigValue(key); +} + +export async function getConfigUser(event: H3Event, key: K): Promise | undefined> { + const auth = getAuth(event); + const user = await auth.getCurrent(); + return getUserConfigValue(user?.id ?? undefined, key); +} + +export async function getConfig(event: H3Event, key: K): Promise> { + const auth = getAuth(event); + const user = await auth.getCurrent(); + return getMergedConfigValue(user?.id ?? undefined, key); +} \ No newline at end of file