All files / Bandstand/src/app/api/availability route.ts

0% Statements 0/71
0% Branches 0/1
0% Functions 0/1
0% Lines 0/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81                                                                                                                                                                 
export const dynamic = 'force-dynamic'
 
import {z} from 'zod'
import {prisma} from '@/lib/db'
import {requireScheduling, requireSession} from '@/lib/guard'
import {formatDay} from '@/lib/availability'
import {scopeFor} from '@/lib/availability-server'
import {route} from '@/lib/route'
 
const Body = z.object({
  date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
  kind: z.enum(['OUT', 'PM_OUT', 'PREFER_NOT']).nullable(),
  // Admins can fill in for someone (e.g. copying from a text message)
  userId: z.string().optional(),
})
 
export const PUT = route(async (req: Request) => {
  const {user, band, isAdmin} = await requireSession()
  requireScheduling(band)
  const parsed = Body.safeParse(await req.json())
  if (!parsed.success) return new Response('Bad Request', {status: 400})
  const {date, kind} = parsed.data
  const userId = parsed.data.userId ?? user.id
  if (userId !== user.id && !isAdmin)
    return new Response('Forbidden', {status: 403})
  const target =
    userId === user.id
      ? user
      : await prisma.user.findFirst({
          where: {id: userId, memberships: {some: {bandId: band.id}}},
        })
  if (!target) return new Response('Not Found', {status: 404})
  const scope = scopeFor(target, band.id)
  const day = new Date(date + 'T00:00:00Z')
 
  await prisma.$transaction(async (tx) => {
    if (kind)
      await tx.unavailability.upsert({
        where: {userId_scope_date: {userId, scope, date: day}},
        create: {userId, scope, date: day, kind},
        update: {kind},
      })
    else await tx.unavailability.deleteMany({where: {userId, scope, date: day}})
    await tx.user.update({
      where: {id: userId},
      data: {availabilityUpdatedAt: new Date()},
    })
 
    // Shared days show in every band they're in; per-band days in this one
    const bandId = scope ? band.id : null
    // One "updated availability" line per person per hour, not one per tap
    const recent = await tx.activity.findFirst({
      where: {
        bandId,
        userId: user.id,
        action: 'availability.update',
        targetId: userId,
        createdAt: {gt: new Date(Date.now() - 3_600_000)},
      },
    })
    const who = userId === user.id ? 'their' : 'someone else’s'
    if (recent)
      await tx.activity.update({
        where: {id: recent.id},
        data: {createdAt: new Date()},
      })
    else
      await tx.activity.create({
        data: {
          bandId,
          userId: user.id,
          action: 'availability.update',
          targetType: 'availability',
          targetId: userId,
          summary: `updated ${who} availability (starting ${formatDay(date)})`,
        },
      })
  })
  return new Response(null, {status: 204})
})