change architecture
This commit is contained in:
6
bun.lock
6
bun.lock
@@ -11,7 +11,6 @@
|
||||
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||
"@hiogawa/utils": "^1.7.0",
|
||||
"@hono-di/cli": "^0.0.15",
|
||||
"@hono-di/core": "^0.0.15",
|
||||
"@nestjs/common": "^11.1.11",
|
||||
"@nestjs/core": "^11.1.11",
|
||||
"@primeuix/themes": "^2.0.2",
|
||||
@@ -21,6 +20,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"hono": "^4.11.3",
|
||||
"is-mobile": "^5.0.0",
|
||||
"nestjs-zod": "^5.1.1",
|
||||
"pinia": "^3.0.4",
|
||||
"primevue": "^4.5.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
@@ -640,6 +640,8 @@
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||
|
||||
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||
|
||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
||||
@@ -762,6 +764,8 @@
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"nestjs-zod": ["nestjs-zod@5.1.1", "", { "dependencies": { "deepmerge": "^4.3.1" }, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "@nestjs/swagger": "^7.4.2 || ^8.0.0 || ^11.0.0", "rxjs": "^7.0.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@nestjs/swagger"] }, "sha512-pXa9Jrdip7iedKvGxJTvvCFVRCoIcNENPCsHjpCefPH3PcFejRgkZkUcr3TYITRyxnUk7Zy5OsLpirZGLYBfBQ=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
||||
|
||||
48
components.d.ts
vendored
48
components.d.ts
vendored
@@ -12,47 +12,47 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
Add: typeof import('./src/components/icons/Add.vue')['default']
|
||||
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||
Add: typeof import('./src/client/components/icons/Add.vue')['default']
|
||||
Bell: typeof import('./src/client/components/icons/Bell.vue')['default']
|
||||
Button: typeof import('primevue/button')['default']
|
||||
Checkbox: typeof import('primevue/checkbox')['default']
|
||||
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||
Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
||||
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||
Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||
CheckIcon: typeof import('./src/client/components/icons/CheckIcon.vue')['default']
|
||||
Credit: typeof import('./src/client/components/icons/Credit.vue')['default']
|
||||
DashboardLayout: typeof import('./src/client/components/DashboardLayout.vue')['default']
|
||||
Home: typeof import('./src/client/components/icons/Home.vue')['default']
|
||||
InputText: typeof import('primevue/inputtext')['default']
|
||||
Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||
Layout: typeof import('./src/client/components/icons/Layout.vue')['default']
|
||||
Message: typeof import('primevue/message')['default']
|
||||
Password: typeof import('primevue/password')['default']
|
||||
RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||
RootLayout: typeof import('./src/client/components/RootLayout.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default']
|
||||
Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
||||
Video: typeof import('./src/components/icons/Video.vue')['default']
|
||||
VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
||||
TestIcon: typeof import('./src/client/components/icons/TestIcon.vue')['default']
|
||||
Upload: typeof import('./src/client/components/icons/Upload.vue')['default']
|
||||
Video: typeof import('./src/client/components/icons/Video.vue')['default']
|
||||
VueHead: typeof import('./src/client/components/VueHead.tsx')['default']
|
||||
}
|
||||
}
|
||||
|
||||
// For TSX support
|
||||
declare global {
|
||||
const Add: typeof import('./src/components/icons/Add.vue')['default']
|
||||
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
||||
const Add: typeof import('./src/client/components/icons/Add.vue')['default']
|
||||
const Bell: typeof import('./src/client/components/icons/Bell.vue')['default']
|
||||
const Button: typeof import('primevue/button')['default']
|
||||
const Checkbox: typeof import('primevue/checkbox')['default']
|
||||
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
||||
const Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
||||
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
||||
const Home: typeof import('./src/components/icons/Home.vue')['default']
|
||||
const CheckIcon: typeof import('./src/client/components/icons/CheckIcon.vue')['default']
|
||||
const Credit: typeof import('./src/client/components/icons/Credit.vue')['default']
|
||||
const DashboardLayout: typeof import('./src/client/components/DashboardLayout.vue')['default']
|
||||
const Home: typeof import('./src/client/components/icons/Home.vue')['default']
|
||||
const InputText: typeof import('primevue/inputtext')['default']
|
||||
const Layout: typeof import('./src/components/icons/Layout.vue')['default']
|
||||
const Layout: typeof import('./src/client/components/icons/Layout.vue')['default']
|
||||
const Message: typeof import('primevue/message')['default']
|
||||
const Password: typeof import('primevue/password')['default']
|
||||
const RootLayout: typeof import('./src/components/RootLayout.vue')['default']
|
||||
const RootLayout: typeof import('./src/client/components/RootLayout.vue')['default']
|
||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||
const RouterView: typeof import('vue-router')['RouterView']
|
||||
const TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default']
|
||||
const Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
||||
const Video: typeof import('./src/components/icons/Video.vue')['default']
|
||||
const VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
||||
const TestIcon: typeof import('./src/client/components/icons/TestIcon.vue')['default']
|
||||
const Upload: typeof import('./src/client/components/icons/Upload.vue')['default']
|
||||
const Video: typeof import('./src/client/components/icons/Video.vue')['default']
|
||||
const VueHead: typeof import('./src/client/components/VueHead.tsx')['default']
|
||||
}
|
||||
@@ -13,7 +13,6 @@
|
||||
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||
"@hiogawa/utils": "^1.7.0",
|
||||
"@hono-di/cli": "^0.0.15",
|
||||
"@hono-di/core": "^0.0.15",
|
||||
"@nestjs/common": "^11.1.11",
|
||||
"@nestjs/core": "^11.1.11",
|
||||
"@primeuix/themes": "^2.0.2",
|
||||
@@ -23,6 +22,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"hono": "^4.11.3",
|
||||
"is-mobile": "^5.0.0",
|
||||
"nestjs-zod": "^5.1.1",
|
||||
"pinia": "^3.0.4",
|
||||
"primevue": "^4.5.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
||||
@@ -115,8 +115,8 @@ export default function ssrPlugin(): Plugin[] {
|
||||
return path.resolve(
|
||||
__dirname,
|
||||
options?.ssr
|
||||
? pwd+"/src/api/httpClientAdapter.server.ts"
|
||||
: pwd+"/src/api/httpClientAdapter.client.ts"
|
||||
? pwd+"/src/client/api/httpClientAdapter.server.ts"
|
||||
: pwd+"/src/client/api/httpClientAdapter.client.ts"
|
||||
);
|
||||
},
|
||||
async configResolved(config) {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import Add from "@/components/icons/Add.vue";
|
||||
import Bell from "@/components/icons/Bell.vue";
|
||||
import Home from "@/components/icons/Home.vue";
|
||||
import Video from "@/components/icons/Video.vue";
|
||||
import Credit from "@/components/icons/Credit.vue";
|
||||
import Home from "@/client/components/icons/Home.vue";
|
||||
import Video from "@/client/components/icons/Video.vue";
|
||||
import Credit from "@/client/components/icons/Credit.vue";
|
||||
import Upload from "./icons/Upload.vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { cn } from "@/client/lib/utils";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
import { createStaticVNode } from "vue";
|
||||
|
||||
const auth = useAuthStore();
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'uno.css';
|
||||
import createVueApp from './shared/createVueApp';
|
||||
import createVueApp from '@/shared/createVueApp';
|
||||
async function render() {
|
||||
const { app, router } = createVueApp();
|
||||
router.isReady().then(() => {
|
||||
@@ -84,7 +84,7 @@ export function buildBootstrapScript() {
|
||||
assets: [],
|
||||
},
|
||||
"1": {
|
||||
file: "src/client.ts",
|
||||
file: "src/client/index.ts",
|
||||
isEntry: true,
|
||||
css: [],
|
||||
},
|
||||
@@ -8,5 +8,5 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VueHead } from "@/components/VueHead";
|
||||
import { VueHead } from "@/client/components/VueHead";
|
||||
</script>
|
||||
@@ -74,7 +74,7 @@ import { reactive } from 'vue';
|
||||
import { Form, type FormSubmitEvent } from '@primevue/forms';
|
||||
import { zodResolver } from '@primevue/forms/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
import { useAuthStore } from '@/client/stores/auth';
|
||||
const auth = useAuthStore();
|
||||
// const $form = Form.useFormContext();
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
createWebHistory,
|
||||
type RouteRecordRaw,
|
||||
} from "vue-router";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
|
||||
type RouteData = RouteRecordRaw & {
|
||||
meta?: ResolvableValue<ReactiveHead> & { requiresAuth?: boolean };
|
||||
@@ -15,7 +15,7 @@ type RouteData = RouteRecordRaw & {
|
||||
const routes: RouteData[] = [
|
||||
{
|
||||
path: "/",
|
||||
component: () => import("@/components/RootLayout.vue"),
|
||||
component: () => import("@/client/components/RootLayout.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
@@ -76,7 +76,7 @@ const routes: RouteData[] = [
|
||||
},
|
||||
{
|
||||
path: "",
|
||||
component: () => import("@/components/DashboardLayout.vue"),
|
||||
component: () => import("@/client/components/DashboardLayout.vue"),
|
||||
meta: { requiresAuth: true },
|
||||
children: [
|
||||
{
|
||||
@@ -184,7 +184,7 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Head } from '@unhead/vue/components'
|
||||
import { cn } from '@/lib/utils';
|
||||
import { cn } from '@/client/lib/utils';
|
||||
const pricing = {
|
||||
title: "Simple, transparent pricing",
|
||||
subtitle: "Choose the plan that fits your needs. No hidden fees.",
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { client } from '@/api/rpcclient';
|
||||
import { client } from '@/client/api/rpcclient';
|
||||
import { ref } from 'vue';
|
||||
|
||||
interface User {
|
||||
117
src/main.ts
117
src/main.ts
@@ -1,76 +1,63 @@
|
||||
import { Hono } from 'hono';
|
||||
import { contextStorage } from 'hono/context-storage';
|
||||
import { cors } from "hono/cors";
|
||||
import isMobile from 'is-mobile';
|
||||
import { rpcServer } from './api/rpc';
|
||||
import { ssrRender } from './worker/ssrRender';
|
||||
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './server/app.module';
|
||||
import { HonoAdapter } from './server/HonoAdapter/adapter';
|
||||
import { NestHonoApplication } from './server/HonoAdapter/interfaces';
|
||||
// import { serveStatic } from "hono/bun";
|
||||
// @ts-ignore
|
||||
// const app = new Hono()
|
||||
// const isDev = import.meta.env.DEV;
|
||||
|
||||
// // app.use(renderer)
|
||||
// app.use('*', contextStorage());
|
||||
// app.use(cors(), async (c, next) => {
|
||||
// c.set("fetch", app.request.bind(app));
|
||||
// const ua = c.req.header("User-Agent")
|
||||
// if (!ua) {
|
||||
// return c.json({ error: "User-Agent header is missing" }, 400);
|
||||
// };
|
||||
// c.set("isMobile", isMobile({ ua }));
|
||||
// await next();
|
||||
// }, rpcServer);
|
||||
// if (!isDev) {
|
||||
// if ((process as any).versions?.bun) {
|
||||
// const { serveStatic } = await import("hono/bun");
|
||||
// app.use(serveStatic({ root: "./dist/client" }))
|
||||
// }
|
||||
// }
|
||||
// app.get("/.well-known/*", (c) => {
|
||||
// return c.json({ ok: true });
|
||||
// });
|
||||
const appHonoNest = await NestFactory.create<NestHonoApplication>(
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import Bun from "bun";
|
||||
import { Hono } from "hono";
|
||||
import { contextStorage } from "hono/context-storage";
|
||||
import isMobile from "is-mobile";
|
||||
import { AppModule } from "./server/app.module";
|
||||
import { HonoAdapter, NestHonoApplication } from "./server/common/adapter/hono";
|
||||
import { CustomZodValidationPipe } from "./server/common/pipes/CustomZodValidation.pipe";
|
||||
import { ssrRender } from "./server/HonoAdapter/ssrRender";
|
||||
import { TransformInterceptor } from "./server/common/interceptor/transform.interceptor";
|
||||
let serve: Bun.Server<undefined> | any = {
|
||||
stop: async () => {},
|
||||
}
|
||||
const hono = new Hono();
|
||||
const app = await NestFactory.create<NestHonoApplication>(
|
||||
AppModule,
|
||||
new HonoAdapter(),
|
||||
{
|
||||
rawBody: true,
|
||||
},
|
||||
new HonoAdapter({
|
||||
hono,
|
||||
close: () => {
|
||||
console.log("Closing server");
|
||||
return serve!.stop();
|
||||
},
|
||||
address: () => String(serve!.hostname),
|
||||
listen({ port, hostname, hono, httpsOptions = {}, forceCloseConnections }) {
|
||||
return new Promise<void>((resolve) => {
|
||||
serve = Bun.serve({
|
||||
port,
|
||||
hostname,
|
||||
fetch: hono.fetch.bind(hono),
|
||||
});
|
||||
console.log(`Server listening on http://${serve.hostname}:${serve.port}`);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
// app.get('*', (c) => {
|
||||
// console.log("Request URL:", c);
|
||||
// // return next();
|
||||
// });
|
||||
// app.use(ssrRender);
|
||||
// app.use(function (req, next) {
|
||||
// console.log("Request:", arguments[1]);
|
||||
// return (c, next) => {
|
||||
// next();
|
||||
// }
|
||||
// });
|
||||
const app = appHonoNest.getHonoInstance();
|
||||
app.use(async (c, next) => {
|
||||
c.set("fetch", app.request.bind(app));
|
||||
const ua = c.req.header("User-Agent")
|
||||
app.setGlobalPrefix("api");
|
||||
app.enableShutdownHooks();
|
||||
app.useGlobalPipes(new CustomZodValidationPipe());
|
||||
app.useGlobalInterceptors(new TransformInterceptor());
|
||||
|
||||
hono.use(async (c, next) => {
|
||||
c.set("fetch", hono.request.bind(hono));
|
||||
const ua = c.req.header("User-Agent");
|
||||
if (!ua) {
|
||||
return c.json({ error: "User-Agent header is missing" }, 400);
|
||||
};
|
||||
}
|
||||
c.set("isMobile", isMobile({ ua }));
|
||||
await next();
|
||||
}, contextStorage());
|
||||
|
||||
appHonoNest.useStaticAssets("/*", { root: "./dist/client" });
|
||||
await appHonoNest.init();
|
||||
app.get("/.well-known/*", (c) => {
|
||||
app.useStaticAssets("/*", { root: "./dist/client" });
|
||||
await app.init();
|
||||
hono.get("/.well-known/*", (c) => {
|
||||
return c.json({ ok: true });
|
||||
});
|
||||
app.use(ssrRender);
|
||||
const httpAdapter = appHonoNest.get(HttpAdapterHost);
|
||||
// await app.listen(3000)
|
||||
// console.log("HTTP Adapter:", app.fetch.toString());
|
||||
export default {
|
||||
fetch: app.fetch.bind(app),
|
||||
hono.use(ssrRender);
|
||||
if (import.meta.env.PROD) {
|
||||
await app.listen(3500);
|
||||
}
|
||||
const honoDev = import.meta.env.DEV ? hono : null;
|
||||
export default honoDev;
|
||||
// };
|
||||
@@ -298,7 +298,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
||||
bodyLimit?: number,
|
||||
) {
|
||||
Logger.log(
|
||||
`Registering body parser middleware for type: ${type} | bodyLimit: ${bodyLimit}`,
|
||||
`Registering body parser middleware for type: ${type} ${bodyLimit ? 'with limit ' + bodyLimit + ' bytes' : ''}`,
|
||||
);
|
||||
if(bodyLimit !== undefined) {
|
||||
this.instance.use(this.bodyLimit(bodyLimit));
|
||||
|
||||
@@ -3,9 +3,9 @@ import { renderSSRHead } from "@unhead/vue/server";
|
||||
import { Context } from "hono";
|
||||
import { streamText } from "hono/streaming";
|
||||
import { renderToWebStream } from "vue/server-renderer";
|
||||
import { buildBootstrapScript } from "@/lib/manifest";
|
||||
import { styleTags } from "@/lib/primePassthrough";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { buildBootstrapScript } from "@/client/lib/manifest";
|
||||
import { styleTags } from "@/client/lib/primePassthrough";
|
||||
import { useAuthStore } from "@/client/stores/auth";
|
||||
// @ts-ignore
|
||||
import Base from "@primevue/core/base";
|
||||
import { BlankEnv, BlankInput } from "hono/types";
|
||||
@@ -1,466 +0,0 @@
|
||||
// import { HttpBindings, createAdaptorServer } from '@hono/node-server';
|
||||
// import { ServeStaticOptions, serveStatic } from '@hono/node-server/serve-static';
|
||||
// import { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response';
|
||||
import { RequestMethod } from '@nestjs/common';
|
||||
import { HttpStatus, Logger } from '@nestjs/common';
|
||||
import {
|
||||
ErrorHandler,
|
||||
NestApplicationOptions,
|
||||
RequestHandler,
|
||||
} from '@nestjs/common/interfaces';
|
||||
import { isObject } from '@nestjs/common/utils/shared.utils';
|
||||
import { AbstractHttpAdapter } from '@nestjs/core/adapters/http-adapter';
|
||||
import { Context, Next, Hono } from 'hono';
|
||||
import { bodyLimit } from 'hono/body-limit';
|
||||
import { cors } from 'hono/cors';
|
||||
// import { Data } from 'hono/dist/types/context';
|
||||
import { RedirectStatusCode, StatusCode } from 'hono/utils/http-status';
|
||||
import http2 from 'http2';
|
||||
import { createServer as createServerHTTP, Server } from 'http';
|
||||
import { createServer as createServerHTTPS } from 'https';
|
||||
|
||||
import { HonoRequest, TypeBodyParser } from './interfaces';
|
||||
import { Data } from 'node_modules/hono/dist/types/context';
|
||||
|
||||
// Define HttpBindings as an empty interface if you don't need specific bindings
|
||||
interface HttpBindings {}
|
||||
|
||||
type HonoHandler = RequestHandler<HonoRequest, Context>;
|
||||
type ServerType = Server | http2.Http2Server | http2.Http2SecureServer;
|
||||
type Ctx = Context | (() => Promise<Context>);
|
||||
type Method =
|
||||
| 'all'
|
||||
| 'get'
|
||||
| 'post'
|
||||
| 'put'
|
||||
| 'delete'
|
||||
| 'use'
|
||||
| 'patch'
|
||||
| 'options';
|
||||
|
||||
/**
|
||||
* Adapter for using Hono with NestJS.
|
||||
*/
|
||||
export class HonoAdapter extends AbstractHttpAdapter<
|
||||
ServerType,
|
||||
HonoRequest,
|
||||
Context
|
||||
> {
|
||||
private _isParserRegistered: boolean = false;
|
||||
|
||||
protected readonly instance: Hono<{ Bindings: HttpBindings }>;
|
||||
|
||||
constructor() {
|
||||
const honoInstance = new Hono<{ Bindings: HttpBindings }>();
|
||||
super(honoInstance);
|
||||
this.instance = honoInstance;
|
||||
}
|
||||
|
||||
get isParserRegistered(): boolean {
|
||||
return !!this._isParserRegistered;
|
||||
}
|
||||
|
||||
private getRouteAndHandler(
|
||||
pathOrHandler: string | HonoHandler,
|
||||
handler?: HonoHandler,
|
||||
): [string, HonoHandler] {
|
||||
const path = typeof pathOrHandler === 'function' ? '' : pathOrHandler;
|
||||
handler = typeof pathOrHandler === 'function' ? pathOrHandler : handler;
|
||||
|
||||
return [path, handler!];
|
||||
}
|
||||
|
||||
private createRouteHandler(routeHandler: HonoHandler) {
|
||||
return async (ctx: Context, next: Next) => {
|
||||
// ctx.req['params'] = ctx.req.param();
|
||||
// ctx.req['params'] = ctx.req.param();
|
||||
Object.assign(ctx.req, { params: ctx.req.param() });
|
||||
|
||||
await routeHandler(ctx.req, ctx, next);
|
||||
|
||||
return this.getBody(ctx);
|
||||
};
|
||||
}
|
||||
|
||||
private async normalizeContext(ctx: Ctx): Promise<Context> {
|
||||
if (typeof ctx === 'function') {
|
||||
return await ctx();
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
private async getBody(ctx: Ctx, body?: Data) {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
|
||||
if (body === undefined && ctx.res && ctx.res.body !== null) {
|
||||
return ctx.res;
|
||||
}
|
||||
|
||||
let responseContentType = await this.getHeader(ctx, 'Content-Type');
|
||||
|
||||
if (!responseContentType || responseContentType.startsWith('text/plain')) {
|
||||
if (
|
||||
body instanceof Buffer ||
|
||||
body instanceof Uint8Array ||
|
||||
body instanceof ArrayBuffer ||
|
||||
body instanceof ReadableStream
|
||||
) {
|
||||
responseContentType = 'application/octet-stream';
|
||||
} else if (isObject(body)) {
|
||||
responseContentType = 'application/json';
|
||||
}
|
||||
|
||||
await this.setHeader(ctx, 'Content-Type', String(responseContentType));
|
||||
}
|
||||
|
||||
if (responseContentType === 'application/json' && isObject(body)) {
|
||||
return ctx.json(body);
|
||||
} else if (body === undefined) {
|
||||
return ctx.newResponse(null);
|
||||
}
|
||||
|
||||
return ctx.body(body);
|
||||
}
|
||||
|
||||
private registerRoute(
|
||||
method: Method,
|
||||
pathOrHandler: string | HonoHandler,
|
||||
handler?: HonoHandler,
|
||||
) {
|
||||
const [routePath, routeHandler] = this.getRouteAndHandler(
|
||||
pathOrHandler,
|
||||
handler,
|
||||
);
|
||||
const routeHandler2 = this.createRouteHandler(routeHandler);
|
||||
|
||||
switch (method) {
|
||||
case 'all':
|
||||
this.instance.all(routePath, routeHandler2);
|
||||
break;
|
||||
case 'get':
|
||||
this.instance.get(routePath, routeHandler2);
|
||||
break;
|
||||
case 'post':
|
||||
this.instance.post(routePath, routeHandler2);
|
||||
break;
|
||||
case 'put':
|
||||
this.instance.put(routePath, routeHandler2);
|
||||
break;
|
||||
case 'delete':
|
||||
this.instance.delete(routePath, routeHandler2);
|
||||
break;
|
||||
case 'use':
|
||||
this.instance.use(routePath, routeHandler2);
|
||||
break;
|
||||
case 'patch':
|
||||
this.instance.patch(routePath, routeHandler2);
|
||||
break;
|
||||
case 'options':
|
||||
this.instance.options(routePath, routeHandler2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public all(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||
this.registerRoute('all', pathOrHandler, handler);
|
||||
}
|
||||
|
||||
public get(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||
this.registerRoute('get', pathOrHandler, handler);
|
||||
}
|
||||
|
||||
public post(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||
this.registerRoute('post', pathOrHandler, handler);
|
||||
}
|
||||
|
||||
public put(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||
this.registerRoute('put', pathOrHandler, handler);
|
||||
}
|
||||
|
||||
public delete(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||
this.registerRoute('delete', pathOrHandler, handler);
|
||||
}
|
||||
|
||||
public use(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||
this.registerRoute('use', pathOrHandler, handler);
|
||||
}
|
||||
|
||||
public patch(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||
this.registerRoute('patch', pathOrHandler, handler);
|
||||
}
|
||||
|
||||
public options(pathOrHandler: string | HonoHandler, handler?: HonoHandler) {
|
||||
this.registerRoute('options', pathOrHandler, handler);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public async reply(ctx: Ctx, body: any, statusCode?: StatusCode) {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
|
||||
if (statusCode) {
|
||||
ctx.status(statusCode);
|
||||
}
|
||||
|
||||
ctx.res = await this.getBody(ctx, body);
|
||||
}
|
||||
|
||||
public async status(ctx: Ctx, statusCode: StatusCode) {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
return ctx.status(statusCode);
|
||||
}
|
||||
|
||||
public async end() {
|
||||
// return RESPONSE_ALREADY_SENT;
|
||||
new Response(null, {
|
||||
headers: { ['x-hono-already-sent']: "true" }
|
||||
})
|
||||
}
|
||||
|
||||
public render() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public async redirect(ctx: Ctx, statusCode: RedirectStatusCode, url: string) {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
ctx.res = ctx.redirect(url, statusCode);
|
||||
}
|
||||
|
||||
public setErrorHandler(handler: ErrorHandler) {
|
||||
this.instance.onError(async (err: Error, ctx: Context) => {
|
||||
await handler(err, ctx.req, ctx);
|
||||
|
||||
return this.getBody(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
public setNotFoundHandler(handler: RequestHandler) {
|
||||
this.instance.notFound(async (ctx: Context) => {
|
||||
await handler(ctx.req, ctx);
|
||||
await this.status(ctx, HttpStatus.NOT_FOUND);
|
||||
|
||||
return this.getBody(ctx, 'Not Found');
|
||||
});
|
||||
}
|
||||
|
||||
public async useStaticAssets(path: string, options: any) {
|
||||
Logger.log("Đã bị comment trong adapter")
|
||||
Logger.log('Registering static assets middleware');
|
||||
if ((process as any).versions?.bun) {
|
||||
const { serveStatic } = await import("hono/bun");
|
||||
// app.use(serveStatic({ root: "./dist/client" }))
|
||||
// this.instance.use(serveStatic({ root: "./dist/client" }))
|
||||
this.instance.use(path, serveStatic(options));
|
||||
}
|
||||
}
|
||||
|
||||
public setViewEngine() {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
public async isHeadersSent(ctx: Ctx): Promise<boolean> {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
return ctx.finalized;
|
||||
}
|
||||
|
||||
public async getHeader(ctx: Ctx, name: string) {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
return ctx.res.headers.get(name);
|
||||
}
|
||||
|
||||
public async setHeader(ctx: Ctx, name: string, value: string) {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
ctx.res.headers.set(name, value);
|
||||
}
|
||||
|
||||
public async appendHeader(ctx: Ctx, name: string, value: string) {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
ctx.res.headers.append(name, value);
|
||||
}
|
||||
|
||||
public async getRequestHostname(ctx: Ctx): Promise<string> {
|
||||
ctx = await this.normalizeContext(ctx);
|
||||
return ctx.req.header().host;
|
||||
}
|
||||
|
||||
public getRequestMethod(request: HonoRequest): string {
|
||||
return request.method;
|
||||
}
|
||||
|
||||
public getRequestUrl(request: HonoRequest): string {
|
||||
return request.url;
|
||||
}
|
||||
|
||||
public enableCors(options: {
|
||||
origin:
|
||||
| string
|
||||
| string[]
|
||||
| ((origin: string, c: Context) => string | undefined | null);
|
||||
allowMethods?: string[];
|
||||
allowHeaders?: string[];
|
||||
maxAge?: number;
|
||||
credentials?: boolean;
|
||||
exposeHeaders?: string[];
|
||||
}) {
|
||||
this.instance.use(cors(options));
|
||||
}
|
||||
|
||||
public useBodyParser(
|
||||
type: TypeBodyParser,
|
||||
rawBody?: boolean,
|
||||
bodyLimit?: number,
|
||||
) {
|
||||
Logger.log(
|
||||
`Registering body parser middleware for type: ${type} | bodyLimit: ${bodyLimit}`,
|
||||
);
|
||||
if(bodyLimit !== undefined) {
|
||||
this.instance.use(this.bodyLimit(bodyLimit));
|
||||
}
|
||||
|
||||
// To avoid the Nest application init to override our custom
|
||||
// body parser, we mark the parsers as registered.
|
||||
this._isParserRegistered = true;
|
||||
}
|
||||
|
||||
public close(): Promise<void> {
|
||||
return new Promise((resolve) => this.httpServer.close(() => resolve()));
|
||||
}
|
||||
|
||||
private extractClientIp(ctx: Context): string {
|
||||
return (
|
||||
ctx.req.header('cf-connecting-ip') ??
|
||||
ctx.req.header('x-forwarded-for') ??
|
||||
ctx.req.header('x-real-ip') ??
|
||||
ctx.req.header('forwarded') ??
|
||||
ctx.req.header('true-client-ip') ??
|
||||
ctx.req.header('x-client-ip') ??
|
||||
ctx.req.header('x-cluster-client-ip') ??
|
||||
ctx.req.header('x-forwarded') ??
|
||||
ctx.req.header('forwarded-for') ??
|
||||
ctx.req.header('via')??
|
||||
"unknown"
|
||||
);
|
||||
}
|
||||
|
||||
private async parseRequestBody(
|
||||
ctx: Context,
|
||||
contentType: string,
|
||||
rawBody: boolean,
|
||||
): Promise<void> {
|
||||
if (
|
||||
contentType?.startsWith('multipart/form-data') ||
|
||||
contentType?.startsWith('application/x-www-form-urlencoded')
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ctx.req as any).body = await ctx.req
|
||||
.parseBody({
|
||||
all: true,
|
||||
})
|
||||
.catch(() => {});
|
||||
} else if (
|
||||
contentType?.startsWith('application/json') ||
|
||||
contentType?.startsWith('text/plain')
|
||||
) {
|
||||
if (rawBody) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ctx.req as any).rawBody = Buffer.from(await ctx.req.text());
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(ctx.req as any).body = await ctx.req.json().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
public initHttpServer(options: NestApplicationOptions) {
|
||||
Logger.log('Initializing Hono HTTP server adapter');
|
||||
this.instance.use(async (ctx, next) => {
|
||||
// ctx.req['ip'] = this.extractClientIp(ctx);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// ctx.req['query'] = ctx.req.query() as any;
|
||||
// ctx.req['headers'] = Object.fromEntries(ctx.req.raw.headers);
|
||||
Object.assign(ctx.req, {
|
||||
ip: this.extractClientIp(ctx),
|
||||
query: ctx.req.query() as Record<string, string>,
|
||||
headers: Object.fromEntries(ctx.req.raw.headers as any),
|
||||
})
|
||||
const contentType = ctx.req.header('content-type');
|
||||
if (contentType) {
|
||||
await this.parseRequestBody(ctx, contentType, options.rawBody!);
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
const isHttpsEnabled = options?.httpsOptions;
|
||||
const createServer = isHttpsEnabled
|
||||
? createServerHTTPS
|
||||
: createServerHTTP;
|
||||
// this.httpServer = createAdaptorServer({
|
||||
// fetch: this.instance.fetch,
|
||||
// createServer,
|
||||
// overrideGlobalObjects: false,
|
||||
// });
|
||||
// this.httpServer = isHttpsEnabled ?
|
||||
return this.httpServer;
|
||||
}
|
||||
|
||||
public getType(): string {
|
||||
return 'hono';
|
||||
}
|
||||
|
||||
public registerParserMiddleware(_prefix?: string, rawBody?: boolean) {
|
||||
if (this._isParserRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.log('Registering parser middleware');
|
||||
this.useBodyParser('application/x-www-form-urlencoded', rawBody);
|
||||
this.useBodyParser('application/json', rawBody);
|
||||
this.useBodyParser('text/plain', rawBody);
|
||||
|
||||
this._isParserRegistered = true;
|
||||
}
|
||||
|
||||
public async createMiddlewareFactory(requestMethod: RequestMethod) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
return (path: string, callback: Function) => {
|
||||
const routeMethodsMap: Record<string, Function> = {
|
||||
[RequestMethod.ALL]: this.instance.all,
|
||||
[RequestMethod.DELETE]: this.instance.delete,
|
||||
[RequestMethod.GET]: this.instance.get,
|
||||
[RequestMethod.OPTIONS]: this.instance.options,
|
||||
[RequestMethod.PATCH]: this.instance.patch,
|
||||
[RequestMethod.POST]: this.instance.post,
|
||||
[RequestMethod.PUT]: this.instance.put,
|
||||
};
|
||||
const routeMethod = (
|
||||
routeMethodsMap[requestMethod] || this.instance.get
|
||||
).bind(this.instance);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||
routeMethod(path, async (ctx: Context, next: Function) => {
|
||||
await callback(ctx.req, ctx, next);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public applyVersionFilter(): () => () => unknown {
|
||||
throw new Error('Versioning not yet supported in Hono');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public listen(port: string | number, ...args: any[]): ServerType {
|
||||
return this.httpServer.listen(port, ...args);
|
||||
}
|
||||
public fetch(input: RequestInfo, init?: RequestInit): Response | Promise<Response> {
|
||||
return this.instance.fetch(input as Request, init);
|
||||
}
|
||||
public getHonoInstance(): Hono<{ Bindings: HttpBindings }> {
|
||||
return this.instance;
|
||||
}
|
||||
public bodyLimit(maxSize: number) {
|
||||
return bodyLimit({
|
||||
maxSize,
|
||||
onError: (ctx) => {
|
||||
const errorMessage = `Body size exceeded: ${maxSize} bytes. Size: ${ctx.req.header('Content-Length')} bytes. Method: ${ctx.req.method}. Path: ${ctx.req.path}`;
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// import { Controller, Get } from '@hono-di/core';
|
||||
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
|
||||
@Controller('app')
|
||||
export class AppController {
|
||||
constructor() {}
|
||||
|
||||
@Get('/')
|
||||
index() {
|
||||
return 'Hello App';
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,20 @@
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { LoggerMiddleware } from './middleware';
|
||||
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
|
||||
import { APP_FILTER } from "@nestjs/core";
|
||||
import { HttpExceptionFilter } from "./common/filter/http-exception.filter";
|
||||
import { LoggerMiddleware } from "./middleware";
|
||||
import { AuthModule } from "./modules/auth/auth.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// hono-di:imports
|
||||
],
|
||||
controllers: [
|
||||
AppController, // hono-di:controllers
|
||||
],
|
||||
imports: [AuthModule],
|
||||
providers: [
|
||||
AppService, // hono-di:providers
|
||||
{
|
||||
provide: APP_FILTER,
|
||||
useClass: HttpExceptionFilter,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer
|
||||
.apply(LoggerMiddleware).forRoutes('*');
|
||||
consumer.apply(LoggerMiddleware).forRoutes("*");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
constructor() {}
|
||||
}
|
||||
86
src/server/common/adapter/hono/HonoAdapter.ts
Normal file
86
src/server/common/adapter/hono/HonoAdapter.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Hono } from "hono";
|
||||
|
||||
import type { NestApplicationOptions } from "@nestjs/common/interfaces";
|
||||
import type { Server as NodeHttpServer } from "node:http";
|
||||
|
||||
import type { NestHttpServerRequired } from "./_nest";
|
||||
import { createNestRequiredHttpServer, isFakeHttpServer } from "./_util";
|
||||
import { HonoRouterAdapter } from "./_adapter";
|
||||
|
||||
export type InitHttpServerConfig = Pick<NestApplicationOptions, "httpsOptions" | "forceCloseConnections"> & {
|
||||
hono: Hono;
|
||||
};
|
||||
export interface CreateHonoAdapterOption {
|
||||
/** Called during server initialization */
|
||||
initHttpServer?(config: InitHttpServerConfig): NestHttpServerRequired;
|
||||
/** Obtain the address information for HTTP Server listening */
|
||||
address?: () => ReturnType<NodeHttpServer["address"]>;
|
||||
/** Call when shutting down the server */
|
||||
close?: () => Promise<void>;
|
||||
listen?: (config: InitHttpServerConfig & { port: number; hostname?: string }) => Promise<void>;
|
||||
/** Customize Hono instance */
|
||||
hono?: Hono;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter for using Hono with NestJS.
|
||||
*/
|
||||
export class HonoAdapter extends HonoRouterAdapter {
|
||||
#honoAdapterConfig: CreateHonoAdapterOption;
|
||||
constructor(config: CreateHonoAdapterOption = {}) {
|
||||
super(config.hono ?? new Hono());
|
||||
this.#honoAdapterConfig = config;
|
||||
}
|
||||
override listen(port: string | number, callback?: () => void): void;
|
||||
override listen(port: string | number, hostname: string, callback?: () => void): void;
|
||||
override async listen(port: string | number, ...args: any[]): Promise<void> {
|
||||
port = +port;
|
||||
|
||||
let callback = args[args.length - 1];
|
||||
if (typeof callback !== "function") callback = undefined;
|
||||
const config = this.#honoAdapterConfig;
|
||||
|
||||
if (config.listen) {
|
||||
try {
|
||||
const hostname = typeof args[0] === "string" ? args[0] : undefined;
|
||||
await config.listen({
|
||||
...this.#initHttServerOption!,
|
||||
port: +port,
|
||||
hostname,
|
||||
hono: this.instance,
|
||||
});
|
||||
} catch (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isFakeHttpServer(this.httpServer)) {
|
||||
this.httpServer.address = config.address ?? (() => "127.0.0.1");
|
||||
callback?.();
|
||||
} else {
|
||||
try {
|
||||
port = +port;
|
||||
return this.httpServer.listen!(port, ...args);
|
||||
} catch (error) {
|
||||
callback?.(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
//implement
|
||||
async close(): Promise<void> {
|
||||
if (!isFakeHttpServer(this.httpServer) && this.httpServer.close) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
return this.httpServer.close!((err) => (err ? reject(err) : resolve()));
|
||||
});
|
||||
}
|
||||
return this.#honoAdapterConfig.close?.();
|
||||
}
|
||||
#initHttServerOption?: Omit<InitHttpServerConfig, "hono">;
|
||||
//implement
|
||||
initHttpServer(options: NestApplicationOptions = {}) {
|
||||
const { forceCloseConnections, httpsOptions } = options;
|
||||
this.#initHttServerOption = { forceCloseConnections, httpsOptions };
|
||||
const httpServer = this.#honoAdapterConfig.initHttpServer?.({ ...this.#initHttServerOption!, hono: this.instance });
|
||||
this.httpServer = httpServer ?? createNestRequiredHttpServer();
|
||||
}
|
||||
}
|
||||
36
src/server/common/adapter/hono/HonoApplication.ts
Normal file
36
src/server/common/adapter/hono/HonoApplication.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { INestApplication } from "@nestjs/common";
|
||||
import type { HonoApplicationExtra, HonoBodyParser } from "./hono.impl.ts";
|
||||
import type { CORSOptions } from "./_adapter.ts";
|
||||
import type { NestHttpServerRequired } from "./_nest.ts";
|
||||
import type { MiddlewareHandler } from "hono";
|
||||
|
||||
export interface NestHonoApplication<TServer extends NestHttpServerRequired = NestHttpServerRequired>
|
||||
extends INestApplication<TServer>, HonoApplicationExtra {
|
||||
use(...handlers: MiddlewareHandler[]): this;
|
||||
use(path: string, ...handlers: MiddlewareHandler[]): this;
|
||||
// getHttpAdapter(): HonoAdapter;
|
||||
/**
|
||||
* By default, `application/json`, `application/x-www-form-urlencoded`, `multipart/form-data` and `text/plain` are automatically resolved
|
||||
* You can customize the parser, for example to parse the `application/custom` request body unmapped
|
||||
* ```ts
|
||||
* const app = await NestFactory.create<NestHonoApplication>(AppModule, adapter);
|
||||
* app.useBodyParser("application/custom", async (honoRequest) => {
|
||||
* const json = await honoRequest.json();
|
||||
* return new Map(json);
|
||||
* });
|
||||
*
|
||||
* @Controller()
|
||||
* class Test {
|
||||
* @Get("data")
|
||||
* method(@Body() body: Map) {
|
||||
* return data;
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
useBodyParser(contentType: string, parser: HonoBodyParser): void;
|
||||
enableCors(options?: CORSOptions): void;
|
||||
enableCors(options: any): void;
|
||||
}
|
||||
declare module "@nestjs/common" {
|
||||
interface INestApplication<TServer = any> {}
|
||||
}
|
||||
298
src/server/common/adapter/hono/_adapter.ts
Normal file
298
src/server/common/adapter/hono/_adapter.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
import type { Context, Env, Hono, Next, Schema } from "hono";
|
||||
|
||||
import type { ServeStaticOptions } from "hono/serve-static";
|
||||
import { bodyLimit as bodyLimitMid } from "hono/body-limit";
|
||||
import { cors } from "hono/cors";
|
||||
import type { RedirectStatusCode, StatusCode } from "hono/utils/http-status";
|
||||
|
||||
import { RequestMethod } from "@nestjs/common";
|
||||
import type { ErrorHandler, RequestHandler } from "@nestjs/common/interfaces";
|
||||
import { AbstractHttpAdapter } from "@nestjs/core/adapters/http-adapter";
|
||||
|
||||
import type { BlankEnv, BlankSchema, MiddlewareHandler } from "hono/types";
|
||||
import type { NestHandler, NestHttpServerRequired } from "./_nest";
|
||||
import { createHonoReq, createHonoRes, InternalHonoReq, InternalHonoRes, sendResult } from "./_util";
|
||||
import type { HonoApplicationExtra, HonoBodyParser } from "./hono.impl";
|
||||
import type { CorsOptions, CorsOptionsDelegate } from "@nestjs/common/interfaces/external/cors-options.interface";
|
||||
|
||||
export type CORSOptions = Partial<NonNullable<Parameters<typeof cors>[0]>>;
|
||||
|
||||
const NEST_HEADERS = Symbol("nest_headers");
|
||||
|
||||
type NestHonoHandler = NestHandler<InternalHonoReq, InternalHonoRes>;
|
||||
|
||||
export interface HonoRouterAdapter<
|
||||
E extends Env = BlankEnv,
|
||||
S extends Schema = BlankSchema,
|
||||
BasePath extends string = "/",
|
||||
> extends AbstractHttpAdapter<NestHttpServerRequired, InternalHonoReq, InternalHonoRes> {
|
||||
getInstance(): Hono<E, S, BasePath>;
|
||||
getInstance<T = any>(): T;
|
||||
getHeader(response: any, name: string): any;
|
||||
appendHeader(response: any, name: string, value: string): any;
|
||||
}
|
||||
|
||||
export abstract class HonoRouterAdapter
|
||||
extends AbstractHttpAdapter<NestHttpServerRequired, InternalHonoReq, InternalHonoRes>
|
||||
implements HonoApplicationExtra {
|
||||
declare protected readonly instance: Hono;
|
||||
declare protected httpServer: NestHttpServerRequired;
|
||||
|
||||
private createRouteHandler(routeHandler: NestHonoHandler) {
|
||||
return async (ctx: Context, next?: Next): Promise<Response> => {
|
||||
const nestHeaders: Record<string, string> = {};
|
||||
Reflect.set(ctx, NEST_HEADERS, nestHeaders);
|
||||
let body: any;
|
||||
const contentType = ctx.req.header("Content-Type");
|
||||
if (contentType) {
|
||||
const parser = this.#bodyParsers.get(contentType);
|
||||
if (parser) body = await parser(ctx.req);
|
||||
}
|
||||
await routeHandler(
|
||||
createHonoReq(ctx, { body, params: ctx.req.param(), rawBody: undefined }),
|
||||
createHonoRes(ctx),
|
||||
next,
|
||||
);
|
||||
return sendResult(ctx, nestHeaders);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Router
|
||||
*/
|
||||
override all(handler: NestHonoHandler): void;
|
||||
override all(path: string, handler: NestHonoHandler): void;
|
||||
override all(pathOrHandler: string | NestHonoHandler, handler?: NestHonoHandler) {
|
||||
const [routePath, routeHandler] = getRouteAndHandler(pathOrHandler, handler);
|
||||
this.instance.all(routePath, this.createRouteHandler(routeHandler));
|
||||
}
|
||||
override get(pathOrHandler: string | NestHonoHandler, handler?: NestHonoHandler) {
|
||||
const [routePath, routeHandler] = getRouteAndHandler(pathOrHandler, handler);
|
||||
this.instance.get(routePath, this.createRouteHandler(routeHandler));
|
||||
}
|
||||
override post(pathOrHandler: string | NestHonoHandler, handler?: NestHonoHandler) {
|
||||
const [routePath, routeHandler] = getRouteAndHandler(pathOrHandler, handler);
|
||||
this.instance.post(routePath, this.createRouteHandler(routeHandler));
|
||||
}
|
||||
override put(pathOrHandler: string | NestHonoHandler, handler?: NestHonoHandler) {
|
||||
const [routePath, routeHandler] = getRouteAndHandler(pathOrHandler, handler);
|
||||
this.instance.put(routePath, this.createRouteHandler(routeHandler));
|
||||
}
|
||||
override delete(pathOrHandler: string | NestHonoHandler, handler?: NestHonoHandler) {
|
||||
const [routePath, routeHandler] = getRouteAndHandler(pathOrHandler, handler);
|
||||
this.instance.delete(routePath, this.createRouteHandler(routeHandler));
|
||||
}
|
||||
override patch(pathOrHandler: string | NestHonoHandler, handler?: NestHonoHandler) {
|
||||
const [routePath, routeHandler] = getRouteAndHandler(pathOrHandler, handler);
|
||||
this.instance.patch(routePath, this.createRouteHandler(routeHandler));
|
||||
}
|
||||
override options(pathOrHandler: string | NestHonoHandler, handler?: NestHonoHandler) {
|
||||
const [routePath, routeHandler] = getRouteAndHandler(pathOrHandler, handler);
|
||||
this.instance.options(routePath, this.createRouteHandler(routeHandler));
|
||||
}
|
||||
override use(...handler: [string | MiddlewareHandler, ...MiddlewareHandler[]]): void {
|
||||
//@ts-ignore
|
||||
this.instance.use(...handler);
|
||||
}
|
||||
|
||||
useBodyLimit(bodyLimit: number) {
|
||||
this.instance.use(
|
||||
bodyLimitMid({
|
||||
maxSize: bodyLimit,
|
||||
onError: () => {
|
||||
throw new Error("Body too large");
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#bodyParsers: Map<string | undefined, HonoBodyParser> = new Map();
|
||||
useBodyParser(contentType: string, parser: HonoBodyParser): void;
|
||||
/**
|
||||
* @param rawBody When NestApplicationOptions.bodyParser is set to true, rawBody will be true
|
||||
*/
|
||||
useBodyParser(contentType: string, rawBody?: boolean | HonoBodyParser, parser?: HonoBodyParser) {
|
||||
if (typeof rawBody === "function") {
|
||||
parser = rawBody;
|
||||
} else if (typeof parser !== "function") {
|
||||
return;
|
||||
}
|
||||
this.#bodyParsers.set(contentType, parser);
|
||||
}
|
||||
|
||||
//implement
|
||||
// useStaticAssets(path: string, options: ServeStaticOptions): never {
|
||||
// throw new Error("Method useStaticAssets not implemented."); //TODO useStaticAssets
|
||||
// }
|
||||
async useStaticAssets(path: string, options: ServeStaticOptions) {
|
||||
// this.Logger.log('Registering static assets middleware');
|
||||
if ((process as any).versions?.bun && import.meta.env.PROD) {
|
||||
const { serveStatic } = await import("hono/bun");
|
||||
// app.use(serveStatic({ root: "./dist/client" }))
|
||||
// this.instance.use(serveStatic({ root: "./dist/client" }))
|
||||
this.instance.use(path, serveStatic(options));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
//implement
|
||||
setViewEngine(options: any | string): never {
|
||||
throw new Error("Method setViewEngine not implemented."); //TODO setViewEngine
|
||||
}
|
||||
//implement
|
||||
getRequestHostname(request: InternalHonoReq): string {
|
||||
return request.req.header("Host") ?? "";
|
||||
}
|
||||
//implement
|
||||
getRequestMethod(request: InternalHonoReq): string {
|
||||
return request.req.method;
|
||||
}
|
||||
//implement
|
||||
getRequestUrl(request: InternalHonoReq): string {
|
||||
return request.req.url;
|
||||
}
|
||||
// implement
|
||||
status(res: InternalHonoRes, statusCode: StatusCode) {
|
||||
res.status(statusCode);
|
||||
}
|
||||
//implement
|
||||
/**
|
||||
* 回复数据
|
||||
*/
|
||||
reply(res: InternalHonoRes, body: any, statusCode?: StatusCode) {
|
||||
if (statusCode) res.status(statusCode);
|
||||
res.send(body);
|
||||
}
|
||||
/**
|
||||
* implement
|
||||
* 没有响应数据时,用于结束http响应
|
||||
*/
|
||||
end(res: InternalHonoRes, message?: string) {
|
||||
res.send(message ?? null);
|
||||
}
|
||||
//implement
|
||||
render(res: InternalHonoRes, view: string | Promise<string>, options: any) {
|
||||
res.send(res.render(view));
|
||||
}
|
||||
|
||||
//implement
|
||||
redirect(res: InternalHonoRes, statusCode: RedirectStatusCode, url: string) {
|
||||
res.send(res.redirect(url, statusCode));
|
||||
}
|
||||
//implement
|
||||
setErrorHandler(handler: ErrorHandler<InternalHonoReq, InternalHonoRes>) {
|
||||
this.instance.onError(async (err: Error, ctx: Context) => {
|
||||
await handler(err, createHonoReq(ctx, { body: {}, params: {}, rawBody: undefined }), createHonoRes(ctx));
|
||||
return sendResult(ctx, {});
|
||||
});
|
||||
}
|
||||
//implement
|
||||
setNotFoundHandler(handler: RequestHandler<InternalHonoReq, InternalHonoRes>) {
|
||||
this.instance.notFound(async (ctx: Context) => {
|
||||
await handler(createHonoReq(ctx, { body: {}, params: {}, rawBody: undefined }), createHonoRes(ctx));
|
||||
return sendResult(ctx, {});
|
||||
});
|
||||
}
|
||||
|
||||
//implement
|
||||
isHeadersSent(res: InternalHonoRes): boolean {
|
||||
return res.finalized;
|
||||
}
|
||||
//implement
|
||||
setHeader(res: InternalHonoRes, name: string, value: string) {
|
||||
Reflect.get(res, NEST_HEADERS)[name] = value.toLowerCase();
|
||||
}
|
||||
|
||||
//implement
|
||||
getType(): string {
|
||||
return "hono";
|
||||
}
|
||||
#isParserRegistered: boolean = false;
|
||||
// implement nest 初始化时设定的一些默认解析器
|
||||
registerParserMiddleware(prefix: string = "", rawBody?: boolean) {
|
||||
if (this.#isParserRegistered) return;
|
||||
|
||||
this.useBodyParser("application/x-www-form-urlencoded", (honoReq) => honoReq.parseBody());
|
||||
this.useBodyParser("multipart/form-data", (honoReq) => honoReq.parseBody());
|
||||
this.useBodyParser("application/json", (honoReq) => honoReq.json());
|
||||
this.useBodyParser("text/plain", (honoReq) => honoReq.text());
|
||||
|
||||
this.#isParserRegistered = true;
|
||||
}
|
||||
|
||||
//implement
|
||||
override enableCors(options?: CORSOptions): void;
|
||||
override enableCors(options: any): void;
|
||||
override enableCors(options?: any) {
|
||||
this.instance.use(cors(options));
|
||||
}
|
||||
//implement
|
||||
createMiddlewareFactory(requestMethod: RequestMethod): (path: string, callback: Function) => any {
|
||||
return (path: string, callback: Function) => {
|
||||
const nestMiddleware = callback as NestMiddlewareHandler;
|
||||
|
||||
async function handler(ctx: Context, next: () => Promise<void>) {
|
||||
// let calledNext = false;
|
||||
// await nestMiddleware(ctx, ctx, () => {
|
||||
// calledNext = true;
|
||||
// });
|
||||
// if (calledNext) return next();
|
||||
await nestMiddleware(
|
||||
ctx.req,
|
||||
ctx,
|
||||
// createHonoReq(ctx, { body: {}, params: {}, rawBody: undefined }),
|
||||
// createHonoRes(ctx),
|
||||
next
|
||||
);
|
||||
}
|
||||
if (requestMethod === RequestMethod.ALL || (requestMethod as number) === -1) {
|
||||
this.instance.use(path, handler);
|
||||
} else {
|
||||
const method = RequestMethod[requestMethod];
|
||||
this.instance.on(method, path, handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//implement
|
||||
applyVersionFilter(): () => () => any {
|
||||
throw new Error("Versioning not yet supported in Hono"); //TODO applyVersionFilter
|
||||
}
|
||||
//@ts-ignore nest 10 implement
|
||||
override getHeader = undefined;
|
||||
//@ts-ignore nest 10 implement
|
||||
override appendHeader = undefined;
|
||||
}
|
||||
type NestMiddlewareHandler = (req: Context['req'], res: Context, next: Next) => Promise<void>;
|
||||
function getRouteAndHandler(
|
||||
pathOrHandler: string | NestHonoHandler,
|
||||
handler?: NestHonoHandler,
|
||||
): [string, NestHonoHandler] {
|
||||
let path: string;
|
||||
if (typeof pathOrHandler === "function") {
|
||||
handler = pathOrHandler;
|
||||
path = "";
|
||||
} else {
|
||||
path = pathOrHandler;
|
||||
handler = handler!;
|
||||
}
|
||||
return [path, handler];
|
||||
}
|
||||
|
||||
function transformsNestCrosOption(options: CorsOptions | CorsOptionsDelegate<InternalHonoReq> = {}): CORSOptions {
|
||||
if (typeof options === "function") throw new Error("options must be an object");
|
||||
let { origin } = options;
|
||||
if (typeof origin === "function") origin = undefined; //TODO
|
||||
return {
|
||||
//@ts-ignore 需要转换
|
||||
origin, //TODO
|
||||
allowHeaders: toArray(options.allowedHeaders),
|
||||
allowMethods: toArray(options.methods),
|
||||
exposeHeaders: toArray(options.exposedHeaders),
|
||||
credentials: options.credentials,
|
||||
maxAge: options.maxAge,
|
||||
};
|
||||
}
|
||||
function toArray<T>(item?: T | T[]): T[] | undefined {
|
||||
if (!item) return undefined;
|
||||
return item instanceof Array ? item : [item];
|
||||
}
|
||||
28
src/server/common/adapter/hono/_nest.ts
Normal file
28
src/server/common/adapter/hono/_nest.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { RequestHandler } from "@nestjs/common/interfaces";
|
||||
import type { Server } from "node:http";
|
||||
|
||||
//see nest project: packages\core\router\route-params-factory.ts
|
||||
export interface NestReqRequired {
|
||||
rawBody?: any;
|
||||
session?: any;
|
||||
files?: any;
|
||||
body: Record<string, any>;
|
||||
params: Record<string, string | undefined>;
|
||||
ip: string;
|
||||
hosts?: Record<string, string | undefined>;
|
||||
query: Record<string, string | undefined>;
|
||||
headers: Record<string, string | undefined>;
|
||||
}
|
||||
|
||||
export type NestHandler<Req extends NestReqRequired, Res extends object> = RequestHandler<Req, Res>;
|
||||
|
||||
//see nest project: packages/core/nest-application.ts
|
||||
export interface NestHttpServerRequired {
|
||||
once(event: "error", listener: (err: any) => void): this;
|
||||
removeListener(event: "error", listener: (...args: any[]) => void): this;
|
||||
// 必须返回一个对象或者字符串,否则 NestApplication.listen() 返回的 Promise 将用于不会解决
|
||||
address(): ReturnType<Server["address"]>;
|
||||
|
||||
listen?(port: number, ...args: any[]): void;
|
||||
close?(callback: (err?: any) => void): void;
|
||||
}
|
||||
154
src/server/common/adapter/hono/_util.ts
Normal file
154
src/server/common/adapter/hono/_util.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import type { NestHttpServerRequired, NestReqRequired } from "./_nest.ts";
|
||||
import type { Context } from "hono";
|
||||
|
||||
export function createHonoReq(
|
||||
ctx: Context,
|
||||
info: {
|
||||
body: Record<string, any>;
|
||||
params: Record<string, string>;
|
||||
rawBody: any;
|
||||
},
|
||||
): InternalHonoReq {
|
||||
const { params, rawBody } = info;
|
||||
let body = info.body ?? {};
|
||||
const honoReq = ctx as InternalHonoReq;
|
||||
if (Object.hasOwn(honoReq, IS_HONO_REQUEST)) return honoReq;
|
||||
const nestReq: Omit<NestReqRequired, "headers" | "query" | "body" | "ip"> = {
|
||||
rawBody,
|
||||
session: ctx.get("session") ?? {},
|
||||
//hosts: {}, //nest sets up hosts automatically
|
||||
files: {}, //TODO files
|
||||
params: params,
|
||||
};
|
||||
Object.assign(honoReq, nestReq);
|
||||
|
||||
let ip: string | undefined;
|
||||
let headers: Record<string, any> | undefined;
|
||||
let query: Record<string, any> | undefined;
|
||||
|
||||
Object.defineProperties(honoReq, {
|
||||
headers: {
|
||||
get(this: Context) {
|
||||
return headers ?? (headers = Object.fromEntries(this.req.raw.headers as any));
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
query: {
|
||||
get(this: Context) {
|
||||
return query ?? (query = this.req.query());
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
// Hono Context already has a body attribute, so we need to use a Proxy to override it
|
||||
body: {
|
||||
value: new Proxy(ctx.body, {
|
||||
get(rawBody, key: string) {
|
||||
return body[key];
|
||||
},
|
||||
ownKeys(rawBody) {
|
||||
return Object.keys(body);
|
||||
},
|
||||
apply(rawBody, thisArg, args) {
|
||||
return rawBody.apply(ctx, args as any);
|
||||
},
|
||||
}),
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
ip: {
|
||||
get() {
|
||||
return "";
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
[IS_HONO_REQUEST]: {
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
|
||||
return honoReq;
|
||||
}
|
||||
const IS_HONO_REQUEST = Symbol("Is Hono Request");
|
||||
|
||||
function mountResponse(ctx: Context, data: any) {
|
||||
Reflect.set(ctx, NEST_BODY, data);
|
||||
}
|
||||
|
||||
export function createHonoRes(context: Context): InternalHonoRes {
|
||||
Reflect.set(context, "send", function (this: Context, response: any = this.newResponse(null)) {
|
||||
mountResponse(this, response);
|
||||
});
|
||||
return context as any as InternalHonoRes;
|
||||
}
|
||||
export function sendResult(ctx: Context, headers: Record<string, string>) {
|
||||
const body = Reflect.get(ctx, NEST_BODY);
|
||||
let response: Response;
|
||||
switch (typeof body) {
|
||||
case "string": {
|
||||
response = ctx.text(body);
|
||||
break;
|
||||
}
|
||||
case "object": {
|
||||
if (body === null) response = ctx.body(null);
|
||||
else if (body instanceof Response) response = body;
|
||||
else if (body instanceof ReadableStream) response = ctx.body(body);
|
||||
else if (body instanceof Uint8Array) response = ctx.body(body as any);
|
||||
else if (body instanceof Blob) response = ctx.body(body.stream());
|
||||
else response = ctx.json(body);
|
||||
break;
|
||||
}
|
||||
case "undefined": {
|
||||
response = ctx.body(null);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return ctx.text("HonoAdapter cannot convert unknown types", 500);
|
||||
}
|
||||
Object.entries(headers).forEach(([key, value]) => response.headers.set(key, value));
|
||||
return response;
|
||||
}
|
||||
|
||||
export type InternalHonoReq = NestReqRequired & Context;
|
||||
|
||||
export type InternalHonoRes = Context & {
|
||||
send(data?: any): void;
|
||||
};
|
||||
|
||||
const NEST_BODY = Symbol("nest_body");
|
||||
const FakeHttpServer = Symbol("Fake HTTP Server");
|
||||
|
||||
export function isFakeHttpServer(server: any) {
|
||||
return Reflect.get(server, FakeHttpServer);
|
||||
}
|
||||
export function createNestRequiredHttpServer(): NestHttpServerRequired {
|
||||
return new Proxy(
|
||||
{
|
||||
once() {
|
||||
return this;
|
||||
},
|
||||
removeListener() {
|
||||
return this;
|
||||
},
|
||||
address() {
|
||||
return null;
|
||||
},
|
||||
then: undefined,
|
||||
[FakeHttpServer]: true,
|
||||
},
|
||||
{
|
||||
get(target, key) {
|
||||
if (typeof key === "symbol" || Object.hasOwn(target, key)) {
|
||||
//@ts-ignore
|
||||
return target[key];
|
||||
}
|
||||
console.trace("Nest Adapter: Nest uses undefined httpServer property", key);
|
||||
const value = function () {
|
||||
throw new Error(`Nest call undefined httpServer property '${String(key)}'`);
|
||||
};
|
||||
//@ts-ignore
|
||||
target[key] = value;
|
||||
return value;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
18
src/server/common/adapter/hono/hono.impl.ts
Normal file
18
src/server/common/adapter/hono/hono.impl.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { HonoRequest } from "hono/request";
|
||||
import type { ServeStaticOptions } from "hono/serve-static";
|
||||
|
||||
export type { InternalHonoRes as HonoResponse } from "./_util.ts";
|
||||
export type { HonoRequest } from "hono/request";
|
||||
|
||||
export interface HonoApplicationExtra {
|
||||
useBodyParser(contentType: string, parser: HonoBodyParser): void;
|
||||
|
||||
/**
|
||||
* Sets a base directory for public assets.
|
||||
* Example `app.useStaticAssets('public', { root: '/' })`
|
||||
* @returns {this}
|
||||
*/
|
||||
useStaticAssets(path: string, options: ServeStaticOptions): Promise<this>;
|
||||
}
|
||||
|
||||
export type HonoBodyParser = (context: HonoRequest) => Promise<any> | any;
|
||||
3
src/server/common/adapter/hono/index.ts
Normal file
3
src/server/common/adapter/hono/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./hono.impl";
|
||||
export * from "./HonoApplication";
|
||||
export * from "./HonoAdapter";
|
||||
71
src/server/common/constant/messages.ts
Normal file
71
src/server/common/constant/messages.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
export const MSG = {
|
||||
OBJECT: {
|
||||
BILL: 'Bill',
|
||||
COURSE: 'Course',
|
||||
COMBO: 'Combo',
|
||||
UNIT: 'Unit',
|
||||
RESOURCE: 'Resource',
|
||||
NOTI: 'Noti',
|
||||
OBJECT: '[OBJECT]',
|
||||
},
|
||||
FRONTEND: {
|
||||
DELETE_SUCCESS: 'Deleted successfully!',
|
||||
UPDATE_SUCCESS: 'Updated successfully!',
|
||||
|
||||
USERNAME_DUPLICATED: 'Username has been used',
|
||||
USERNAME_NOT_EXIST: 'Username not exists',
|
||||
OTP_NOT_EXIST: 'Otp not exists',
|
||||
USERNAME_INVALID: 'Username not valid format',
|
||||
EMAIL_DUPLICATED: 'Duplicate email',
|
||||
EMAIL_NOT_EXIST: 'Email not found',
|
||||
EMAIL_INACTIVE_DUPLICATED: 'Email duplicate',
|
||||
ACCOUNT_INACTIVE: 'Email not active',
|
||||
WRONG_PASSWORD: 'Wrong password',
|
||||
DOUPLICATE_PASSWORD: 'The same password',
|
||||
TOO_MANY_REQUEST: 'Too many request',
|
||||
ID_NOTFOUND: 'Id not found',
|
||||
NO_PERMISSION: 'You might have not permission to do this action',
|
||||
OBJECT_NOT_FOUND: '[OBJECT] does not exist!',
|
||||
AUTH: {
|
||||
UN_AUTH: 'unauthorized!',
|
||||
LOGIN_SUCCESS: 'Đăng nhập thành công!',
|
||||
LOGIN_ERR: 'Email hoặc mật khẩu không đúng!',
|
||||
// AUTH_FAILED: 'Invalid login info!',
|
||||
AUTH_FAILED: 'Thông tin đăng nhập không chính xác!',
|
||||
AUTH_FAILED_EMAIL_NOT_EXIST: 'Not found Email!',
|
||||
AUTH_FAILED_WRONG_PASSWORD: 'Wrong password!',
|
||||
},
|
||||
ACTIVE_USER: {
|
||||
ERROR: 'Internal error',
|
||||
TOKEN_INVALID: 'Token invalid',
|
||||
EMAIL_VERIFIED: 'Email have been verify',
|
||||
EMAIL_VERIFY_SUCCESS: 'Success verify email',
|
||||
SUCCESS: 'Account success verify',
|
||||
EXPIRY: 'Expiry account',
|
||||
},
|
||||
},
|
||||
RESPONSE: {
|
||||
SUCCESS: 'Thành công!',
|
||||
GET_REQUEST_OK: 'GET OK',
|
||||
POST_REQUEST_OK: 'POST OK',
|
||||
PUT_REQUEST_OK: 'Cập nhật thành công!',
|
||||
PATCH_REQUEST_OK: 'PATCH OK',
|
||||
DELETE_REQUEST_OK: 'DELETE OK',
|
||||
CREATED: 'Đã tạo thành công!',
|
||||
REQUEST_GET_FAIL: 'GET OK',
|
||||
POST_REQUEST_FAIL: 'POST FAIL',
|
||||
PUT_REQUEST_FAIL: 'PUT FAIL',
|
||||
PATCH_REQUEST_FAIL: 'PATCH FAIL',
|
||||
DELETE_REQUEST_FAIL: 'DELETE FAIL',
|
||||
DUPLICATED: 'DUPLICATED',
|
||||
PROCESSING: 'Processing request',
|
||||
BAD_REQUEST: 'Bad request',
|
||||
INTERNAL_SERVER_ERROR: 'Internal Server Error',
|
||||
METHOD_NOT_ALLOWED: 'Method Not Allowed Error',
|
||||
DELETE_BILL_ERROR: 'Không thể xoá hoá đơn đã thanh toán!',
|
||||
},
|
||||
UPLOAD: {
|
||||
IMAGE_FILE_TYPE_ONLY: 'Forbidden image type',
|
||||
DOCUMENT_FILE_TYPE_ONLY: 'Forbidden file type',
|
||||
},
|
||||
};
|
||||
105
src/server/common/filter/http-exception.filter.ts
Normal file
105
src/server/common/filter/http-exception.filter.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import * as path from 'path';
|
||||
import { MSG } from '../constant/messages';
|
||||
import { Context } from 'hono';
|
||||
import { HttpAdapterHost } from '@nestjs/core';
|
||||
import { sendResult } from '../adapter/hono/_util';
|
||||
import { HonoResponse } from '../adapter/hono';
|
||||
|
||||
@Catch()
|
||||
export class HttpExceptionFilter implements ExceptionFilter {
|
||||
private readonly logger = new Logger(HttpExceptionFilter.name);
|
||||
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
|
||||
private convertStack(stack: string): string {
|
||||
return stack
|
||||
.split('\n')
|
||||
.map((row) => row.trim())[0];
|
||||
}
|
||||
|
||||
async catch(exception: any, host: ArgumentsHost) {
|
||||
// console.log(host.getArgs());
|
||||
this.logger.error(this.convertStack(exception.stack));
|
||||
// this.logger.error(JSON.stringify(exception, null, 2));
|
||||
const ctx = host.switchToHttp().getResponse<HonoResponse>();
|
||||
// const request = ctx.getRequest<Request>();
|
||||
// const response = ctx.getResponse();
|
||||
let statusCode: any = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
let message: any = MSG.RESPONSE.INTERNAL_SERVER_ERROR;
|
||||
if (exception instanceof HttpException) {
|
||||
[statusCode, message] = this.handleHttpException(exception);
|
||||
}
|
||||
if (
|
||||
String(exception.name) === 'QueryFailedError' &&
|
||||
exception.errno === 1062
|
||||
) {
|
||||
[statusCode, message] = this.handleConflict();
|
||||
}
|
||||
if (String(exception.name) === 'ValidationError') {
|
||||
[statusCode, message] = this.handleValidatorError(exception);
|
||||
}
|
||||
if (String(exception.name) === 'BadRequestException') {
|
||||
[statusCode, message] = this.handleValidatorError(exception);
|
||||
}
|
||||
if (String(exception.name) === 'UnauthorizedException') {
|
||||
[statusCode, message] = this.handleAuthError(exception);
|
||||
}
|
||||
|
||||
// const user = request.user as User;
|
||||
const time = new Date().toLocaleString();
|
||||
const routePath = `${ctx.req.method} ${ctx.req.url}`;
|
||||
const errorObject = {
|
||||
error: {
|
||||
time,
|
||||
message,
|
||||
path: routePath,
|
||||
detail: {
|
||||
stack: this.convertStack(exception.stack),
|
||||
},
|
||||
},
|
||||
exception: exception.code || exception.name,
|
||||
statusCode,
|
||||
};
|
||||
// sendResult(ctx, {});
|
||||
ctx.status(statusCode);
|
||||
ctx.send(errorObject);
|
||||
// mountResponse
|
||||
// httpAdapter.reply(ctx, errorObject, statusCode);
|
||||
// return ctx.json(errorObject);
|
||||
// ctx.set("__nest_response", ctx.json(errorObject, statusCode));
|
||||
// return response.status(500).json({
|
||||
// statusCode: 500,
|
||||
// message: "Internal server error",
|
||||
// });
|
||||
}
|
||||
|
||||
handleHttpException(exception: any) {
|
||||
const statusCode = Number(exception.getStatus());
|
||||
const message = exception.response.error;
|
||||
return [statusCode, message];
|
||||
}
|
||||
|
||||
handleAuthError(exception: any) {
|
||||
const statusCode = Number(exception.getStatus());
|
||||
const message = exception.response.message;
|
||||
return [statusCode, message];
|
||||
}
|
||||
|
||||
handleValidatorError(exception: any) {
|
||||
const statusCode = HttpStatus.BAD_REQUEST;
|
||||
const message = exception.response.message[0].constraints;
|
||||
return [statusCode, message];
|
||||
}
|
||||
|
||||
handleConflict() {
|
||||
const statusCode = HttpStatus.CONFLICT;
|
||||
const message = `${MSG.FRONTEND.USERNAME_DUPLICATED} OR ${MSG.FRONTEND.EMAIL_DUPLICATED}`;
|
||||
return [statusCode, message];
|
||||
}
|
||||
}
|
||||
24
src/server/common/interceptor/transform.interceptor.ts
Normal file
24
src/server/common/interceptor/transform.interceptor.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class TransformInterceptor<T>
|
||||
implements NestInterceptor<T, any>
|
||||
{
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler,
|
||||
) {
|
||||
return next.handle().pipe(
|
||||
map((data) => ({
|
||||
success: true,
|
||||
data,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
14
src/server/common/pipes/CustomZodValidation.pipe.ts
Normal file
14
src/server/common/pipes/CustomZodValidation.pipe.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { ZodValidationPipe } from 'nestjs-zod';
|
||||
import { ZodError } from 'zod';
|
||||
import { MSG } from '../constant/messages';
|
||||
|
||||
@Injectable()
|
||||
export class CustomZodValidationPipe extends ZodValidationPipe {
|
||||
protected exceptionFactory(error: ZodError) {
|
||||
return new BadRequestException(
|
||||
error,
|
||||
MSG.RESPONSE.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
}
|
||||
13
src/server/modules/auth/auth.controller.ts
Normal file
13
src/server/modules/auth/auth.controller.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor() {}
|
||||
|
||||
@Get('/')
|
||||
index() {
|
||||
return { message: 'Auth Controller is working' };
|
||||
// throw new Error('Not implemented');
|
||||
// return 'Hello Auth';
|
||||
}
|
||||
}
|
||||
16
src/server/modules/auth/auth.module.ts
Normal file
16
src/server/modules/auth/auth.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// hono-di:imports
|
||||
],
|
||||
controllers: [
|
||||
AuthController, // hono-di:controllers
|
||||
],
|
||||
providers: [
|
||||
AuthService, // hono-di:providers
|
||||
],
|
||||
})
|
||||
export class AuthModule {}
|
||||
6
src/server/modules/auth/auth.service.ts
Normal file
6
src/server/modules/auth/auth.service.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ import ToastService from 'primevue/toastservice';
|
||||
import Tooltip from 'primevue/tooltip';
|
||||
import { createSSRApp } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
import { withErrorBoundary } from '@/lib/hoc/withErrorBoundary';
|
||||
import { vueSWR } from '@/lib/swr/use-swrv';
|
||||
import createAppRouter from '@/routes';
|
||||
import { withErrorBoundary } from '@/client/lib/hoc/withErrorBoundary';
|
||||
import { vueSWR } from '@/client/lib/swr/use-swrv';
|
||||
import createAppRouter from '@/client/routes';
|
||||
const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen bg-gray-50";
|
||||
function createApp() {
|
||||
const pinia = createPinia();
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"jsxImportSource": "vue",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": ["./src/*"],
|
||||
},
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineConfig, presetAttributify, presetTypography, presetWind4, transformerCompileClass, transformerVariantGroup } from 'unocss'
|
||||
import { presetBootstrapBtn } from "./bootstrap_btn";
|
||||
import transformerClassnamesMinifier from './plugins/encodeClassTransformer'
|
||||
// import transformerClassnamesMinifier from './plugins/encodeClassTransformer'
|
||||
export default defineConfig({
|
||||
presets: [
|
||||
presetWind4() as any,
|
||||
@@ -106,9 +106,11 @@ export default defineConfig({
|
||||
],
|
||||
transformers: [transformerVariantGroup(), transformerCompileClass({
|
||||
classPrefix: "_",
|
||||
}),transformerClassnamesMinifier({
|
||||
trigger: ':m:',
|
||||
})],
|
||||
}),
|
||||
// transformerClassnamesMinifier({
|
||||
// trigger: ':m:',
|
||||
// })
|
||||
],
|
||||
preflights: [
|
||||
{
|
||||
getCSS: (context) => {
|
||||
|
||||
@@ -21,7 +21,7 @@ export default defineConfig((env) => {
|
||||
dts: true, // Generate TypeScript declaration file
|
||||
}),
|
||||
Components({
|
||||
dirs: ["src/components"],
|
||||
dirs: ["src/client/components"],
|
||||
extensions: ["vue", "tsx"],
|
||||
dts: true,
|
||||
dtsTsx: true,
|
||||
@@ -44,7 +44,7 @@ export default defineConfig((env) => {
|
||||
build: {
|
||||
outDir: "dist/client",
|
||||
rollupOptions: {
|
||||
input: { index: "/src/client.ts" },
|
||||
input: { index: "/src/client/index.ts" },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user