Skip to content

Commit

Permalink
Merge pull request #1095 from samchon/feat/editor-module
Browse files Browse the repository at this point in the history
`NestiaEditorModule` for NestJS backend server's editor.
  • Loading branch information
samchon authored Nov 2, 2024
2 parents 807474a + fd1a189 commit fd658dd
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 20 deletions.
14 changes: 11 additions & 3 deletions packages/editor/package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "@nestia/editor",
"version": "0.6.2",
"version": "0.7.1",
"typings": "lib/index.d.ts",
"main": "lib/index.js",
"module": "lib/index.mjs",
"scripts": {
"build:lib": "tsc --project tsconfig.lib.json && rollup -c",
"build:static": "tsc -b && vite build",
"build": "npm run build:static && npm run build:lib",
"build:static": "rimraf dist && tsc -b && vite build",
"build:lib": "rimraf lib && tsc --project tsconfig.lib.json && rollup -c",
"dev": "vite",
"lint": "eslint .",
"preview": "vite preview"
Expand Down Expand Up @@ -42,9 +43,14 @@
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@nestjs/common": "^10.4.6",
"@nestjs/core": "^10.4.6",
"@nestjs/platform-express": "^10.4.6",
"@nestjs/platform-fastify": "^10.4.6",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.1",
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.8.6",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
Expand All @@ -55,6 +61,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rollup": "^4.24.2",
"ts-node": "^10.9.2",
"typescript": "^5.6.2",
"typescript-eslint": "^8.10.0",
"vite": "^5.4.9"
Expand All @@ -63,6 +70,7 @@
"README.md",
"LICENSE",
"package.json",
"dist",
"lib",
"src"
]
Expand Down
130 changes: 130 additions & 0 deletions packages/editor/src/NestiaEditorModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import type { OpenApiV3, OpenApiV3_1, SwaggerV2 } from "@samchon/openapi";
import * as fs from "fs";

export namespace NestiaEditorModule {
export const setup = async (props: {
path: string;
application: INestApplication;
swagger:
| string
| SwaggerV2.IDocument
| OpenApiV3.IDocument
| OpenApiV3_1.IDocument;
package?: string;
simulate?: boolean;
e2e?: boolean;
}): Promise<void> => {
const prefix: string =
"/" +
[getGlobalPrefix(props.application), props.path]
.join("/")
.split("/")
.filter((str) => str.length !== 0)
.join("/");
const adaptor: INestHttpAdaptor = props.application.getHttpAdapter();
const staticFiles: IStaticFile[] = [
{
path: "/index.html",
type: "text/html",
content: await getIndex(props),
},
{
path: "/swagger.json",
type: "application/json",
content: JSON.stringify(
typeof props.swagger === "string"
? await getSwagger(props.swagger)
: props.swagger,
null,
2,
),
},
await getJavaScript(),
];
for (const f of staticFiles) {
adaptor.get(prefix + f.path, (_: any, res: any) => {
res.type(f.type);
return res.send(f.content);
});
}
for (const p of ["", "/"])
adaptor.get(prefix + p, (_: any, res: any) => {
return res.redirect(prefix + "/index.html");
});
};

const getGlobalPrefix = (app: INestApplication): string =>
typeof (app as any).config?.globalPrefix === "string"
? (app as any).config.globalPrefix
: "";
}

interface INestApplication {
use(...args: any[]): this;
getUrl(): Promise<string>;
getHttpAdapter(): INestHttpAdaptor;
setGlobalPrefix(prefix: string, options?: any): this;
}
interface INestHttpAdaptor {
getType(): string;
close(): any;
init?(): Promise<void>;
get: Function;
post: Function;
put: Function;
patch: Function;
delete: Function;
head: Function;
all: Function;
}
interface IStaticFile {
path: string;
type: string;
content: string;
}

