Skip to content

Commit

Permalink
fix: fix broken photo upload/delete (#1226)
Browse files Browse the repository at this point in the history
  • Loading branch information
vnugent authored Nov 17, 2024
1 parent 46df23c commit 53a63aa
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 229 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@google-cloud/storage": "^6.11.0",
"@google-cloud/storage": "^7.14.0",
"@headlessui/react": "^1.7.15",
"@heroicons/react": "2.0.13",
"@hookform/resolvers": "^3.1.1",
Expand All @@ -38,6 +38,7 @@
"daisyui": "^3.9.4",
"date-fns": "^2.28.0",
"embla-carousel-react": "^8.0.0",
"encoding": "^0.1.13",
"file-saver": "^2.0.5",
"fuse.js": "^6.6.2",
"graphql": "^16.2.0",
Expand Down
6 changes: 3 additions & 3 deletions src/app/api/user/bulkImportTicks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { updateUser } from '@/js/auth/ManagementClient'
import { graphqlClient } from '@/js/graphql/Client'
import { MUTATION_IMPORT_TICKS } from '@/js/graphql/gql/fragments'
import { withUserAuth } from '@/js/auth/withUserAuth'
import { withUserAuth, PREDEFINED_HEADERS } from '@/js/auth/withUserAuth'

export interface Tick {
name: string
Expand Down Expand Up @@ -79,8 +79,8 @@ async function getMPTicks (profileUrl: string): Promise<MPTick[]> {
}

const postHandler = async (req: NextRequest): Promise<any> => {
const uuid = req.headers.get('x-openbeta-user-uuid')
const auth0Userid = req.headers.get('x-auth0-userid')
const uuid = req.headers.get(PREDEFINED_HEADERS.user_uuid)
const auth0Userid = req.headers.get(PREDEFINED_HEADERS.auth0_id)
const payload = await req.json()
const profileUrl: string = payload.profileUrl

Expand Down
65 changes: 65 additions & 0 deletions src/app/api/user/get-signed-url/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { NextRequest, NextResponse } from 'next/server'
import { customAlphabet } from 'nanoid'
import { nolookalikesSafe } from 'nanoid-dictionary'
import { extname } from 'path'

import { withUserAuth, PREDEFINED_HEADERS } from '@/js/auth/withUserAuth'
import { getSignedUrlForUpload } from '@/js/media/storageClient'

export interface MediaPreSignedProps {
url: string
fullFilename: string
}

/**
* Endpoint for getting a signed url to upload a media file to remote cloud storage.
* Usage: `/api/user/get-signed-url?filename=image001.jpg`
* See https://cloud.google.com/storage/docs/access-control/signed-urls
*/
const getHanlder = async (req: NextRequest): Promise<any> => {
try {
const fullFilename = prepareFilenameFromRequest(req)
if (fullFilename == null) {
return NextResponse.json({ status: 400 })
}
const url = await getSignedUrlForUpload(fullFilename)

return NextResponse.json({ url, fullFilename: '/' + fullFilename })
} catch (e) {
console.error('Uploading to media server failed', e)
return NextResponse.json({ status: 500 })
}
}

export const GET = withUserAuth(getHanlder)

/**
* Random filename generator
*/
const safeFilename = (original: string): string => {
return safeRandomString() + extname(original)
}

const safeRandomString = customAlphabet(nolookalikesSafe, 10)

/**
* Construct the S3 path for a given media file and an authenticated user. Format: `u/{user_uuid}/{filename}`.
* It's super important **not** to have the leading slash '/'.
*/
export const prepareFilenameFromRequest = (req: NextRequest): string | null => {
const searchParams = req.nextUrl.searchParams
const filename = searchParams.get('filename')
if (filename == null) {
return null
}

const uuid = req.headers.get(PREDEFINED_HEADERS.user_uuid)
if (uuid == null) {
return null
}

/**
* Important: no starting slash / when working with buckets
*/
return `u/${uuid}/${safeFilename(filename)}`
}
6 changes: 3 additions & 3 deletions src/app/api/user/me/route.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { withUserAuth } from '@/js/auth/withUserAuth'
import { NextRequest, NextResponse } from 'next/server'
import { PREDEFINED_HEADERS, withUserAuth } from '@/js/auth/withUserAuth'
import useUserProfileCmd from '@/js/hooks/useUserProfileCmd'

/**
* Direct `/api/user/me` to `/u/<user_id`
*/
const getHandler = async (req: NextRequest): Promise<any> => {
const uuid = req.headers.get('x-openbeta-user-uuid')
const accessToken = req.headers.get('x-auth0-access-token')
const uuid = req.headers.get(PREDEFINED_HEADERS.user_uuid)
const accessToken = req.headers.get(PREDEFINED_HEADERS.access_token)

if (accessToken == null || uuid == null) {
return NextResponse.json({ status: 500 })
Expand Down
24 changes: 24 additions & 0 deletions src/app/api/user/remove-media/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NextRequest, NextResponse } from 'next/server'

import { withUserAuth } from '@/js/auth/withUserAuth'
import { deleteMediaFromBucket } from '@/js/media/storageClient'
import { prepareFilenameFromRequest } from '../get-signed-url/route'

/**
* Endpoint for removing a media object from remote cloud storage
*/
const postHandler = async (req: NextRequest): Promise<any> => {
try {
const filename = prepareFilenameFromRequest(req)
if (filename == null) {
return NextResponse.json({ status: 400 })
}
await deleteMediaFromBucket(filename)
return NextResponse.json({ status: 200 })
} catch (e) {
console.log('Removing file from media server failed', e)
return NextResponse.json({ status: 500 })
}
}

export const POST = withUserAuth(postHandler)
12 changes: 9 additions & 3 deletions src/js/auth/withUserAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ export const withUserAuth = (handler: Next13APIHandler): Next13APIHandler => {
const session = await getServerSession({ req, ...authOptions })
if (session != null) {
// Passing useful session data downstream
req.headers.set('x-openbeta-user-uuid', session.user.metadata.uuid)
req.headers.set('x-auth0-userid', session.id)
req.headers.set('x-auth0-access-token', session.accessToken)
req.headers.set(PREDEFINED_HEADERS.user_uuid, session.user.metadata.uuid)
req.headers.set(PREDEFINED_HEADERS.auth0_id, session.id)
req.headers.set(PREDEFINED_HEADERS.access_token, session.accessToken)
return await handler(req)
} else {
return NextResponse.json({ status: 401 })
}
}
}

export enum PREDEFINED_HEADERS {
user_uuid = 'x-openbeta-user-uuid',
auth0_id = 'x-auth0-userid', // Example: 'auth0|1237492749372923498234'
access_token = 'x-auth0-access-token' // JWT token
}
6 changes: 3 additions & 3 deletions src/js/userApi/media.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from 'axios'

import { MediaPreSignedProps } from '../../pages/api/media/get-signed-url'
import { MediaPreSignedProps } from '@/app/api/user/get-signed-url/route'

const client = axios.create()

Expand All @@ -12,7 +12,7 @@ const client = axios.create()
*/
export const uploadPhoto = async (filename: string, rawData: ArrayBuffer): Promise<string> => {
const res = await client.get<MediaPreSignedProps>(
'/api/media/get-signed-url?filename=' + encodeURIComponent(filename))
'/api/user/get-signed-url?filename=' + encodeURIComponent(filename))

if (res.data?.url != null && res.data?.fullFilename != null) {
const signedUploadUrl = res.data.url
Expand All @@ -30,7 +30,7 @@ export const uploadPhoto = async (filename: string, rawData: ArrayBuffer): Promi

export const deleteMediaFromStorage = async (filename: string): Promise<void> => {
const res = await client.post(
'/api/media/remove?filename=' + encodeURIComponent(filename),
'/api/user/remove-media?filename=' + encodeURIComponent(filename),
{
headers: {
'Content-Type': 'application/json'
Expand Down
62 changes: 0 additions & 62 deletions src/pages/api/media/get-signed-url.ts

This file was deleted.

40 changes: 0 additions & 40 deletions src/pages/api/media/remove.ts

This file was deleted.

Loading

0 comments on commit 53a63aa

Please sign in to comment.