This commit is contained in:
bipproduction
2025-10-28 16:09:04 +08:00
parent b52da1c4bd
commit e009e27d47

176
xx.ts
View File

@@ -1,127 +1,65 @@
import { readdirSync, statSync, writeFileSync } from "fs"; /* eslint-disable @typescript-eslint/no-explicit-any */
import _ from "lodash"; import { Elysia } from 'elysia'
import { basename, extname, join, relative } from "path"; import jwt, { type JWTPayloadSpec } from '@elysiajs/jwt'
import bearer from '@elysiajs/bearer'
import { prisma } from '../lib/prisma'
const PAGES_DIR = join(process.cwd(), "src/pages"); // =========================================================
const OUTPUT_FILE = join(process.cwd(), "src/AppRoutes.tsx"); // JWT Secret Validation
// =========================================================
const secret = process.env.JWT_SECRET
if (!secret) throw new Error('JWT_SECRET environment variable is missing')
// 🧩 Ubah nama file ke nama komponen (PascalCase) // =========================================================
const toComponentName = (fileName: string) => // Auth Middleware Plugin
fileName // =========================================================
.replace(/_/g, " ") export default function apiAuth(app: Elysia) {
.replace(/\b\w/g, (c) => c.toUpperCase()) if (!secret) throw new Error('JWT_SECRET environment variable is missing')
.replace(/\s/g, ""); return app
// Register Bearer and JWT plugins
.use(bearer()) // ✅ Extracts Bearer token automatically (case-insensitive)
.use(
jwt({
name: 'jwt',
secret,
})
)
// 🧩 Ubah nama file ke path route // Derive user from JWT or cookie
function toRoutePath(name: string): string { .derive(async ({ bearer, cookie, jwt }) => {
if (name.toLowerCase() === "home") return "/"; // Normalize token type to string or undefined
if (name.toLowerCase() === "login") return "/login"; const token =
if (name.toLowerCase() === "notfound") return "/*"; (typeof bearer === 'string' ? bearer : undefined) ??
if (name.endsWith("_page")) return name.replace("_page", "").toLowerCase(); (typeof cookie?.token?.value === 'string' ? cookie.token.value : undefined)
if (name.startsWith("form_")) return name.replace("form_", "").toLowerCase();
return name.toLowerCase();
}
// 🧭 Scan folder pages secara rekursif let user: Awaited<ReturnType<typeof prisma.user.findUnique>> | null = null
function scan(dir: string): any[] {
const items = readdirSync(dir);
const routes: any[] = [];
for (const item of items) { if (token) {
const full = join(dir, item); try {
const stat = statSync(full); const decoded = (await jwt.verify(token)) as JWTPayloadSpec
if (stat.isDirectory()) { if (decoded?.sub && typeof decoded.sub === 'string') {
routes.push({ user = await prisma.user.findUnique({
name: item, where: { id: decoded.sub },
path: item.toLowerCase(), })
children: scan(full), }
}); } catch (err) {
} else if (extname(item) === ".tsx") { console.warn('[SERVER][apiAuth] Invalid token:', (err as Error).message)
routes.push({
name: basename(item, ".tsx"),
filePath: relative(join(process.cwd(), "src"), full).replace(/\\/g, "/"),
});
} }
} }
return routes;
return { user }
})
// Protect all routes by default
.onBeforeHandle(({ user, set, request }) => {
// Whitelist public routes if needed
const publicPaths = ['/auth/login', '/auth/register', '/public']
if (publicPaths.some((path) => request.url.includes(path))) return
if (!user) {
set.status = 401
return { error: 'Unauthorized' }
}
})
} }
// 🏗️ Generate <Route> JSX dari struktur folder
function generateJSX(routes: any[], parentPath = ""): string {
let jsx = "";
for (const route of routes) {
if (route.children) {
// cari layout di folder
const layout = route.children.find((r: any) => r.name.endsWith("_layout"));
if (layout) {
const LayoutComponent = toComponentName(layout.name.replace("_layout", "Layout"));
const nested = route.children.filter((r: any) => r !== layout);
const nestedRoutes = generateJSX(nested, `${parentPath}/${route.path}`);
jsx += `
<Route path="${parentPath}/${route.path}" element={<${LayoutComponent} />}>
${nestedRoutes}
</Route>
`;
} else {
jsx += generateJSX(route.children, `${parentPath}/${route.path}`);
}
} else {
const Component = toComponentName(route.name);
const routePath = toRoutePath(route.name);
// Hapus duplikasi segmen
const fullPath =
routePath.startsWith("/")
? routePath
: `${parentPath}/${_.kebabCase(routePath)}`.replace(/\/+/g, "/");
jsx += `<Route path="${fullPath}" element={<${Component} />} />\n`;
}
}
return jsx;
}
// 🧾 Generate import otomatis
function generateImports(routes: any[]): string {
const imports = new Set<string>();
function collect(rs: any[]) {
for (const r of rs) {
if (r.children) collect(r.children);
else {
const Comp = toComponentName(r.name);
imports.add(`import ${Comp} from "./${r.filePath.replace(/\.tsx$/, "")}";`);
}
}
}
collect(routes);
return Array.from(imports).join("\n");
}
// 🧠 Main generator
const allRoutes = scan(PAGES_DIR);
const imports = generateImports(allRoutes);
const jsxRoutes = generateJSX(allRoutes);
const finalCode = `
// ⚡ Auto-generated by generateRoutes.ts — DO NOT EDIT MANUALLY
import { BrowserRouter, Routes, Route } from "react-router-dom";
import ProtectedRoute from "./components/ProtectedRoute";
${imports}
export default function AppRoutes() {
return (
<BrowserRouter>
<Routes>
${jsxRoutes}
</Routes>
</BrowserRouter>
);
}
`;
writeFileSync(OUTPUT_FILE, finalCode);
console.log("✅ Routes generated successfully → src/AppRoutes.generated.tsx");
Bun.spawnSync(["bunx", "prettier", "--write", "src/**/*.tsx"])