Files
memind/mindspace-oa-drive/lib/trash.mjs
T
john 97a3e89ff7 fix(mindspace): repair OA drive JSON body parsing and trash paths
Express already parses JSON request bodies, so drive handlers must read req.body directly. Also fix nested trash item original-path parsing to match file-drop-sync behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 19:04:27 +08:00

93 lines
3.3 KiB
JavaScript

import fs from 'node:fs';
import fsp from 'node:fs/promises';
import path from 'node:path';
import { basenameOf, joinRel, safeJoin, trashRoot } from './paths.mjs';
import { removeFileMeta } from './fs-tree.mjs';
async function ensureTrashDir(ctx) {
await fsp.mkdir(trashRoot(ctx), { recursive: true });
}
function trashNameFor(rel) {
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
return `${stamp}__${rel.replace(/\//g, '__')}`;
}
export async function moveToTrash(ctx, rel) {
await ensureTrashDir(ctx);
const src = safeJoin(ctx.uploadDir, rel);
if (!fs.existsSync(src)) throw new Error('not found');
const entryName = trashNameFor(rel);
const dest = path.join(trashRoot(ctx), entryName);
await fsp.rename(src, dest);
await removeFileMeta(ctx, rel);
return {
id: entryName,
original: rel,
trashedAt: Date.now(),
};
}
export async function listTrash(ctx) {
await ensureTrashDir(ctx);
const entries = await fsp.readdir(trashRoot(ctx), { withFileTypes: true });
const items = [];
for (const entry of entries) {
const full = path.join(trashRoot(ctx), entry.name);
const stat = await fsp.stat(full);
const separator = entry.name.indexOf('__');
const original = separator >= 0
? entry.name.slice(separator + 2).replace(/__/g, '/')
: entry.name;
items.push({
id: entry.name,
original,
trashedAt: stat.mtimeMs,
isDirectory: stat.isDirectory(),
size: stat.isDirectory() ? null : stat.size,
});
}
return items.sort((a, b) => b.trashedAt - a.trashedAt);
}
export async function restoreFromTrash(ctx, id, onConflict = 'rename') {
const src = path.join(trashRoot(ctx), id);
if (!fs.existsSync(src)) throw new Error('not found');
const item = (await listTrash(ctx)).find((x) => x.id === id);
if (!item) throw new Error('not found');
let dest = safeJoin(ctx.uploadDir, item.original);
if (fs.existsSync(dest)) {
if (onConflict === 'skip') return null;
if (onConflict === 'overwrite') await fsp.rm(dest, { recursive: true, force: true });
else {
const base = basenameOf(item.original);
const parent = item.original.includes('/') ? item.original.slice(0, item.original.lastIndexOf('/')) : '';
let i = 1;
let candidate = joinRel(parent, `${path.parse(base).name} (恢复)${path.extname(base)}`);
while (fs.existsSync(safeJoin(ctx.uploadDir, candidate))) {
i += 1;
candidate = joinRel(parent, `${path.parse(base).name} (恢复 ${i})${path.extname(base)}`);
}
dest = safeJoin(ctx.uploadDir, candidate);
}
}
await fsp.mkdir(path.dirname(dest), { recursive: true });
await fsp.rename(src, dest);
const finalRel = path.relative(ctx.uploadDir, dest).split(path.sep).join('/');
return { id, from: item.original, to: finalRel };
}
export async function emptyTrash(ctx) {
await ensureTrashDir(ctx);
const entries = await fsp.readdir(trashRoot(ctx), { withFileTypes: true });
for (const entry of entries) {
await fsp.rm(path.join(trashRoot(ctx), entry.name), { recursive: true, force: true });
}
}
export async function purgeTrashItem(ctx, id) {
const target = path.join(trashRoot(ctx), id);
if (!fs.existsSync(target)) throw new Error('not found');
await fsp.rm(target, { recursive: true, force: true });
}