Skip to content

Commit

Permalink
Attachment Download (#970)
Browse files Browse the repository at this point in the history
* feat: attachment api

* feat: download attachment

* feat: change status code

* refactor: change api fetching

* fix: create task with attachment
  • Loading branch information
MasterIceZ authored Jul 24, 2024
1 parent 71ba7ed commit e1d7955
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 1 deletion.
58 changes: 58 additions & 0 deletions src/app/api/tasks/[id]/attachment/route.ts
Original file line number Diff line number Diff line change
@@ -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 })
}
}
4 changes: 3 additions & 1 deletion src/app/api/tasks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
39 changes: 39 additions & 0 deletions src/components/Task/Attachment.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="my-3 text-gray-500 dark:text-gray-100">
<p className="mb-1">Attachment</p>
<button
className="flex w-full items-center justify-between rounded-md border p-3"
onClick={downloadAttachment}
>
<p className="w-2/3 truncate">{id}.zip</p>
<DownloadIcon />
</button>
<hr className="my-4 hidden md:block" />
</div>
)
}
4 changes: 4 additions & 0 deletions src/components/Task/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -153,6 +155,8 @@ export const SideBar = ({

<hr className="my-4 hidden md:block" />

<Attachment id={task.id} />

<div className="hidden flex-col items-center justify-center md:flex">
<a
target="_blank"
Expand Down
15 changes: 15 additions & 0 deletions src/lib/zipFetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default async function zipFetcher<JSON = unknown>(
input: RequestInfo,
init?: RequestInit
): Promise<JSON | Blob> {
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)
}
}
18 changes: 18 additions & 0 deletions src/svg/Illustrations/DownloadIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const DownloadIcon = () => {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.01465 11.7202C2.01465 11.3612 2.30566 11.0702 2.66465 11.0702H10.4646C10.8236 11.0702 11.1146 11.3612 11.1146 11.7202C11.1146 12.0792 10.8236 12.3702 10.4646 12.3702H2.66465C2.30566 12.3702 2.01465 12.0792 2.01465 11.7202ZM4.15503 6.7106C4.40887 6.45675 4.82043 6.45675 5.07427 6.7106L5.91465 7.55098L5.91465 2.62021C5.91465 2.26123 6.20566 1.97021 6.56465 1.97021C6.92363 1.97021 7.21465 2.26123 7.21465 2.62021L7.21465 7.55098L8.05503 6.7106C8.30887 6.45675 8.72043 6.45675 8.97427 6.7106C9.22811 6.96444 9.22811 7.37599 8.97427 7.62983L7.02427 9.57983C6.90237 9.70173 6.73704 9.77022 6.56465 9.77022C6.39226 9.77022 6.22693 9.70173 6.10503 9.57983L4.15503 7.62983C3.90119 7.37599 3.90119 6.96444 4.15503 6.7106Z"
fill="#3B82F6"
/>
</svg>
)
}

0 comments on commit e1d7955

Please sign in to comment.