Workspaces in Workspaces all around

This commit is contained in:
2026-02-19 01:49:15 +00:00
parent 72bca6cd42
commit dd8ab84df5
84 changed files with 3047 additions and 749 deletions
+65
View File
@@ -413,6 +413,71 @@ router.post('/git-clone', async (ctx) => {
return ctx.json({ ok: true });
});
// Download file or directory (directories are zipped on-the-fly)
router.get('/download', async (ctx) => {
const user = ctx.get('user');
const rootDir = getRootDir(user, ctx.req.query('root') ?? undefined);
const relPath = (ctx.req.query('path') || '').replace(/^\/+/, '');
if (!relPath) throw errors.BAD_REQUEST('path is required');
const absPath = resolveUserPath(rootDir, relPath);
const s = await stat(absPath);
const name = absPath.split('/').pop()!;
if (s.isDirectory()) {
const proc = Bun.spawn(['zip', '-r', '-', name], {
cwd: dirname(absPath),
stdout: 'pipe',
stderr: 'ignore',
});
return new Response(proc.stdout as ReadableStream, {
headers: {
'Content-Type': 'application/zip',
'Content-Disposition': `attachment; filename="${name}.zip"`,
},
});
}
const file = Bun.file(absPath);
return new Response(file, {
headers: {
'Content-Type': file.type || 'application/octet-stream',
'Content-Disposition': `attachment; filename="${name}"`,
'Content-Length': String(s.size),
},
});
});
// Download multiple items as a single zip
router.post('/download', async (ctx) => {
const user = ctx.get('user');
const rootDir = getRootDir(user, ctx.req.query('root') ?? undefined);
const { paths } = ctx.get('body') as { paths: string[] };
if (!Array.isArray(paths) || paths.length === 0) throw errors.BAD_REQUEST('paths is required');
const items: string[] = [];
for (const p of paths) {
const relPath = p.replace(/^\/+/, '');
const absPath = resolveUserPath(rootDir, relPath);
await stat(absPath); // throws if not found
items.push(relPath);
}
const proc = Bun.spawn(['zip', '-r', '-', ...items], {
cwd: rootDir,
stdout: 'pipe',
stderr: 'ignore',
});
return new Response(proc.stdout as ReadableStream, {
headers: {
'Content-Type': 'application/zip',
'Content-Disposition': 'attachment; filename="download.zip"',
},
});
});
// Delete file or directory
router.delete('/rm', async (ctx) => {
const user = ctx.get('user');