generate-attachment-url.ts 2.8 KB

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