Skip to content

Commit

Permalink
Merge commit from fork
Browse files Browse the repository at this point in the history
  • Loading branch information
diced authored Sep 12, 2024
1 parent f3638f3 commit 9c26d64
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 18 deletions.
14 changes: 14 additions & 0 deletions prisma/migrations/20240912180249_exports/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "Export" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"complete" BOOLEAN NOT NULL DEFAULT false,
"path" TEXT NOT NULL,
"userId" INTEGER NOT NULL,

CONSTRAINT "Export_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "Export" ADD CONSTRAINT "Export_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
14 changes: 14 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ model User {
Invite Invite[]
Folder Folder[]
IncompleteFile IncompleteFile[]
Exports Export[]
}

model Export {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
complete Boolean @default(false)
path String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int
}

model Folder {
Expand Down
29 changes: 28 additions & 1 deletion src/components/pages/Manage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
IconUserExclamation,
IconUserMinus,
IconUserX,
IconX,
} from '@tabler/icons-react';
import AnchorNext from 'components/AnchorNext';
import { FlameshotIcon, ShareXIcon } from 'components/icons';
Expand Down Expand Up @@ -264,14 +265,34 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
setExports(
res.exports
?.map((s) => ({
date: new Date(Number(s.name.split('_')[3].slice(0, -4))),
date: new Date(s.createdAt),
size: s.size,
full: s.name,
}))
.sort((a, b) => a.date.getTime() - b.date.getTime()),
);
};

const deleteExport = async (name) => {
const res = await useFetch('/api/user/export?name=' + name, 'DELETE');
if (res.error) {
showNotification({
title: 'Error deleting export',
message: res.error,
color: 'red',
icon: <IconX size='1rem' />,
});
} else {
showNotification({
message: 'Deleted export',
color: 'green',
icon: <IconFileZip size='1rem' />,
});

await getExports();
}
};

const handleDelete = async () => {
const res = await useFetch('/api/user/files', 'DELETE', {
all: true,
Expand Down Expand Up @@ -580,6 +601,7 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
{ id: 'name', name: 'Name' },
{ id: 'date', name: 'Date' },
{ id: 'size', name: 'Size' },
{ id: 'actions', name: '' },
]}
rows={
exports
Expand All @@ -591,6 +613,11 @@ export default function Manage({ oauth_registration, oauth_providers: raw_oauth_
),
date: x.date.toLocaleString(),
size: bytesToHuman(x.size),
actions: (
<ActionIcon onClick={() => deleteExport(x.full)}>
<IconTrash size='1rem' />
</ActionIcon>
),
}))
: []
}
Expand Down
92 changes: 75 additions & 17 deletions src/pages/api/user/export.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Zip, ZipPassThrough } from 'fflate';
import { createReadStream, createWriteStream } from 'fs';
import { readdir, stat } from 'fs/promises';
import { rm, stat } from 'fs/promises';
import datasource from 'lib/datasource';
import Logger from 'lib/logger';
import prisma from 'lib/prisma';
Expand All @@ -23,6 +23,13 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const export_name = `zipline_export_${user.id}_${Date.now()}.zip`;
const path = join(config.core.temp_directory, export_name);

const exportDb = await prisma.export.create({
data: {
path: export_name,
userId: user.id,
},
});

logger.debug(`creating write stream at ${path}`);
const write_stream = createWriteStream(path);

Expand Down Expand Up @@ -79,11 +86,27 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
logger.info(
`Export for ${user.username} (${user.id}) has completed and is available at ${export_name}`,
);

await prisma.export.update({
where: {
id: exportDb.id,
},
data: {
complete: true,
},
});
}
} else {
write_stream.close();
logger.debug(`error while writing to zip: ${err}`);
logger.error(`Export for ${user.username} (${user.id}) has failed\n${err}`);
logger.error(
`Export for ${user.username} (${user.id}) has failed and has been removed from the database\n${err}`,
);

await prisma.export.delete({
where: {
id: exportDb.id,
},
});
}
};

Expand Down Expand Up @@ -114,27 +137,62 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
res.json({
url: '/api/user/export?name=' + export_name,
});
} else if (req.method === 'DELETE') {
const name = req.query.name as string;
if (!name) return res.badRequest('no name provided');

const exportDb = await prisma.export.findFirst({
where: {
userId: user.id,
path: name,
},
});

if (!exportDb) return res.notFound('export not found');

await prisma.export.delete({
where: {
id: exportDb.id,
},
});

try {
await rm(join(config.core.temp_directory, exportDb.path));
} catch (e) {
logger
.error(`export file ${exportDb.path} has been removed from the database`)
.error(`but failed to remove the file from the filesystem: ${e}`);
}

res.json({
success: true,
});
} else {
const export_name = req.query.name as string;
if (export_name) {
const parts = export_name.split('_');
if (Number(parts[2]) !== user.id) return res.unauthorized('cannot access export owned by another user');
const exportsDb = await prisma.export.findMany({
where: {
userId: user.id,
},
});

const name = req.query.name as string;
if (name) {
const exportDb = exportsDb.find((e) => e.path === name);
if (!exportDb) return res.notFound('export not found');

const stream = createReadStream(join(config.core.temp_directory, export_name));
const stream = createReadStream(join(config.core.temp_directory, exportDb.path));

res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', `attachment; filename="${export_name}"`);
res.setHeader('Content-Disposition', `attachment; filename="${exportDb.path}"`);
stream.pipe(res);
} else {
const files = await readdir(config.core.temp_directory);
const exp = files.filter((f) => f.startsWith('zipline_export_'));
const exports = [];
for (let i = 0; i !== exp.length; ++i) {
const name = exp[i];
const stats = await stat(join(config.core.temp_directory, name));

if (Number(exp[i].split('_')[2]) !== user.id) continue;
exports.push({ name, size: stats.size });
for (let i = 0; i !== exportsDb.length; ++i) {
const exportDb = exportsDb[i];
if (!exportDb.complete) continue;

const stats = await stat(join(config.core.temp_directory, exportDb.path));
exports.push({ name: exportDb.path, size: stats.size, createdAt: exportDb.createdAt });
}

res.json({
Expand All @@ -145,6 +203,6 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
}

export default withZipline(handler, {
methods: ['GET', 'POST'],
methods: ['GET', 'POST', 'DELETE'],
user: true,
});

0 comments on commit 9c26d64

Please sign in to comment.