Workspaces in Workspaces all around
This commit is contained in:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user