Files
hackerspace.sh/lib/actions/classes/definitions.ts
T
Moheeb ZaraandClaude Opus 4.8 dbaae4851b refactor(actions): split classes.ts into a folder (slice 3)
908-line lib/actions/classes.ts → lib/actions/classes/{definitions,sessions,
signups,instructor}.ts ('use server') + _guard.ts (requirePermission/Gate) +
_forms.ts (findSpaceForm/hasFormSubmission) + a plain index.ts barrel. All 14
actions preserved verbatim; lib/actions/index.ts unchanged. Helpers stay
internal (not re-exported). tsc 0, tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 06:01:02 -07:00

154 lines
5.4 KiB
TypeScript

'use server'
import { revalidatePath } from 'next/cache'
import { createAdminClient } from '@/lib/supabase/admin'
import {
logActivity,
parseInput,
} from '@/lib/auth-helpers'
import {
createClassSchema,
updateClassSchema,
classIdSchema,
} from '@/lib/validations'
import { requirePermission } from './_guard'
import { findSpaceForm } from './_forms'
// ─── Class definitions (classes.manage) ──────────────────────────────────────
export async function createClass(input: unknown) {
const gate = await requirePermission('classes.manage')
if (!gate.ok) return { error: gate.error }
const { supabase, member } = gate
const v = parseInput(createClassSchema, input)
if (!v.ok) return { error: v.error }
const c = v.data
if (c.grants_certification_id) {
const { data: cert } = await supabase
.from('certifications')
.select('id')
.eq('id', c.grants_certification_id)
.eq('space_id', member.space_id)
.maybeSingle()
if (!cert) return { error: 'The selected certification was not found in this space.' }
}
if (c.required_form_id) {
const form = await findSpaceForm(createAdminClient(), member.space_id, c.required_form_id)
if (!form) return { error: 'The selected form was not found in this space.' }
if (form.status !== 'published') return { error: 'The required form must be published before it can gate signups.' }
}
const { data, error } = await supabase
.from('classes')
.insert({
space_id: member.space_id,
title: c.title,
description: c.description ?? null,
payment_link: c.payment_link ?? null,
capacity: c.capacity ?? null,
grants_certification_id: c.grants_certification_id ?? null,
required_form_id: c.required_form_id ?? null,
created_by: member.id,
})
.select('id')
.single()
if (error) return { error: error.message }
await logActivity(supabase, member, 'created', 'class', data.id as string, c.title)
revalidatePath('/classes')
return { data: { id: data.id as string } }
}
export async function updateClass(input: unknown) {
const gate = await requirePermission('classes.manage')
if (!gate.ok) return { error: gate.error }
const { supabase, member } = gate
const v = parseInput(updateClassSchema, input)
if (!v.ok) return { error: v.error }
const u = v.data
if (u.grants_certification_id) {
const { data: cert } = await supabase
.from('certifications')
.select('id')
.eq('id', u.grants_certification_id)
.eq('space_id', member.space_id)
.maybeSingle()
if (!cert) return { error: 'The selected certification was not found in this space.' }
}
if (u.required_form_id) {
const form = await findSpaceForm(createAdminClient(), member.space_id, u.required_form_id)
if (!form) return { error: 'The selected form was not found in this space.' }
if (form.status !== 'published') return { error: 'The required form must be published before it can gate signups.' }
}
const patch: Record<string, unknown> = {}
if (u.title !== undefined) patch.title = u.title
if (u.description !== undefined) patch.description = u.description ?? null
if (u.payment_link !== undefined) patch.payment_link = u.payment_link ?? null
if (u.capacity !== undefined) patch.capacity = u.capacity ?? null
if (u.grants_certification_id !== undefined) patch.grants_certification_id = u.grants_certification_id ?? null
if (u.required_form_id !== undefined) patch.required_form_id = u.required_form_id ?? null
if (u.is_active !== undefined) patch.is_active = u.is_active
if (Object.keys(patch).length === 0) return { data: { id: u.classId } }
const { error } = await supabase
.from('classes')
.update(patch)
.eq('id', u.classId)
.eq('space_id', member.space_id)
if (error) return { error: error.message }
await logActivity(supabase, member, 'updated', 'class', u.classId)
revalidatePath('/classes')
return { data: { id: u.classId } }
}
export async function deleteClass(input: unknown) {
const gate = await requirePermission('classes.manage')
if (!gate.ok) return { error: gate.error }
const { supabase, member } = gate
const v = parseInput(classIdSchema, input)
if (!v.ok) return { error: v.error }
const { count } = await supabase
.from('class_sessions')
.select('id', { count: 'exact', head: true })
.eq('class_id', v.data.classId)
if ((count ?? 0) > 0) {
return { error: 'This class has scheduled sessions. Archive it instead of deleting.' }
}
const { error } = await supabase
.from('classes')
.delete()
.eq('id', v.data.classId)
.eq('space_id', member.space_id)
if (error) return { error: error.message }
await logActivity(supabase, member, 'deleted', 'class', v.data.classId)
revalidatePath('/classes')
return { data: { id: v.data.classId } }
}
export async function listClasses() {
const gate = await requirePermission('classes.manage')
if (!gate.ok) return { error: gate.error }
const { supabase, member } = gate
const { data, error } = await supabase
.from('classes')
.select('id, title, description, payment_link, capacity, is_active, grants_certification_id, required_form_id, created_at, updated_at')
.eq('space_id', member.space_id)
.order('is_active', { ascending: false })
.order('title', { ascending: true })
if (error) return { error: error.message }
return { data: data ?? [] }
}