6d5eb15b1b
Expose authenticated /api/mindspace/v1/oa/drive APIs over each user's oa/ workspace and wire a MindSpace entry at /space/oa/drive. Co-authored-by: Cursor <cursoragent@cursor.com>
85 lines
2.6 KiB
JavaScript
85 lines
2.6 KiB
JavaScript
import fsp from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import crypto from 'node:crypto';
|
|
import { joinRel, normalizeRelPath, safeJoin, sessionRoot } from './paths.mjs';
|
|
import { setFileMeta } from './fs-tree.mjs';
|
|
|
|
const sessions = new Map();
|
|
|
|
function sessionFile(ctx, id) {
|
|
return path.join(sessionRoot(ctx), `${id}.part`);
|
|
}
|
|
|
|
function sessionMetaFile(ctx, id) {
|
|
return path.join(sessionRoot(ctx), `${id}.json`);
|
|
}
|
|
|
|
export async function ensureSessionDir(ctx) {
|
|
await fsp.mkdir(sessionRoot(ctx), { recursive: true });
|
|
}
|
|
|
|
export async function createUploadSession(ctx, { relPath, totalSize, prefix = '', clientMeta = {} }) {
|
|
await ensureSessionDir(ctx);
|
|
const id = crypto.randomBytes(8).toString('hex');
|
|
const rel = joinRel(prefix, normalizeRelPath(relPath));
|
|
const meta = {
|
|
id,
|
|
rel,
|
|
totalSize: Number(totalSize),
|
|
received: 0,
|
|
clientMeta,
|
|
createdAt: Date.now(),
|
|
userId: ctx.userId,
|
|
};
|
|
await fsp.writeFile(sessionMetaFile(ctx, id), JSON.stringify(meta));
|
|
await fsp.writeFile(sessionFile(ctx, id), Buffer.alloc(0));
|
|
sessions.set(`${ctx.userId}:${id}`, meta);
|
|
return meta;
|
|
}
|
|
|
|
async function loadSession(ctx, id) {
|
|
const key = `${ctx.userId}:${id}`;
|
|
if (sessions.has(key)) return sessions.get(key);
|
|
const raw = await fsp.readFile(sessionMetaFile(ctx, id), 'utf8');
|
|
const meta = JSON.parse(raw);
|
|
if (meta.userId && meta.userId !== ctx.userId) {
|
|
throw new Error('session not found');
|
|
}
|
|
sessions.set(key, meta);
|
|
return meta;
|
|
}
|
|
|
|
export async function appendUploadChunk(ctx, id, buffer, offset) {
|
|
const meta = await loadSession(ctx, id);
|
|
const file = sessionFile(ctx, id);
|
|
const handle = await fsp.open(file, 'r+');
|
|
try {
|
|
await handle.write(buffer, 0, buffer.length, Number(offset));
|
|
} finally {
|
|
await handle.close();
|
|
}
|
|
meta.received = Math.max(meta.received, Number(offset) + buffer.length);
|
|
await fsp.writeFile(sessionMetaFile(ctx, id), JSON.stringify(meta));
|
|
return meta;
|
|
}
|
|
|
|
export async function completeUploadSession(ctx, id) {
|
|
const meta = await loadSession(ctx, id);
|
|
const part = sessionFile(ctx, id);
|
|
const stat = await fsp.stat(part);
|
|
if (stat.size < meta.totalSize) throw new Error('incomplete');
|
|
const dest = safeJoin(ctx.uploadDir, meta.rel);
|
|
await fsp.mkdir(path.dirname(dest), { recursive: true });
|
|
await fsp.rename(part, dest);
|
|
await fsp.unlink(sessionMetaFile(ctx, id)).catch(() => {});
|
|
sessions.delete(`${ctx.userId}:${id}`);
|
|
if (meta.clientMeta?.clientId) {
|
|
await setFileMeta(ctx, meta.rel, meta.clientMeta);
|
|
}
|
|
return { saved: meta.rel, size: stat.size };
|
|
}
|
|
|
|
export async function getSession(ctx, id) {
|
|
return loadSession(ctx, id);
|
|
}
|