130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
'use server'
|
|
|
|
import { z } from 'zod'
|
|
import { createClient, createAdminClient } from '@/lib/supabase/server'
|
|
import { revalidatePath } from 'next/cache'
|
|
|
|
const CreateRoundSchema = z.object({
|
|
course_id: z.string().uuid(),
|
|
tee_id: z.string().uuid(),
|
|
name: z.string().max(100).optional(),
|
|
date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
|
|
holes_count: z.union([z.literal(9), z.literal(18)]),
|
|
emails: z.array(z.string().email()).default([]),
|
|
})
|
|
|
|
export type CreateRoundInput = z.infer<typeof CreateRoundSchema>
|
|
|
|
export async function createRoundWithInvites(data: CreateRoundInput) {
|
|
const parsed = CreateRoundSchema.safeParse(data)
|
|
if (!parsed.success) {
|
|
return { error: parsed.error.issues[0]?.message ?? 'Invalid data' }
|
|
}
|
|
|
|
const supabase = await createClient()
|
|
const adminClient = await createAdminClient()
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const db = supabase as any
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const adminDb = adminClient as any
|
|
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser()
|
|
if (!user) return { error: 'Not authenticated' }
|
|
|
|
// Snapshot handicap and compute course handicap
|
|
const { data: profile } = await db
|
|
.from('profiles')
|
|
.select('handicap_index')
|
|
.eq('id', user.id)
|
|
.single()
|
|
|
|
const { data: tee } = await db
|
|
.from('tees')
|
|
.select('course_rating, slope_rating')
|
|
.eq('id', parsed.data.tee_id)
|
|
.single()
|
|
|
|
const { data: course } = await db
|
|
.from('courses')
|
|
.select('par')
|
|
.eq('id', parsed.data.course_id)
|
|
.single()
|
|
|
|
const handicapIndex: number | null = profile?.handicap_index ?? null
|
|
const courseHandicap =
|
|
handicapIndex !== null && tee && course
|
|
? Math.round(handicapIndex * (tee.slope_rating / 113) + (tee.course_rating - course.par))
|
|
: null
|
|
|
|
// Create round
|
|
const { data: round, error: roundError } = await db
|
|
.from('rounds')
|
|
.insert({
|
|
course_id: parsed.data.course_id,
|
|
tee_id: parsed.data.tee_id,
|
|
name: parsed.data.name || null,
|
|
date: parsed.data.date,
|
|
holes_count: parsed.data.holes_count,
|
|
created_by: user.id,
|
|
status: 'lobby',
|
|
})
|
|
.select('id')
|
|
.single()
|
|
if (roundError) return { error: roundError.message }
|
|
|
|
// Add creator as admin player
|
|
await db.from('round_players').insert({
|
|
round_id: round.id,
|
|
user_id: user.id,
|
|
role: 'admin',
|
|
handicap_index: handicapIndex,
|
|
course_handicap: courseHandicap,
|
|
})
|
|
|
|
// Send invites
|
|
const failedInvites: string[] = []
|
|
for (const email of parsed.data.emails) {
|
|
const redirectTo = `${process.env.SITE_URL}/auth/confirm?next=${encodeURIComponent(`/rounds/${round.id}/join`)}`
|
|
const { error } = await adminDb.auth.admin.inviteUserByEmail(email, { redirectTo })
|
|
if (error) {
|
|
failedInvites.push(email)
|
|
} else {
|
|
await db.from('round_invites').insert({
|
|
round_id: round.id,
|
|
email,
|
|
invited_by: user.id,
|
|
})
|
|
}
|
|
}
|
|
|
|
return {
|
|
roundId: round.id as string,
|
|
failedInvites,
|
|
}
|
|
}
|
|
|
|
export async function startRound(roundId: string) {
|
|
const parsed = z.string().uuid().safeParse(roundId)
|
|
if (!parsed.success) return { error: 'Invalid round ID' }
|
|
|
|
const supabase = await createClient()
|
|
const {
|
|
data: { user },
|
|
} = await supabase.auth.getUser()
|
|
if (!user) return { error: 'Not authenticated' }
|
|
|
|
const { error } = await (supabase as any)
|
|
.from('rounds')
|
|
.update({ status: 'active' })
|
|
.eq('id', parsed.data)
|
|
.eq('created_by', user.id)
|
|
.eq('status', 'lobby')
|
|
|
|
if (error) return { error: error.message }
|
|
|
|
revalidatePath(`/rounds/${roundId}`)
|
|
return { success: true }
|
|
}
|