import { AlreadyExistsError, BadRequestError, NotFoundError, ValidationError, resolveNotebookPath } from "./chunk-ER4KPD22.js"; import { asyncHandler, createApiModule, defineApiModule, defineEndpoints, successResponse } from "./chunk-74TMTGBG.js"; // shared/modules/recycle-bin/api.ts var RECYCLE_BIN_ENDPOINTS = defineEndpoints({ list: { path: "/", method: "GET" }, restore: { path: "/restore", method: "POST" }, permanent: { path: "/permanent", method: "DELETE" }, empty: { path: "/empty", method: "DELETE" } }); // shared/modules/recycle-bin/index.ts var RECYCLE_BIN_MODULE = defineApiModule({ id: "recycle-bin", name: "\u56DE\u6536\u7AD9", basePath: "/recycle-bin", order: 40, version: "1.0.0", endpoints: RECYCLE_BIN_ENDPOINTS }); // api/modules/recycle-bin/routes.ts import express from "express"; import fs2 from "fs/promises"; import path2 from "path"; // api/modules/recycle-bin/recycleBinService.ts import fs from "fs/promises"; import path from "path"; async function restoreFile(srcPath, destPath, deletedDate, year, month, day) { const { fullPath: imagesDir } = resolveNotebookPath(`images/${year}/${month}/${day}`); let content = await fs.readFile(srcPath, "utf-8"); const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g; let match; const imageReplacements = []; while ((match = imageRegex.exec(content)) !== null) { const imagePath = match[2]; const imageName = path.basename(imagePath); const rbImageName = `${deletedDate}_${imageName}`; const { fullPath: srcImagePath } = resolveNotebookPath(`RB/${rbImageName}`); try { await fs.access(srcImagePath); await fs.mkdir(imagesDir, { recursive: true }); const destImagePath = path.join(imagesDir, imageName); await fs.rename(srcImagePath, destImagePath); const newImagePath = `images/${year}/${month}/${day}/${imageName}`; imageReplacements.push({ oldPath: imagePath, newPath: newImagePath }); } catch { } } for (const { oldPath, newPath } of imageReplacements) { content = content.replace(new RegExp(oldPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), newPath); } await fs.writeFile(destPath, content, "utf-8"); await fs.unlink(srcPath); } async function restoreFolder(srcPath, destPath, deletedDate, year, month, day) { await fs.mkdir(destPath, { recursive: true }); const entries = await fs.readdir(srcPath, { withFileTypes: true }); for (const entry of entries) { const srcEntryPath = path.join(srcPath, entry.name); const destEntryPath = path.join(destPath, entry.name); if (entry.isDirectory()) { await restoreFolder(srcEntryPath, destEntryPath, deletedDate, year, month, day); } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) { await restoreFile(srcEntryPath, destEntryPath, deletedDate, year, month, day); } else { await fs.rename(srcEntryPath, destEntryPath); } } const remaining = await fs.readdir(srcPath); if (remaining.length === 0) { await fs.rmdir(srcPath); } } // api/modules/recycle-bin/routes.ts var router = express.Router(); router.get( "/", asyncHandler(async (req, res) => { const { fullPath: rbDir } = resolveNotebookPath("RB"); try { await fs2.access(rbDir); } catch { successResponse(res, { groups: [] }); return; } const entries = await fs2.readdir(rbDir, { withFileTypes: true }); const items = []; for (const entry of entries) { const match = entry.name.match(/^(\d{8})_(.+)$/); if (!match) continue; const [, dateStr, originalName] = match; if (entry.isDirectory()) { items.push({ name: entry.name, originalName, type: "dir", deletedDate: dateStr, path: `RB/${entry.name}` }); } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) { items.push({ name: entry.name, originalName, type: "file", deletedDate: dateStr, path: `RB/${entry.name}` }); } } const groupedMap = /* @__PURE__ */ new Map(); for (const item of items) { const existing = groupedMap.get(item.deletedDate) || []; existing.push(item); groupedMap.set(item.deletedDate, existing); } const groups = Array.from(groupedMap.entries()).map(([date, items2]) => ({ date, items: items2.sort((a, b) => a.originalName.localeCompare(b.originalName)) })).sort((a, b) => b.date.localeCompare(a.date)); successResponse(res, { groups }); }) ); router.post( "/restore", asyncHandler(async (req, res) => { const { path: relPath, type } = req.body; if (!relPath || !type) { throw new ValidationError("Path and type are required"); } const { fullPath: itemPath } = resolveNotebookPath(relPath); try { await fs2.access(itemPath); } catch { throw new NotFoundError("Item not found in recycle bin"); } const match = path2.basename(itemPath).match(/^(\d{8})_(.+)$/); if (!match) { throw new BadRequestError("Invalid recycle bin item name"); } const [, dateStr, originalName] = match; const year = dateStr.substring(0, 4); const month = dateStr.substring(4, 6); const day = dateStr.substring(6, 8); const { fullPath: markdownsDir } = resolveNotebookPath("markdowns"); await fs2.mkdir(markdownsDir, { recursive: true }); const destPath = path2.join(markdownsDir, originalName); const existing = await fs2.stat(destPath).catch(() => null); if (existing) { throw new AlreadyExistsError("A file or folder with this name already exists"); } if (type === "dir") { await restoreFolder(itemPath, destPath, dateStr, year, month, day); } else { await restoreFile(itemPath, destPath, dateStr, year, month, day); } successResponse(res, null); }) ); router.delete( "/permanent", asyncHandler(async (req, res) => { const { path: relPath, type } = req.body; if (!relPath || !type) { throw new ValidationError("Path and type are required"); } const { fullPath: itemPath } = resolveNotebookPath(relPath); try { await fs2.access(itemPath); } catch { throw new NotFoundError("Item not found in recycle bin"); } if (type === "dir") { await fs2.rm(itemPath, { recursive: true, force: true }); } else { await fs2.unlink(itemPath); } successResponse(res, null); }) ); router.delete( "/empty", asyncHandler(async (req, res) => { const { fullPath: rbDir } = resolveNotebookPath("RB"); try { await fs2.access(rbDir); } catch { successResponse(res, null); return; } const entries = await fs2.readdir(rbDir, { withFileTypes: true }); for (const entry of entries) { const entryPath = path2.join(rbDir, entry.name); if (entry.isDirectory()) { await fs2.rm(entryPath, { recursive: true, force: true }); } else { await fs2.unlink(entryPath); } } successResponse(res, null); }) ); var routes_default = router; // api/modules/recycle-bin/index.ts var createRecycleBinModule = () => { return createApiModule(RECYCLE_BIN_MODULE, { routes: (_container) => { return routes_default; } }); }; var recycle_bin_default = createRecycleBinModule; export { restoreFile, restoreFolder, createRecycleBinModule, recycle_bin_default };