Skip to content

Commit

Permalink
draft: add model Media to allow user to select a color during photo c…
Browse files Browse the repository at this point in the history
…reation
  • Loading branch information
claireso committed Sep 21, 2024
1 parent c758e36 commit c238709
Show file tree
Hide file tree
Showing 31 changed files with 963 additions and 589 deletions.
31 changes: 31 additions & 0 deletions app/api/media/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextRequest } from 'next/server'
import { createRouteHandler, withAuth } from '@services/middlewares'
import { pool, queries } from '@services/db'
import { MediaRequestSchema, Media, formatMedia } from '@models'
import uploadFile from '@utils/uploadFile'

// endpoint POST media
const createMedia = async (request: NextRequest) => {
const formData = await request.formData()

const body = Object.fromEntries(formData)

const { file } = MediaRequestSchema.parse(body)

const { filename, width, height } = await uploadFile(file)

const mediaPhoto = {
name: filename,
portrait: height > width,
square: height === width
}

// todo: on error, delete file in the directory
const response = await pool.query(queries.insert_media(), [mediaPhoto.name, mediaPhoto.portrait, mediaPhoto.square])

const media: Media = formatMedia(response.rows[0])

return Response.json(media, { status: 201 })
}

export const POST = createRouteHandler(withAuth, createMedia)
52 changes: 21 additions & 31 deletions app/api/photos/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { NextRequest } from 'next/server'
import { revalidateTag, unstable_cache } from 'next/cache'
import { createRouteHandler, withAuth } from '@services/middlewares'
import { pool, queries } from '@services/db'
import uploadFile from '@utils/uploadFile'
import { Photo, PhotoRequestSchema, formatPhoto, createPhoto as createPhotoHelper } from '@models'
import { Photo, PhotoRequestSchema, formatPhoto as formatPhotoHelper, createPhoto as createPhotoHelper } from '@models'

