Skip to content

Commit

Permalink
chore: set up add finding attachments action
Browse files Browse the repository at this point in the history
  • Loading branch information
matejfalat committed May 3, 2024
1 parent 4a8e83f commit f6699ae
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 3 deletions.
34 changes: 34 additions & 0 deletions src/app/api/upload/finding/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {handleUpload, type HandleUploadBody} from '@vercel/blob/client'
import {NextResponse} from 'next/server'

import {requireServerSession} from '@/server/utils/auth'

export const POST = async (request: Request): Promise<NextResponse> => {
const body = (await request.json()) as HandleUploadBody

try {
const jsonResponse = await handleUpload({
body,
request,
onBeforeGenerateToken: async (_, clientPayload) => {
await requireServerSession()

return {
maximumSizeInBytes: 4 * 1024 * 1024, // 4 MB
tokenPayload: clientPayload,
}
},
onUploadCompleted: async () => {
// ⚠️ This will not work on `localhost` websites,
// Use ngrok or similar to get the full upload
},
})

return NextResponse.json(jsonResponse)
} catch (error) {
return NextResponse.json(
{error: (error as Error).message},
{status: 400}, // The webhook will retry 5 times waiting for a 200
)
}
}
36 changes: 34 additions & 2 deletions src/lib/queries/finding/addFinding.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,50 @@
import {MutateOptions, useMutation} from '@tanstack/react-query'
import {upload} from '@vercel/blob/client'

import {
AddFindingRequest,
AddFinding,
AddFindingResponse,
addFinding,
} from '@/server/actions/finding/addFinding'
import {withApiErrorHandler} from '@/lib/utils/common/error'

type AddFindingRequest = {
finding: AddFinding
attachments?: File[]
}

const addFindingWithAttachments = async ({
finding,
attachments,
}: AddFindingRequest) => {
if (!attachments) {
return addFinding({finding, attachments: []})
}

const blobs = attachments.map((attachment) =>
upload(attachment.name, attachment, {
access: 'public',
handleUploadUrl: '/api/upload/finding',
}),
)

const uploadedBlobs = await Promise.all(blobs)

return addFinding({
finding,
attachments: uploadedBlobs.map((blob) => ({
attachmentUrl: blob.url,
mimeType: blob.contentType,
})),
})
}

export const useAddFinding = (
options?: MutateOptions<AddFindingResponse, Error, AddFindingRequest>,
) => {
return useMutation({
...options,
mutationFn: withApiErrorHandler(addFinding),
mutationFn: withApiErrorHandler(addFindingWithAttachments),
// TODO: invalidate relevant GET queries
})
}
40 changes: 40 additions & 0 deletions src/server/actions/finding/addFindingAttachment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use server'

import {z} from 'zod'

import {db, schema} from '@/server/db'
import {insertFindingAttachmentSchema} from '@/server/db/schema/findingAttachment'
import {requireServerSession} from '@/server/utils/auth'
import {getApiZodError} from '@/lib/utils/common/error'

export type AddFindingAttachmentRequest = z.infer<
typeof insertFindingAttachmentSchema
>

export const addFindingAttachment = async (
request: AddFindingAttachmentRequest,
) => {
const session = await requireServerSession()

const result = insertFindingAttachmentSchema.safeParse(request)

if (!result.success) {
return getApiZodError(result.error)
}

const {findingId} = result.data

const finding = await db.query.findings.findFirst({
where: (findings, {eq}) => eq(findings.id, findingId),
})

if (!finding) {
throw new Error('Finding not found.')
}

if (finding.authorId === session.user.id) {
throw new Error('Only finding author can add attachments.')
}

return db.insert(schema.findingAttachments).values(result.data).returning()
}
2 changes: 1 addition & 1 deletion src/server/db/schema/findingAttachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const findingAttachments = pgTable(
.notNull()
.references(() => findings.id, {onDelete: 'cascade'}),
attachmentUrl: varchar('url', {length: 255}).notNull(),
mimeType: varchar('mimeType', {length: 255}).notNull(),
mimeType: varchar('mimeType', {length: 255}),
createdAt: timestamp('createdAt', {
mode: 'date',
}).default(sql`CURRENT_TIMESTAMP`),
Expand Down

0 comments on commit f6699ae

Please sign in to comment.