314 lines
8.9 KiB
JavaScript
314 lines
8.9 KiB
JavaScript
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
|
|
};
|