diff --git a/app/app.vue b/app/app.vue index 28d8f53..3214b6f 100644 --- a/app/app.vue +++ b/app/app.vue @@ -1,5 +1,5 @@ diff --git a/app/utils/http.ts b/app/utils/http.ts new file mode 100644 index 0000000..2f0e6fb --- /dev/null +++ b/app/utils/http.ts @@ -0,0 +1,95 @@ +import { createUseFetch } from '#imports' +import type { AsyncData, UseFetchOptions } from '#app' +import type { KeysOf, PickFrom } from '#app/composables/asyncData' +import type { NitroFetchRequest, TypedInternalResponse, AvailableRouterMethod } from 'nitropack/types' +import type { FetchError } from 'ofetch' +import type { Ref } from 'vue' + +/** 与 `R.success` / `R.error` 返回结构对齐 */ +export type ApiResponse = { + code: number + message: string + data: T +} + +/** 从 Nitro 推断的响应体上剥离一层 `ApiResponse`,得到 `data` 字段类型 */ +export type UnwrapApiResponse = T extends ApiResponse ? D : T + +export function unwrapApiBody(payload: ApiResponse): T { + if (payload.code !== 0) { + throw new Error(payload.message) + } + return payload.data +} + +export const request = $fetch.create({}) + +const httpFetchDefaults = { + retry: 0, + $fetch: request, + transform: unwrapApiBody, +} + +const _useHttpFetch = createUseFetch(httpFetchDefaults) +const _useLazyHttpFetch = createUseFetch({ + ...httpFetchDefaults, + lazy: true, +}) + +type DefaultMethod = 'get' extends AvailableRouterMethod + ? 'get' + : AvailableRouterMethod + +type HttpFetchOptions< + _ResT, + DataT, + PickKeys extends KeysOf, + DefaultT, + ReqT extends NitroFetchRequest, + Method extends AvailableRouterMethod, +> = Omit, 'transform'> + +/** + * 带项目默认选项的 `useFetch`,并在类型上将 `data` 视为接口 `{ code, data, message }` 中的内层 `data`。 + * + * 说明:Nuxt的 `createUseFetch` 在类型上不会把工厂里的 `transform` 的出参当作 `AsyncData['data']`, + * 因此这里用薄包装修正 `DataT`(见 `UnwrapApiResponse`)。 + */ +export function useHttpFetch< + ResT = void, + ErrorT = FetchError, + ReqT extends NitroFetchRequest = NitroFetchRequest, + Method extends AvailableRouterMethod = ResT extends void + ? DefaultMethod + : AvailableRouterMethod, + _ResT = ResT extends void ? TypedInternalResponse> : ResT, + DataT = UnwrapApiResponse<_ResT>, + PickKeys extends KeysOf = KeysOf, + DefaultT = undefined, +>( + url: ReqT | Ref | (() => ReqT), + opts?: HttpFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, +): AsyncData, ErrorT | undefined> { + return _useHttpFetch(url, opts) as AsyncData, ErrorT | undefined> +} + +export function useLazyHttpFetch< + ResT = void, + ErrorT = FetchError, + ReqT extends NitroFetchRequest = NitroFetchRequest, + Method extends AvailableRouterMethod = ResT extends void + ? DefaultMethod + : AvailableRouterMethod, + _ResT = ResT extends void ? TypedInternalResponse> : ResT, + DataT = UnwrapApiResponse<_ResT>, + PickKeys extends KeysOf = KeysOf, + DefaultT = undefined, +>( + url: ReqT | Ref | (() => ReqT), + opts?: HttpFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>, +): AsyncData, ErrorT | undefined> { + return _useLazyHttpFetch(url, opts) as AsyncData< + DefaultT | PickFrom, + ErrorT | undefined + > +} diff --git a/bun.lock b/bun.lock index dbc8b73..8be5baf 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,7 @@ "drizzle-zod": "0.8.3", "log4js": "6.9.1", "logger": "workspace:*", - "mime": "^4.1.0", + "mime": "4.1.0", "multer": "2.1.1", "nuxt": "4.4.2", "pg": "8.20.0", diff --git a/nuxt.config.ts b/nuxt.config.ts index 4f8e5fe..94d068f 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -2,4 +2,19 @@ export default defineNuxtConfig({ compatibilityDate: '2025-07-15', devtools: { enabled: true }, + nitro: { + typescript: { + tsConfig: { + compilerOptions: { + resolvePackageJsonExports: true, + resolvePackageJsonImports: true, + baseUrl: './', + paths: { + 'drizzle-pkg': ['./packages/drizzle-pkg/lib'], + 'logger': ['./packages/logger/lib'] + }, + } + }, + } + }, }) diff --git a/packages/drizzle-pkg/lib/schema/auth.ts b/packages/drizzle-pkg/lib/schema/auth.ts index 0532ada..c11e658 100644 --- a/packages/drizzle-pkg/lib/schema/auth.ts +++ b/packages/drizzle-pkg/lib/schema/auth.ts @@ -1,8 +1,18 @@ -import { integer, pgTable, varchar } from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm"; +import { integer, pgTable, timestamp, varchar } from "drizzle-orm/pg-core"; export const usersTable = pgTable("users", { - id: integer().primaryKey().generatedAlwaysAsIdentity(), - name: varchar().notNull(), - age: integer().notNull(), - email: varchar().notNull().unique(), + id: integer().primaryKey(), + email: varchar().unique(), + username: varchar().notNull().unique(), + nickname: varchar(), + password: varchar().notNull(), + avatar: varchar(), + tel: varchar(), + role: varchar().notNull().default('user').$type<('user' | 'admin')>(), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at') + .defaultNow() + .$onUpdate(() => sql`CURRENT_TIMESTAMP`) + .notNull(), }); \ No newline at end of file diff --git a/server/api/hello.get.ts b/server/api/hello.get.ts index a59dc93..00cb859 100644 --- a/server/api/hello.get.ts +++ b/server/api/hello.get.ts @@ -1,6 +1,19 @@ +import { dbGlobal } from "drizzle-pkg/lib/db" +import { usersTable } from "drizzle-pkg/lib/schema/auth" -export default defineWrappedResponseHandler((event) => { - return { +export default defineWrappedResponseHandler(async (event) => { + + await dbGlobal.insert(usersTable).values({ + id: 1, + username: "test", + password: "test", + email: "test@test.com", + nickname: "test", + avatar: "test", + tel: "test", + role: "user", + }) + return R.success({ hello: "aa", users: [ { @@ -10,5 +23,5 @@ export default defineWrappedResponseHandler((event) => { age: 23, } ] - } + }) }) \ No newline at end of file diff --git a/server/plugins/03.error-logger.ts b/server/plugins/03.error-logger.ts index 9a7b1ec..c0385fe 100644 --- a/server/plugins/03.error-logger.ts +++ b/server/plugins/03.error-logger.ts @@ -36,8 +36,8 @@ export default defineNitroPlugin((nitroApp) => { event?.method ?? "", event?.path ?? "(no request)", tags ? `[${tags}]` : "", - error.message, - error.stack ?? "" + "\n", + error ); }); }); diff --git a/server/utils/handler.ts b/server/utils/handler.ts index 55d98cf..f5f9854 100644 --- a/server/utils/handler.ts +++ b/server/utils/handler.ts @@ -1,3 +1,4 @@ +import log4js from "logger"; interface IConfig { @@ -7,6 +8,8 @@ const defaultConfig: IConfig = { } +const logger = log4js.getLogger("ERROR"); + export const defineWrappedResponseHandler = ( handlerOrConfig?: EventHandler | IConfig, _handler?: EventHandler, @@ -16,13 +19,20 @@ export const defineWrappedResponseHandler = ( throw new Error('handler or config is required'); } const config = Object.assign({ ...defaultConfig }, typeof handlerOrConfig === 'object' ? handlerOrConfig : {}); - + return defineEventHandler(async (event) => { try { const response = await handler(event) return response - } catch (err) { - return { err } + } catch (error) { + logger.error( + event?.method ?? "", + event?.path ?? "(no request)", + `[request]`, + "\n", + error + ); + return error } }) } \ No newline at end of file diff --git a/server/utils/response.ts b/server/utils/response.ts new file mode 100644 index 0000000..ba81a29 --- /dev/null +++ b/server/utils/response.ts @@ -0,0 +1,24 @@ + +export const R = { + success: (data: T) => { + return { + code: 0, + message: 'success', + data: data, + } as const + }, + error: (message: string, data: T) => { + return { + code: 1, + message: message, + data: null, + } as const + }, + throwError: (code: number, message: string, data: T) => { + throw createError({ + statusCode: code, + statusMessage: message, + data: data, + }) as never + } +} as const \ No newline at end of file