diff --git a/src/app/api/tasks/[id]/attachment/route.ts b/src/app/api/tasks/[id]/attachment/route.ts new file mode 100644 index 00000000..feee4e78 --- /dev/null +++ b/src/app/api/tasks/[id]/attachment/route.ts @@ -0,0 +1,58 @@ +import { notFound } from 'next/navigation' +import { NextRequest, NextResponse } from 'next/server' + +import { GetObjectCommand } from '@aws-sdk/client-s3' + +import checkUserPermissionOnTask from '@/lib/api/queries/checkUserPermissionOnTask' +import prisma from '@/lib/prisma' +import { s3Client } from '@/lib/s3Client' +import { getServerUser } from '@/lib/session' +import { forbidden, unauthorized } from '@/utils/apiResponse' + +export async function GET( + _: NextRequest, + { params }: { params: { id: string } } +) { + const id = params.id + + const task = await prisma.task.findUnique({ + where: { id }, + select: { + id: true, + private: true, + statement: true + } + }) + + if (!task) return notFound() + + if (task.private) { + const user = await getServerUser() + + if (!user) { + return unauthorized() + } + + if (!(await checkUserPermissionOnTask(user, task.id))) { + return forbidden() + } + } + + try { + const response = await s3Client.send( + new GetObjectCommand({ + Bucket: process.env.BUCKET_NAME, + Key: `statements/attachments/${id}.zip` + }) + ) + + return new NextResponse(response.Body as ReadableStream, { + headers: { + 'Content-Type': 'application/zip', + 'Content-Disposition': `inline; attachment; filename=${id}.zip` + } + }) + } catch { + return new NextResponse(null, { status: 204 }) + } +} diff --git a/src/app/api/tasks/route.ts b/src/app/api/tasks/route.ts index 674de2a8..fbe50e57 100644 --- a/src/app/api/tasks/route.ts +++ b/src/app/api/tasks/route.ts @@ -54,7 +54,9 @@ export async function POST(req: NextRequest) { const Key = file.type === 'application/pdf' ? `statements/pdf/${task.id}.pdf` - : `testcases/${task.id}/${file.path}` + : file.type === 'application/zip' + ? `statements/attachments/${task.id}.zip` + : `testcases/${task.id}/${file.path}` const url = await getSignedUrl( s3Client, new PutObjectCommand({ diff --git a/src/components/Task/Attachment.tsx b/src/components/Task/Attachment.tsx new file mode 100644 index 00000000..5d7e4aae --- /dev/null +++ b/src/components/Task/Attachment.tsx @@ -0,0 +1,39 @@ +'use client' + +import useSWR from 'swr' + +import zipFetcher from '@/lib/zipFetcher' +import { DownloadIcon } from '@/svg/Illustrations/DownloadIcon' + +export const Attachment = ({ id }: { id: string }) => { + const { data: attachment } = useSWR(`/api/tasks/${id}/attachment`, zipFetcher) + + if (!(attachment instanceof Blob)) { + return null + } + + const downloadAttachment = () => { + const url = URL.createObjectURL(attachment as Blob) + const a = document.createElement('a') + a.href = url + a.download = `${id}.zip` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + } + + return ( +
+

Attachment

+ +
+
+ ) +} diff --git a/src/components/Task/SideBar.tsx b/src/components/Task/SideBar.tsx index 6e0ef736..1b5a5e60 100644 --- a/src/components/Task/SideBar.tsx +++ b/src/components/Task/SideBar.tsx @@ -14,6 +14,8 @@ import { PieChart } from '@/components/common/PieChart' import fetcher from '@/lib/fetcher' import { IListSubmission } from '@/types/submissions' +import { Attachment } from './Attachment' + const NormalTabs = [ { label: 'Statement', @@ -153,6 +155,8 @@ export const SideBar = ({
+ +
( + input: RequestInfo, + init?: RequestInit +): Promise { + try { + const res = await fetch(input, init) + + if (res.headers.get('Content-Type')?.includes('application/zip')) { + return res.blob() + } + return res.json() + } catch (e) { + return Promise.reject(e) + } +} diff --git a/src/svg/Illustrations/DownloadIcon.tsx b/src/svg/Illustrations/DownloadIcon.tsx new file mode 100644 index 00000000..a8ea680c --- /dev/null +++ b/src/svg/Illustrations/DownloadIcon.tsx @@ -0,0 +1,18 @@ +export const DownloadIcon = () => { + return ( + + + + ) +}