const getIndex = async (props: {
package?: string;
simulate?: boolean;
e2e?: boolean;
}): Promise<string> => {
const content: string = await fs.promises.readFile(
`${__dirname}/../dist/index.html`,
"utf8",
);
return content
.replace(
`"@ORGANIZATION/PROJECT"`,
JSON.stringify(props.package ?? "@ORGANIZATION/PROJECT"),
)
.replace("window.simulate = false", `window.simulate = ${!!props.simulate}`)
.replace("window.e2e = false", `window.e2e = ${!!props.e2e}`);
};

const getJavaScript = async (): Promise<IStaticFile> => {
const directory: string[] = await fs.promises.readdir(
`${__dirname}/../dist/assets`,
);
const path: string | undefined = directory[0];
if (path === undefined)
throw new Error("Unreachable code, no JS file exists.");
return {
path: `/assets/${path}`,
type: "application/javascript",
content: await fs.promises.readFile(
`${__dirname}/../dist/assets/${path}`,
"utf8",
),
};
};

const getSwagger = async (
url: string,
): Promise<
SwaggerV2.IDocument | OpenApiV3.IDocument | OpenApiV3_1.IDocument
> => {
const response: Response = await fetch(url);
if (response.status !== 200)
throw new Error(`Failed to fetch Swagger document from ${url}`);
return response.json();
};
22 changes: 22 additions & 0 deletions packages/editor/test/express.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Module } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";

import { NestiaEditorModule } from "../src/NestiaEditorModule";

@Module({})
class MyModule {}

const main = async (): Promise<void> => {
const app = await NestFactory.create(MyModule, { logger: false });
await NestiaEditorModule.setup({
path: "editor",
application: app,
swagger:
"https://raw.githubusercontent.com/samchon/openapi/refs/heads/master/examples/v3.1/shopping.json",
});
await app.listen(3_001);
};
main().catch((exp) => {
console.error(exp);
process.exit(-1);
});
25 changes: 25 additions & 0 deletions packages/editor/test/fastify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Module } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { FastifyAdapter } from "@nestjs/platform-fastify";

import { NestiaEditorModule } from "../src/NestiaEditorModule";

@Module({})
class MyModule {}

const main = async (): Promise<void> => {
const app = await NestFactory.create(MyModule, new FastifyAdapter(), {
logger: false,
});
await NestiaEditorModule.setup({
path: "editor",
application: app,
swagger:
"https://raw.githubusercontent.com/samchon/openapi/refs/heads/master/examples/v3.1/shopping.json",
});
await app.listen(3_001);
};
main().catch((exp) => {
console.error(exp);
process.exit(-1);
});
2 changes: 1 addition & 1 deletion packages/editor/tsconfig.app.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"root":["./src/nestiaeditorapplication.tsx","./src/nestiaeditoriframe.tsx","./src/nestiaeditoruploader.tsx","./src/index.ts","./src/main.tsx","./src/vite-env.d.ts","./src/internal/nestiaeditorcomposer.ts","./src/internal/nestiaeditorfileuploader.tsx"],"errors":true,"version":"5.6.3"}
{"root":["./src/nestiaeditorapplication.tsx","./src/nestiaeditoriframe.tsx","./src/nestiaeditormodule.ts","./src/nestiaeditoruploader.tsx","./src/index.ts","./src/main.tsx","./src/vite-env.d.ts","./src/internal/nestiaeditorcomposer.ts","./src/internal/nestiaeditorfileuploader.tsx"],"version":"5.6.3"}
10 changes: 10 additions & 0 deletions packages/editor/tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"target": "ES2015",
"module": "CommonJS",
"outDir": "bin",
"noEmit": true,
},
"include": ["src", "test"]
}
32 changes: 21 additions & 11 deletions website/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"@mui/icons-material": "5.15.6",
"@mui/material": "5.15.6",
"@mui/system": "5.15.6",
"@nestia/editor": "^0.6.2",
"@nestia/editor": "^0.7.1",
"next": "14.2.13",
"nextra": "^2.13.4",
"nextra-theme-docs": "^2.13.4",
Expand Down
Loading

0 comments on commit fd658dd

Please sign in to comment.