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/tiny-rpc": "^0.2.3-pre.18",
|
||||||
"@hiogawa/utils": "^1.7.0",
|
"@hiogawa/utils": "^1.7.0",
|
||||||
"@hono-di/cli": "^0.0.15",
|
"@hono-di/cli": "^0.0.15",
|
||||||
"@hono-di/core": "^0.0.15",
|
|
||||||
"@nestjs/common": "^11.1.11",
|
"@nestjs/common": "^11.1.11",
|
||||||
"@nestjs/core": "^11.1.11",
|
"@nestjs/core": "^11.1.11",
|
||||||
"@primeuix/themes": "^2.0.2",
|
"@primeuix/themes": "^2.0.2",
|
||||||
@@ -21,6 +20,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"hono": "^4.11.3",
|
"hono": "^4.11.3",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
|
"nestjs-zod": "^5.1.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"primevue": "^4.5.4",
|
"primevue": "^4.5.4",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
@@ -640,6 +640,8 @@
|
|||||||
|
|
||||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
"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=="],
|
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
|
||||||
|
|
||||||
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
|
"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=="],
|
"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-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||||
|
|
||||||
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
|
"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 */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
Add: typeof import('./src/components/icons/Add.vue')['default']
|
Add: typeof import('./src/client/components/icons/Add.vue')['default']
|
||||||
Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
Bell: typeof import('./src/client/components/icons/Bell.vue')['default']
|
||||||
Button: typeof import('primevue/button')['default']
|
Button: typeof import('primevue/button')['default']
|
||||||
Checkbox: typeof import('primevue/checkbox')['default']
|
Checkbox: typeof import('primevue/checkbox')['default']
|
||||||
CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
CheckIcon: typeof import('./src/client/components/icons/CheckIcon.vue')['default']
|
||||||
Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
Credit: typeof import('./src/client/components/icons/Credit.vue')['default']
|
||||||
DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
DashboardLayout: typeof import('./src/client/components/DashboardLayout.vue')['default']
|
||||||
Home: typeof import('./src/components/icons/Home.vue')['default']
|
Home: typeof import('./src/client/components/icons/Home.vue')['default']
|
||||||
InputText: typeof import('primevue/inputtext')['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']
|
Message: typeof import('primevue/message')['default']
|
||||||
Password: typeof import('primevue/password')['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']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default']
|
TestIcon: typeof import('./src/client/components/icons/TestIcon.vue')['default']
|
||||||
Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
Upload: typeof import('./src/client/components/icons/Upload.vue')['default']
|
||||||
Video: typeof import('./src/components/icons/Video.vue')['default']
|
Video: typeof import('./src/client/components/icons/Video.vue')['default']
|
||||||
VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
VueHead: typeof import('./src/client/components/VueHead.tsx')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For TSX support
|
// For TSX support
|
||||||
declare global {
|
declare global {
|
||||||
const Add: typeof import('./src/components/icons/Add.vue')['default']
|
const Add: typeof import('./src/client/components/icons/Add.vue')['default']
|
||||||
const Bell: typeof import('./src/components/icons/Bell.vue')['default']
|
const Bell: typeof import('./src/client/components/icons/Bell.vue')['default']
|
||||||
const Button: typeof import('primevue/button')['default']
|
const Button: typeof import('primevue/button')['default']
|
||||||
const Checkbox: typeof import('primevue/checkbox')['default']
|
const Checkbox: typeof import('primevue/checkbox')['default']
|
||||||
const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default']
|
const CheckIcon: typeof import('./src/client/components/icons/CheckIcon.vue')['default']
|
||||||
const Credit: typeof import('./src/components/icons/Credit.vue')['default']
|
const Credit: typeof import('./src/client/components/icons/Credit.vue')['default']
|
||||||
const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default']
|
const DashboardLayout: typeof import('./src/client/components/DashboardLayout.vue')['default']
|
||||||
const Home: typeof import('./src/components/icons/Home.vue')['default']
|
const Home: typeof import('./src/client/components/icons/Home.vue')['default']
|
||||||
const InputText: typeof import('primevue/inputtext')['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 Message: typeof import('primevue/message')['default']
|
||||||
const Password: typeof import('primevue/password')['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 RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
const RouterView: typeof import('vue-router')['RouterView']
|
const RouterView: typeof import('vue-router')['RouterView']
|
||||||
const TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default']
|
const TestIcon: typeof import('./src/client/components/icons/TestIcon.vue')['default']
|
||||||
const Upload: typeof import('./src/components/icons/Upload.vue')['default']
|
const Upload: typeof import('./src/client/components/icons/Upload.vue')['default']
|
||||||
const Video: typeof import('./src/components/icons/Video.vue')['default']
|
const Video: typeof import('./src/client/components/icons/Video.vue')['default']
|
||||||
const VueHead: typeof import('./src/components/VueHead.tsx')['default']
|
const VueHead: typeof import('./src/client/components/VueHead.tsx')['default']
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
"@hiogawa/tiny-rpc": "^0.2.3-pre.18",
|
||||||
"@hiogawa/utils": "^1.7.0",
|
"@hiogawa/utils": "^1.7.0",
|
||||||
"@hono-di/cli": "^0.0.15",
|
"@hono-di/cli": "^0.0.15",
|
||||||
"@hono-di/core": "^0.0.15",
|
|
||||||
"@nestjs/common": "^11.1.11",
|
"@nestjs/common": "^11.1.11",
|
||||||
"@nestjs/core": "^11.1.11",
|
"@nestjs/core": "^11.1.11",
|
||||||
"@primeuix/themes": "^2.0.2",
|
"@primeuix/themes": "^2.0.2",
|
||||||
@@ -23,6 +22,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"hono": "^4.11.3",
|
"hono": "^4.11.3",
|
||||||
"is-mobile": "^5.0.0",
|
"is-mobile": "^5.0.0",
|
||||||
|
"nestjs-zod": "^5.1.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"primevue": "^4.5.4",
|
"primevue": "^4.5.4",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
|
|||||||
@@ -115,8 +115,8 @@ export default function ssrPlugin(): Plugin[] {
|
|||||||
return path.resolve(
|
return path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
options?.ssr
|
options?.ssr
|
||||||
? pwd+"/src/api/httpClientAdapter.server.ts"
|
? pwd+"/src/client/api/httpClientAdapter.server.ts"
|
||||||
: pwd+"/src/api/httpClientAdapter.client.ts"
|
: pwd+"/src/client/api/httpClientAdapter.client.ts"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
async configResolved(config) {
|
async configResolved(config) {
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Add from "@/components/icons/Add.vue";
|
import Home from "@/client/components/icons/Home.vue";
|
||||||
import Bell from "@/components/icons/Bell.vue";
|
import Video from "@/client/components/icons/Video.vue";
|
||||||
import Home from "@/components/icons/Home.vue";
|
import Credit from "@/client/components/icons/Credit.vue";
|
||||||
import Video from "@/components/icons/Video.vue";
|
|
||||||
import Credit from "@/components/icons/Credit.vue";
|
|
||||||
import Upload from "./icons/Upload.vue";
|
import Upload from "./icons/Upload.vue";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/client/lib/utils";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/client/stores/auth";
|
||||||
import { createStaticVNode } from "vue";
|
import { createStaticVNode } from "vue";
|
||||||
|
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'uno.css';
|
import 'uno.css';
|
||||||
import createVueApp from './shared/createVueApp';
|
import createVueApp from '@/shared/createVueApp';
|
||||||
async function render() {
|
async function render() {
|
||||||
const { app, router } = createVueApp();
|
const { app, router } = createVueApp();
|
||||||
router.isReady().then(() => {
|
router.isReady().then(() => {
|
||||||
@@ -84,7 +84,7 @@ export function buildBootstrapScript() {
|
|||||||
assets: [],
|
assets: [],
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
file: "src/client.ts",
|
file: "src/client/index.ts",
|
||||||
isEntry: true,
|
isEntry: true,
|
||||||
css: [],
|
css: [],
|
||||||
},
|
},
|
||||||
@@ -8,5 +8,5 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { VueHead } from "@/components/VueHead";
|
import { VueHead } from "@/client/components/VueHead";
|
||||||
</script>
|
</script>
|
||||||
@@ -74,7 +74,7 @@ import { reactive } from 'vue';
|
|||||||
import { Form, type FormSubmitEvent } from '@primevue/forms';
|
import { Form, type FormSubmitEvent } from '@primevue/forms';
|
||||||
import { zodResolver } from '@primevue/forms/resolvers/zod';
|
import { zodResolver } from '@primevue/forms/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/client/stores/auth';
|
||||||
const auth = useAuthStore();
|
const auth = useAuthStore();
|
||||||
// const $form = Form.useFormContext();
|
// const $form = Form.useFormContext();
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
createWebHistory,
|
createWebHistory,
|
||||||
type RouteRecordRaw,
|
type RouteRecordRaw,
|
||||||
} from "vue-router";
|
} from "vue-router";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/client/stores/auth";
|
||||||
|
|
||||||
type RouteData = RouteRecordRaw & {
|
type RouteData = RouteRecordRaw & {
|
||||||
meta?: ResolvableValue<ReactiveHead> & { requiresAuth?: boolean };
|
meta?: ResolvableValue<ReactiveHead> & { requiresAuth?: boolean };
|
||||||
@@ -15,7 +15,7 @@ type RouteData = RouteRecordRaw & {
|
|||||||
const routes: RouteData[] = [
|
const routes: RouteData[] = [
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
component: () => import("@/components/RootLayout.vue"),
|
component: () => import("@/client/components/RootLayout.vue"),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
@@ -76,7 +76,7 @@ const routes: RouteData[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "",
|
path: "",
|
||||||
component: () => import("@/components/DashboardLayout.vue"),
|
component: () => import("@/client/components/DashboardLayout.vue"),
|
||||||
meta: { requiresAuth: true },
|
meta: { requiresAuth: true },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Head } from '@unhead/vue/components'
|
import { Head } from '@unhead/vue/components'
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/client/lib/utils';
|
||||||
const pricing = {
|
const pricing = {
|
||||||
title: "Simple, transparent pricing",
|
title: "Simple, transparent pricing",
|
||||||
subtitle: "Choose the plan that fits your needs. No hidden fees.",
|
subtitle: "Choose the plan that fits your needs. No hidden fees.",
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { client } from '@/api/rpcclient';
|
import { client } from '@/client/api/rpcclient';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
115
src/main.ts
115
src/main.ts
@@ -1,76 +1,63 @@
|
|||||||
import { Hono } from 'hono';
|
import { NestFactory } from "@nestjs/core";
|
||||||
import { contextStorage } from 'hono/context-storage';
|
import Bun from "bun";
|
||||||
import { cors } from "hono/cors";
|
import { Hono } from "hono";
|
||||||
import isMobile from 'is-mobile';
|
import { contextStorage } from "hono/context-storage";
|
||||||
import { rpcServer } from './api/rpc';
|
import isMobile from "is-mobile";
|
||||||
import { ssrRender } from './worker/ssrRender';
|
import { AppModule } from "./server/app.module";
|
||||||
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
|
import { HonoAdapter, NestHonoApplication } from "./server/common/adapter/hono";
|
||||||
import { AppModule } from './server/app.module';
|
import { CustomZodValidationPipe } from "./server/common/pipes/CustomZodValidation.pipe";
|
||||||
import { HonoAdapter } from './server/HonoAdapter/adapter';
|
import { ssrRender } from "./server/HonoAdapter/ssrRender";
|
||||||
import { NestHonoApplication } from './server/HonoAdapter/interfaces';
|
import { TransformInterceptor } from "./server/common/interceptor/transform.interceptor";
|
||||||
// import { serveStatic } from "hono/bun";
|
let serve: Bun.Server<undefined> | any = {
|
||||||
// @ts-ignore
|
stop: async () => {},
|
||||||
// const app = new Hono()
|
}
|
||||||
// const isDev = import.meta.env.DEV;
|
const hono = new Hono();
|
||||||
|
const app = await NestFactory.create<NestHonoApplication>(
|
||||||
// // 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>(
|
|
||||||
AppModule,
|
AppModule,
|
||||||
new HonoAdapter(),
|
new HonoAdapter({
|
||||||
{
|
hono,
|
||||||
rawBody: true,
|
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) => {
|
app.setGlobalPrefix("api");
|
||||||
// console.log("Request URL:", c);
|
app.enableShutdownHooks();
|
||||||
// // return next();
|
app.useGlobalPipes(new CustomZodValidationPipe());
|
||||||
// });
|
app.useGlobalInterceptors(new TransformInterceptor());
|
||||||
// app.use(ssrRender);
|
|
||||||
// app.use(function (req, next) {
|
hono.use(async (c, next) => {
|
||||||
// console.log("Request:", arguments[1]);
|
c.set("fetch", hono.request.bind(hono));
|
||||||
// return (c, next) => {
|
const ua = c.req.header("User-Agent");
|
||||||
// next();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
const app = appHonoNest.getHonoInstance();
|
|
||||||
app.use(async (c, next) => {
|
|
||||||
c.set("fetch", app.request.bind(app));
|
|
||||||
const ua = c.req.header("User-Agent")
|
|
||||||
if (!ua) {
|
if (!ua) {
|
||||||
return c.json({ error: "User-Agent header is missing" }, 400);
|
return c.json({ error: "User-Agent header is missing" }, 400);
|
||||||
};
|
}
|
||||||
c.set("isMobile", isMobile({ ua }));
|
c.set("isMobile", isMobile({ ua }));
|
||||||
await next();
|
await next();
|
||||||
}, contextStorage());
|
}, contextStorage());
|
||||||
|
|
||||||
appHonoNest.useStaticAssets("/*", { root: "./dist/client" });
|
app.useStaticAssets("/*", { root: "./dist/client" });
|
||||||
await appHonoNest.init();
|
await app.init();
|
||||||
app.get("/.well-known/*", (c) => {
|
hono.get("/.well-known/*", (c) => {
|
||||||
return c.json({ ok: true });
|
return c.json({ ok: true });
|
||||||
});
|
});
|
||||||
app.use(ssrRender);
|
hono.use(ssrRender);
|
||||||
const httpAdapter = appHonoNest.get(HttpAdapterHost);
|
if (import.meta.env.PROD) {
|
||||||
// await app.listen(3000)
|
await app.listen(3500);
|
||||||
// console.log("HTTP Adapter:", app.fetch.toString());
|
|
||||||
export default {
|
|
||||||
fetch: app.fetch.bind(app),
|
|
||||||
}
|
}
|
||||||
|
const honoDev = import.meta.env.DEV ? hono : null;
|
||||||
|
export default honoDev;
|
||||||
|
// };
|
||||||
@@ -298,7 +298,7 @@ export class HonoAdapter extends AbstractHttpAdapter<
|
|||||||
bodyLimit?: number,
|
bodyLimit?: number,
|
||||||
) {
|
) {
|
||||||
Logger.log(
|
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) {
|
if(bodyLimit !== undefined) {
|
||||||
this.instance.use(this.bodyLimit(bodyLimit));
|
this.instance.use(this.bodyLimit(bodyLimit));
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { renderSSRHead } from "@unhead/vue/server";
|
|||||||
import { Context } from "hono";
|
import { Context } from "hono";
|
||||||
import { streamText } from "hono/streaming";
|
import { streamText } from "hono/streaming";
|
||||||
import { renderToWebStream } from "vue/server-renderer";
|
import { renderToWebStream } from "vue/server-renderer";
|
||||||
import { buildBootstrapScript } from "@/lib/manifest";
|
import { buildBootstrapScript } from "@/client/lib/manifest";
|
||||||
import { styleTags } from "@/lib/primePassthrough";
|
import { styleTags } from "@/client/lib/primePassthrough";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/client/stores/auth";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Base from "@primevue/core/base";
|
import Base from "@primevue/core/base";
|
||||||
import { BlankEnv, BlankInput } from "hono/types";
|
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 { MiddlewareConsumer, Module, NestModule } from "@nestjs/common";
|
||||||
import { AppController } from './app.controller';
|
import { APP_FILTER } from "@nestjs/core";
|
||||||
import { AppService } from './app.service';
|
import { HttpExceptionFilter } from "./common/filter/http-exception.filter";
|
||||||
import { LoggerMiddleware } from './middleware';
|
import { LoggerMiddleware } from "./middleware";
|
||||||
|
import { AuthModule } from "./modules/auth/auth.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [AuthModule],
|
||||||
// hono-di:imports
|
|
||||||
],
|
|
||||||
controllers: [
|
|
||||||
AppController, // hono-di:controllers
|
|
||||||
],
|
|
||||||
providers: [
|
providers: [
|
||||||
AppService, // hono-di:providers
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: HttpExceptionFilter,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule implements NestModule {
|
export class AppModule implements NestModule {
|
||||||
configure(consumer: MiddlewareConsumer) {
|
configure(consumer: MiddlewareConsumer) {
|
||||||
consumer
|
consumer.apply(LoggerMiddleware).forRoutes("*");
|
||||||
.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 Tooltip from 'primevue/tooltip';
|
||||||
import { createSSRApp } from 'vue';
|
import { createSSRApp } from 'vue';
|
||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
import { withErrorBoundary } from '@/lib/hoc/withErrorBoundary';
|
import { withErrorBoundary } from '@/client/lib/hoc/withErrorBoundary';
|
||||||
import { vueSWR } from '@/lib/swr/use-swrv';
|
import { vueSWR } from '@/client/lib/swr/use-swrv';
|
||||||
import createAppRouter from '@/routes';
|
import createAppRouter from '@/client/routes';
|
||||||
const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen bg-gray-50";
|
const bodyClass = ":uno: font-sans text-gray-800 antialiased flex flex-col min-h-screen bg-gray-50";
|
||||||
function createApp() {
|
function createApp() {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"jsxImportSource": "vue",
|
"jsxImportSource": "vue",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"],
|
||||||
},
|
},
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { defineConfig, presetAttributify, presetTypography, presetWind4, transformerCompileClass, transformerVariantGroup } from 'unocss'
|
import { defineConfig, presetAttributify, presetTypography, presetWind4, transformerCompileClass, transformerVariantGroup } from 'unocss'
|
||||||
import { presetBootstrapBtn } from "./bootstrap_btn";
|
import { presetBootstrapBtn } from "./bootstrap_btn";
|
||||||
import transformerClassnamesMinifier from './plugins/encodeClassTransformer'
|
// import transformerClassnamesMinifier from './plugins/encodeClassTransformer'
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
presets: [
|
presets: [
|
||||||
presetWind4() as any,
|
presetWind4() as any,
|
||||||
@@ -106,9 +106,11 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
transformers: [transformerVariantGroup(), transformerCompileClass({
|
transformers: [transformerVariantGroup(), transformerCompileClass({
|
||||||
classPrefix: "_",
|
classPrefix: "_",
|
||||||
}),transformerClassnamesMinifier({
|
}),
|
||||||
trigger: ':m:',
|
// transformerClassnamesMinifier({
|
||||||
})],
|
// trigger: ':m:',
|
||||||
|
// })
|
||||||
|
],
|
||||||
preflights: [
|
preflights: [
|
||||||
{
|
{
|
||||||
getCSS: (context) => {
|
getCSS: (context) => {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export default defineConfig((env) => {
|
|||||||
dts: true, // Generate TypeScript declaration file
|
dts: true, // Generate TypeScript declaration file
|
||||||
}),
|
}),
|
||||||
Components({
|
Components({
|
||||||
dirs: ["src/components"],
|
dirs: ["src/client/components"],
|
||||||
extensions: ["vue", "tsx"],
|
extensions: ["vue", "tsx"],
|
||||||
dts: true,
|
dts: true,
|
||||||
dtsTsx: true,
|
dtsTsx: true,
|
||||||
@@ -44,7 +44,7 @@ export default defineConfig((env) => {
|
|||||||
build: {
|
build: {
|
||||||
outDir: "dist/client",
|
outDir: "dist/client",
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: { index: "/src/client.ts" },
|
input: { index: "/src/client/index.ts" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user