import { validateBody, validateQuery } from "./chunk-5EGA6GHY.js"; import { getTempDir } from "./chunk-FTVFWJFJ.js"; import { AlreadyExistsError, NotFoundError, ValidationError, isNodeError, resolveNotebookPath } from "./chunk-ER4KPD22.js"; import { asyncHandler, createApiModule, defineApiModule, defineEndpoints, successResponse } from "./chunk-74TMTGBG.js"; // shared/modules/pydemos/api.ts var PYDEMOS_ENDPOINTS = defineEndpoints({ list: { path: "/", method: "GET" }, create: { path: "/create", method: "POST" }, delete: { path: "/delete", method: "DELETE" }, rename: { path: "/rename", method: "POST" } }); // shared/modules/pydemos/index.ts var PYDEMOS_MODULE = defineApiModule({ id: "pydemos", name: "Python Demos", basePath: "/pydemos", order: 50, version: "1.0.0", endpoints: PYDEMOS_ENDPOINTS }); // api/modules/pydemos/routes.ts import express from "express"; import fs from "fs/promises"; import path from "path"; import multer from "multer"; // api/schemas/files.ts import { z } from "zod"; var listFilesQuerySchema = z.object({ path: z.string().optional().default("") }); var contentQuerySchema = z.object({ path: z.string().min(1) }); var rawQuerySchema = z.object({ path: z.string().min(1) }); var pathSchema = z.object({ path: z.string().min(1) }); var saveFileSchema = z.object({ path: z.string().min(1), content: z.string() }); var renameSchema = z.object({ oldPath: z.string().min(1), newPath: z.string().min(1) }); var searchSchema = z.object({ keywords: z.array(z.string()).min(1) }); var existsSchema = z.object({ path: z.string().min(1) }); var createDirSchema = z.object({ path: z.string().min(1) }); var createFileSchema = z.object({ path: z.string().min(1) }); // api/schemas/pydemos.ts import { z as z2 } from "zod"; var listPyDemosQuerySchema = z2.object({ year: z2.string().optional() }); var createPyDemoSchema = z2.object({ name: z2.string().min(1), year: z2.string().min(1), month: z2.string().min(1), folderStructure: z2.string().optional() }); var deletePyDemoSchema = z2.object({ path: z2.string().min(1) }); var renamePyDemoSchema = z2.object({ oldPath: z2.string().min(1), newName: z2.string().min(1) }); // api/modules/pydemos/routes.ts var tempDir = getTempDir(); var upload = multer({ dest: tempDir, limits: { fileSize: 50 * 1024 * 1024 } }); var toPosixPath = (p) => p.replace(/\\/g, "/"); var getYearPath = (year) => { const relPath = `pydemos/${year}`; const { fullPath } = resolveNotebookPath(relPath); return { relPath, fullPath }; }; var getMonthPath = (year, month) => { const monthStr = month.toString().padStart(2, "0"); const relPath = `pydemos/${year}/${monthStr}`; const { fullPath } = resolveNotebookPath(relPath); return { relPath, fullPath }; }; var countFilesInDir = async (dirPath) => { try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); return entries.filter((e) => e.isFile()).length; } catch { return 0; } }; var createPyDemosRoutes = () => { const router = express.Router(); router.get( "/", validateQuery(listPyDemosQuerySchema), asyncHandler(async (req, res) => { const year = parseInt(req.query.year) || (/* @__PURE__ */ new Date()).getFullYear(); const { fullPath: yearPath } = getYearPath(year); const months = []; try { await fs.access(yearPath); } catch { successResponse(res, { months }); return; } const monthEntries = await fs.readdir(yearPath, { withFileTypes: true }); for (const monthEntry of monthEntries) { if (!monthEntry.isDirectory()) continue; const monthNum = parseInt(monthEntry.name); if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) continue; const monthPath = path.join(yearPath, monthEntry.name); const demoEntries = await fs.readdir(monthPath, { withFileTypes: true }); const demos = []; for (const demoEntry of demoEntries) { if (!demoEntry.isDirectory()) continue; const demoPath = path.join(monthPath, demoEntry.name); const relDemoPath = `pydemos/${year}/${monthEntry.name}/${demoEntry.name}`; let created; try { const stats = await fs.stat(demoPath); created = stats.birthtime.toISOString(); } catch { created = (/* @__PURE__ */ new Date()).toISOString(); } const fileCount = await countFilesInDir(demoPath); demos.push({ name: demoEntry.name, path: toPosixPath(relDemoPath), created, fileCount }); } demos.sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime()); if (demos.length > 0) { months.push({ month: monthNum, demos }); } } months.sort((a, b) => a.month - b.month); successResponse(res, { months }); }) ); router.post( "/create", upload.array("files"), validateBody(createPyDemoSchema), asyncHandler(async (req, res) => { const { name, year, month, folderStructure } = req.body; const yearNum = parseInt(year); const monthNum = parseInt(month); if (!/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(name)) { throw new ValidationError("Invalid name format"); } const { fullPath: monthPath, relPath: monthRelPath } = getMonthPath(yearNum, monthNum); const demoPath = path.join(monthPath, name); const relDemoPath = `${monthRelPath}/${name}`; try { await fs.access(demoPath); throw new AlreadyExistsError("Demo already exists"); } catch (err) { if (isNodeError(err) && err.code === "ENOENT") { } else if (err instanceof AlreadyExistsError) { throw err; } else { throw err; } } await fs.mkdir(demoPath, { recursive: true }); const files = req.files; let fileCount = 0; if (files && files.length > 0) { let structure = {}; if (folderStructure) { try { structure = JSON.parse(folderStructure); } catch { structure = {}; } } for (const file of files) { const relativePath = structure[file.originalname] || file.originalname; const targetPath = path.join(demoPath, relativePath); const targetDir = path.dirname(targetPath); await fs.mkdir(targetDir, { recursive: true }); await fs.copyFile(file.path, targetPath); await fs.unlink(file.path).catch(() => { }); fileCount++; } } successResponse(res, { path: toPosixPath(relDemoPath), fileCount }); }) ); router.delete( "/delete", validateBody(deletePyDemoSchema), asyncHandler(async (req, res) => { const { path: demoPath } = req.body; if (!demoPath.startsWith("pydemos/")) { throw new ValidationError("Invalid path"); } const { fullPath } = resolveNotebookPath(demoPath); try { await fs.access(fullPath); } catch { throw new NotFoundError("Demo not found"); } await fs.rm(fullPath, { recursive: true, force: true }); successResponse(res, null); }) ); router.post( "/rename", validateBody(renamePyDemoSchema), asyncHandler(async (req, res) => { const { oldPath, newName } = req.body; if (!oldPath.startsWith("pydemos/")) { throw new ValidationError("Invalid path"); } if (!/^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$/.test(newName)) { throw new ValidationError("Invalid name format"); } const { fullPath: oldFullPath } = resolveNotebookPath(oldPath); try { await fs.access(oldFullPath); } catch { throw new NotFoundError("Demo not found"); } const parentDir = path.dirname(oldFullPath); const newFullPath = path.join(parentDir, newName); const newPath = toPosixPath(path.join(path.dirname(oldPath), newName)); try { await fs.access(newFullPath); throw new AlreadyExistsError("Demo with this name already exists"); } catch (err) { if (isNodeError(err) && err.code === "ENOENT") { } else if (err instanceof AlreadyExistsError) { throw err; } else { throw err; } } await fs.rename(oldFullPath, newFullPath); successResponse(res, { newPath }); }) ); return router; }; var routes_default = createPyDemosRoutes(); // api/modules/pydemos/index.ts var createPyDemosModule = () => { return createApiModule(PYDEMOS_MODULE, { routes: (_container) => { return createPyDemosRoutes(); } }); }; var pydemos_default = createPyDemosModule; export { listFilesQuerySchema, contentQuerySchema, rawQuerySchema, pathSchema, saveFileSchema, renameSchema, createDirSchema, createFileSchema, createPyDemosRoutes, createPyDemosModule, pydemos_default };