diff --git a/xx.ts b/xx.ts index 85ad40d..7796d70 100644 --- a/xx.ts +++ b/xx.ts @@ -1,127 +1,65 @@ -import { readdirSync, statSync, writeFileSync } from "fs"; -import _ from "lodash"; -import { basename, extname, join, relative } from "path"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Elysia } from 'elysia' +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) => - fileName - .replace(/_/g, " ") - .replace(/\b\w/g, (c) => c.toUpperCase()) - .replace(/\s/g, ""); +// ========================================================= +// Auth Middleware Plugin +// ========================================================= +export default function apiAuth(app: Elysia) { + if (!secret) throw new Error('JWT_SECRET environment variable is missing') + 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 -function toRoutePath(name: string): string { - if (name.toLowerCase() === "home") return "/"; - if (name.toLowerCase() === "login") return "/login"; - if (name.toLowerCase() === "notfound") return "/*"; - if (name.endsWith("_page")) return name.replace("_page", "").toLowerCase(); - if (name.startsWith("form_")) return name.replace("form_", "").toLowerCase(); - return name.toLowerCase(); -} + // Derive user from JWT or cookie + .derive(async ({ bearer, cookie, jwt }) => { + // Normalize token type to string or undefined + const token = + (typeof bearer === 'string' ? bearer : undefined) ?? + (typeof cookie?.token?.value === 'string' ? cookie.token.value : undefined) -// ๐Ÿงญ Scan folder pages secara rekursif -function scan(dir: string): any[] { - const items = readdirSync(dir); - const routes: any[] = []; + let user: Awaited> | null = null - for (const item of items) { - const full = join(dir, item); - const stat = statSync(full); + if (token) { + try { + const decoded = (await jwt.verify(token)) as JWTPayloadSpec - if (stat.isDirectory()) { - routes.push({ - name: item, - path: item.toLowerCase(), - children: scan(full), - }); - } else if (extname(item) === ".tsx") { - routes.push({ - name: basename(item, ".tsx"), - filePath: relative(join(process.cwd(), "src"), full).replace(/\\/g, "/"), - }); + if (decoded?.sub && typeof decoded.sub === 'string') { + user = await prisma.user.findUnique({ + where: { id: decoded.sub }, + }) + } + } catch (err) { + console.warn('[SERVER][apiAuth] Invalid token:', (err as Error).message) } - } - 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 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 += ` - }> - ${nestedRoutes} - - `; - } 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 += `} />\n`; - } - } - return jsx; -} - -// ๐Ÿงพ Generate import otomatis -function generateImports(routes: any[]): string { - const imports = new Set(); - - 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 ( - - - ${jsxRoutes} - - - ); -} -`; - -writeFileSync(OUTPUT_FILE, finalCode); -console.log("โœ… Routes generated successfully โ†’ src/AppRoutes.generated.tsx"); -Bun.spawnSync(["bunx", "prettier", "--write", "src/**/*.tsx"])