interface RequestContext {
params: { id: string }
Expand All @@ -32,14 +31,13 @@ const getPhotoById = async (request: NextRequest, { params }: RequestContext) =>

const response = await getCachedPhoto(id)

// const response = await pool.query(queries.get_photo(id))
const photo: Photo = response.rows[0]

if (photo === undefined) {
if (response.rowCount === 0) {
return Response.json({}, { status: 404 })
}

return Response.json(formatPhoto(photo), { status: 200 })
const photo: Photo = formatPhotoHelper(response.rows[0])

return Response.json(photo, { status: 200 })
}

// Endpoint edit photo
Expand All @@ -51,34 +49,26 @@ const editPhoto = async (request: NextRequest, { params }: RequestContext) => {
}

let response = await getCachedPhoto(id)
const photo: Photo = response.rows[0]

if (photo === undefined) {
if (response.rowCount === 0) {
return Response.json({}, { status: 404 })
}

const photo: Photo = formatPhotoHelper(response.rows[0])

const formData = await request.formData()

const body = Object.fromEntries(formData)

const result = PhotoRequestSchema.parse(body)

const { file, ...partialPhoto } = result

const data: Partial<Photo> = {
title: partialPhoto.title ?? photo.title,
description: partialPhoto.description ?? photo.description,
name: photo.name,
position: partialPhoto.position ?? photo.position,
portrait: photo.portrait,
square: photo.square,
color: partialPhoto.color ?? photo.color,
updated_at: new Date()
}

const uploadedFile = file.name ? await uploadFile(file) : undefined

const newPhoto = createPhotoHelper(data, uploadedFile)
const newPhoto: Partial<Photo> = createPhotoHelper({
title: result.title ?? photo.title,
description: result.description ?? photo.description,
media_id: result.media_id ?? photo.media_id,
color: result.color ?? photo.color,
position: result.position ?? photo.position
})

const fields = Object.entries(newPhoto)
.map((entry, index) => `${entry[0]}=($${index + 1})`)
Expand All @@ -88,7 +78,8 @@ const editPhoto = async (request: NextRequest, { params }: RequestContext) => {

revalidateTag('photos')
revalidateTag(`photo_${id}`)
return Response.json(formatPhoto(response.rows[0]), { status: 200 })

return Response.json(formatPhotoHelper(response.rows[0]), { status: 200 })
}

// endpoint delete photo
Expand All @@ -100,17 +91,16 @@ const deletePhoto = async (request: NextRequest, { params }: RequestContext) =>
}

const response = await getCachedPhoto(id)
const photo: Photo = response.rows[0]

if (photo === undefined) {
if (response.rowCount === 0) {
return Response.json({}, { status: 404 })
}

// delete photo from the folder
await unlink(path.resolve('uploads', photo.name))

const photo: Photo = formatPhotoHelper(response.rows[0])
// delete photo from database
await pool.query(queries.delete_photo(id))
// delete photo from the folder
await unlink(path.resolve('uploads', photo.media.name))

revalidateTag('photos')
revalidateTag(`photo_${id}`)
Expand Down
86 changes: 49 additions & 37 deletions app/api/photos/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { IS_NOTIFICATIONS_ENABLED, sendNotification, NOTIFICATION_NEW_PHOTO } fr
import {
PhotoRequestSchema,
Photo,
Media,
Subscription,
Pager,
createPhoto as createPhotoHelper,
Expand All @@ -22,9 +23,11 @@ const getAllPhotos = async (request: NextRequest & { pager: Pager }) => {
})
)

const photos: Array<Photo> = response.rows.map(formatPhotoHelper)

return Response.json(
{
items: response.rows.map(formatPhotoHelper),
items: photos,
pager: request.pager
},
{ status: 200 }
Expand All @@ -39,49 +42,58 @@ const createPhoto = async (request: NextRequest) => {

const result = PhotoRequestSchema.parse(body)

const { file, ...partialPhoto } = result
// idea: check if it is a good idea to add the check in zod media schema
const mediaResponse = await pool.query(queries.get_media(result.media_id))

if (mediaResponse.rowCount === 0) {
return Response.json({ message: 'media_id is required and must exist' }, { status: 422 })
}

const media: Media = mediaResponse.rows[0]

const uploadedFile = await uploadFile(file)
// todo: check if media is already linkedin with a photo

const photo = createPhotoHelper(partialPhoto, uploadedFile)
const data = createPhotoHelper(result)

const response = await pool.query(queries.insert_photo(), [
photo.title,
photo.description,
photo.name,
photo.position,
photo.portrait,
photo.square
const responsePhoto = await pool.query(queries.insert_photo(), [
media.name, // set a value for "name" to keep legacy working (a database migration is required in the future)
data.title,
data.description,
data.position,
data.color,
data.media_id
])

// send web-push notification
if (IS_NOTIFICATIONS_ENABLED) {
const responseForPreviousPhoto = await pool.query(queries.get_previous_photo())
let skipNotification = false

if (responseForPreviousPhoto.rowCount === 1) {
const previousPhoto: Photo = responseForPreviousPhoto.rows[0]
// do not send web push if the previous photo was posted less than 30 minutes ago
if (differenceInMinutes(new Date(), new Date(previousPhoto.created_at)) < 30) {
skipNotification = true
}
}

if (!skipNotification) {
const responseSub = await pool.query(queries.get_subscriptions())
const subscriptions: Subscription[] = responseSub.rows
subscriptions.map(({ subscription, id }) =>
sendNotification(subscription, NOTIFICATION_NEW_PHOTO).catch((err: any) => {
if (err && [410, 404].includes(err.statusCode)) {
pool.query(queries.delete_subscription(id))
}
})
)
}
}
const photo = formatPhotoHelper(responsePhoto.rows[0])

// // send web-push notification
// if (IS_NOTIFICATIONS_ENABLED) {
// const responseForPreviousPhoto = await pool.query(queries.get_previous_photo())
// let skipNotification = false

// if (responseForPreviousPhoto.rowCount === 1) {
// const previousPhoto: Photo = responseForPreviousPhoto.rows[0]
// // do not send web push if the previous photo was posted less than 30 minutes ago
// if (differenceInMinutes(new Date(), new Date(previousPhoto.created_at)) < 30) {
// skipNotification = true
// }
// }

// if (!skipNotification) {
// const responseSub = await pool.query(queries.get_subscriptions())
// const subscriptions: Subscription[] = responseSub.rows
// subscriptions.map(({ subscription, id }) =>
// sendNotification(subscription, NOTIFICATION_NEW_PHOTO).catch((err: any) => {
// if (err && [410, 404].includes(err.statusCode)) {
// pool.query(queries.delete_subscription(id))
// }
// })
// )
// }
// }

revalidateTag('photos')
return Response.json(formatPhotoHelper(response.rows[0]), { status: 201 })
return Response.json(photo, { status: 201 })
}

export const GET = createRouteHandler(withPagination('photos'), getAllPhotos)
Expand Down
Loading

0 comments on commit c238709

Please sign in to comment.