generate-attachment-url.ts88 lines · main
1import { createClient } from '@supabase/supabase-js'
2import type { NextApiRequest, NextApiResponse } from 'next'
3import z from 'zod'
4
5import { DASHBOARD_LOG_BUCKET } from '@/components/interfaces/Support/dashboard-logs'
6import apiWrapper from '@/lib/api/apiWrapper'
7import { getUserClaims } from '@/lib/gotrue'
8
9export const maxDuration = 120
10
11const GenerateAttachmentUrlSchema = z.object({
12 filenames: z.array(z.string()),
13 bucket: z
14 .enum(['support-attachments', 'feedback-attachments', DASHBOARD_LOG_BUCKET])
15 .default('support-attachments'),
16})
17
18async function handlePost(req: NextApiRequest, res: NextApiResponse) {
19 const { claims, error: userClaimsError } = await getUserClaims(req.headers.authorization!)
20 if (userClaimsError || !claims) {
21 return res.status(401).json({ error: { message: 'Unauthorized' } })
22 }
23 const userId = claims.sub
24
25 const json = JSON.parse(req.body)
26 const parseResult = GenerateAttachmentUrlSchema.safeParse(json)
27 if (!parseResult.success) {
28 return res.status(400).json({ error: { message: 'Invalid request body' } })
29 }
30 const filenames = parseResult.data.filenames
31
32 const requestedPrefixes = [...new Set(filenames.map((filename) => filename.split('/')[0]))]
33 if (requestedPrefixes.some((prefix) => prefix !== userId)) {
34 return res
35 .status(403)
36 .json({ error: { message: 'Forbidden: Users can only access their own resources' } })
37 }
38
39 const adminBriven = createClient(
40 process.env.NEXT_PUBLIC_SUPPORT_API_URL!,
41 process.env.SUPPORT_BRIVEN_SECRET_KEY!,
42 {
43 auth: {
44 persistSession: false,
45 autoRefreshToken: false,
46 // @ts-expect-error
47 multiTab: false,
48 detectSessionInUrl: false,
49 localStorage: {
50 getItem: (_key: string) => undefined,
51 setItem: (_key: string, _value: string) => {},
52 removeItem: (_key: string) => {},
53 },
54 },
55 }
56 )
57
58 const bucket = parseResult.data.bucket
59 // Create signed URLs for 10 years
60 const { data, error: signedUrlError } = await adminBriven.storage
61 .from(bucket)
62 .createSignedUrls(filenames, 10 * 365 * 24 * 60 * 60)
63 if (signedUrlError) {
64 console.error('Failed to sign URLs for attachments', signedUrlError)
65 return res.status(500).json({ error: { message: 'Failed to sign URLs for attachments' } })
66 }
67 return res.status(200).json(data ? data.map((file) => file.signedUrl) : [])
68}
69
70async function handler(req: NextApiRequest, res: NextApiResponse) {
71 const { method } = req
72
73 switch (method) {
74 case 'POST':
75 return handlePost(req, res)
76 default:
77 res.setHeader('Allow', ['POST'])
78 res.status(405).json({
79 data: null,
80 error: { message: `Method ${method} Not Allowed` },
81 })
82 }
83}
84
85const wrapper = (req: NextApiRequest, res: NextApiResponse) =>
86 apiWrapper(req, res, handler, { withAuth: true })
87
88export default wrapper