mirror of
https://github.com/diced/zipline.git
synced 2025-12-12 07:40:45 -08:00
feat: export folder as zip file
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
IconShare,
|
||||
IconShareOff,
|
||||
IconTrashFilled,
|
||||
IconZip,
|
||||
} from '@tabler/icons-react';
|
||||
import ViewFilesModal from '../ViewFilesModal';
|
||||
import EditFolderNameModal from '../EditFolderNameModal';
|
||||
@@ -169,6 +170,14 @@ export default function FolderTableView() {
|
||||
<IconPencil size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label='Export folder as ZIP'>
|
||||
<ActionIcon
|
||||
color='blue'
|
||||
onClick={() => window.open(`/api/user/folders/${folder.id}/export`, '_blank')}
|
||||
>
|
||||
<IconZip size='1rem' />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label='Delete Folder'>
|
||||
<ActionIcon
|
||||
color='red'
|
||||
|
||||
68
src/server/routes/api/user/folders/[id]/export.ts
Normal file
68
src/server/routes/api/user/folders/[id]/export.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { datasource } from '@/lib/datasource';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { log } from '@/lib/logger';
|
||||
import { userMiddleware } from '@/server/middleware/user';
|
||||
import archiver from 'archiver';
|
||||
import fastifyPlugin from 'fastify-plugin';
|
||||
|
||||
export type ApiUserFoldersIdExportResponse = null;
|
||||
|
||||
type Params = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const logger = log('api').c('user').c('folders').c('[id]').c('export');
|
||||
|
||||
export const PATH = '/api/user/folders/:id/export';
|
||||
export default fastifyPlugin(
|
||||
(server, _, done) => {
|
||||
server.get<{ Params: Params }>(PATH, { preHandler: [userMiddleware] }, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
const folder = await prisma.folder.findUnique({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
include: {
|
||||
files: true,
|
||||
},
|
||||
});
|
||||
if (!folder) return res.notFound('Folder not found');
|
||||
if (req.user.id !== folder.userId) return res.forbidden('You do not own this folder');
|
||||
|
||||
if (!folder.files.length) return res.badRequest("Can't export an empty folder.");
|
||||
|
||||
logger.info(`folder export requested: ${folder.name}`, { user: req.user.id, folder: folder.id });
|
||||
|
||||
res.hijack();
|
||||
|
||||
const zip = archiver('zip', {
|
||||
zlib: { level: 9 },
|
||||
});
|
||||
|
||||
zip.pipe(res.raw);
|
||||
|
||||
for (const file of folder.files) {
|
||||
const stream = await datasource.get(file.name);
|
||||
if (!stream) {
|
||||
logger.warn('failed to get file stream for folder export', { file: file.id, folder: folder.id });
|
||||
continue;
|
||||
}
|
||||
|
||||
zip.append(stream, { name: file.name });
|
||||
}
|
||||
|
||||
zip.on('error', (err) => {
|
||||
logger.error('error during folder export zip creation', { folder: folder.id }).error(err as Error);
|
||||
});
|
||||
|
||||
zip.on('finish', () => {
|
||||
logger.info(`folder export completed: ${folder.name}`, { user: req.user.id, folder: folder.id });
|
||||
});
|
||||
|
||||
await zip.finalize();
|
||||
});
|
||||
done();
|
||||
},
|
||||
{ name: PATH },
|
||||
);
|
||||
Reference in New Issue
Block a user