# Hackerspace.sh - API Reference > **Last Updated**: 2026-03-10 > Complete reference for all server actions, database operations, and API patterns. --- ## Table of Contents 1. [Server Actions Overview](#1-server-actions-overview) 2. [Authentication Actions](#2-authentication-actions) 3. [Task Actions](#3-task-actions) 4. [Project Actions](#4-project-actions) 5. [Member Actions](#5-member-actions) 6. [Payment Actions](#6-payment-actions) 7. [Knowledge Base Actions](#7-knowledge-base-actions) 8. [Contact Actions](#8-contact-actions) 9. [Settings Actions](#9-settings-actions) 10. [Data Fetching Patterns](#10-data-fetching-patterns) 11. [Real-time Subscriptions](#11-real-time-subscriptions) 12. [Error Handling](#12-error-handling) --- ## 1. Server Actions Overview All mutations use Next.js Server Actions with `'use server'` directive. Actions: - Authenticate via `supabase.auth.getUser()` - Validate membership and roles - Return `{ data?, error?, success? }` objects - Call `revalidatePath()` for cache invalidation ### Import Pattern ```typescript // In client components import { createTask, claimTask } from '@/lib/actions' import { signIn, signOut } from '@/lib/auth-actions' // Usage const result = await createTask({ title: 'Clean laser', type: 'chore' }) if (result.error) { setError(result.error) } else { // Success - result.data contains the new task } ``` --- ## 2. Authentication Actions **File**: `lib/auth-actions.ts` ### `signIn(email, password)` Authenticates user with email/password. ```typescript async function signIn(email: string, password: string): Promise<{ data?: AuthData error?: string }> ``` **Parameters**: - `email` (string, required): User email - `password` (string, required): User password **Returns**: - `{ data: AuthData }` on success - `{ error: string }` on failure **Example**: ```typescript const result = await signIn('user@example.com', 'password123') if (result.error) { toast.error(result.error) } else { router.push('/dashboard') } ``` --- ### `signUp(formData)` Creates new account and optionally creates/joins a space. ```typescript async function signUp(formData: { email: string password: string fullName: string action: 'create' | 'join' spaceName?: string spaceSlug?: string city?: string inviteCode?: string }): Promise<{ data?: AuthData; error?: string }> ``` **Parameters**: - `email` (string, required): User email - `password` (string, required): Minimum 8 characters - `fullName` (string, required): Display name - `action` ('create' | 'join', required): Space action - `spaceName` (string): Required if action='create' - `spaceSlug` (string): Required if action='create' - `city` (string, optional): Space location - `inviteCode` (string): Required if action='join' **Behavior**: - Creates auth user with metadata - DB trigger `handle_space_signup` creates space/member - Returns session if email confirmation disabled - Otherwise redirects to confirmation page --- ### `signOut()` Signs out current user and redirects to login. ```typescript async function signOut(): Promise ``` **Behavior**: - Calls `supabase.auth.signOut()` - Redirects to `/login` --- ### `getUser()` Gets current authenticated user. ```typescript async function getUser(): Promise ``` --- ### `getCurrentMembership()` Gets current user's space membership with space details. ```typescript async function getCurrentMembership(): Promise ``` --- ### `createSpace(formData)` Creates a new space for authenticated user. ```typescript async function createSpace(formData: { spaceName: string spaceSlug: string spaceCity?: string displayName: string }): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Authenticated (no existing space membership) **Behavior**: - Uses admin client (bypasses RLS) - Creates space with auto-generated invite code - Creates member as admin/plus/current --- ### `joinSpace(formData)` Joins existing space via invite code. ```typescript async function joinSpace(formData: { inviteCode: string displayName: string }): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Authenticated **Behavior**: - Validates invite code - Creates member with status based on `require_approval` --- ## 3. Task Actions **File**: `lib/actions.ts` ### `createTask(formData)` Creates a new task or chore. ```typescript async function createTask(formData: { title: string description?: string type: string // 'task' | 'chore' area?: string recurrence?: string // 'none' | 'daily' | 'weekly' | 'biweekly' | 'monthly' due_date?: string // ISO date string }): Promise<{ data?: Task; error?: string }> ``` **Role Required**: Member **Behavior**: - Sets `requested_by` to current user - Sets `status` to 'open' - Logs activity - Revalidates `/tasks` and `/dashboard` --- ### `claimTask(taskId)` Claims an open task. ```typescript async function claimTask(taskId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Member **Behavior**: - Sets `claimed_by` to current user - Sets `status` to 'claimed' - Logs activity --- ### `completeTask(taskId)` Marks task as completed. ```typescript async function completeTask(taskId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Member (ideally claimant) **Behavior**: - Sets `status` to 'completed' - Sets `completed_at` and `last_done_at` - Logs activity --- ### `deleteTask(taskId)` Deletes a task. ```typescript async function deleteTask(taskId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Member --- ## 4. Project Actions ### `createProject(formData)` Creates a new project. ```typescript async function createProject(formData: { title: string description?: string area?: string tags?: string[] due_date?: string }): Promise<{ data?: Project; error?: string }> ``` **Role Required**: Member **Behavior**: - Sets `status` to 'backlog' - Logs activity --- ### `updateProjectStatus(projectId, status)` Updates project kanban status. ```typescript async function updateProjectStatus( projectId: string, status: string // 'backlog' | 'in_progress' | 'review' | 'done' | 'blocked' ): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Member --- ### `deleteProject(projectId)` Deletes a project. ```typescript async function deleteProject(projectId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Member --- ## 5. Member Actions ### `addMember(formData)` Manually adds a member to the space. ```typescript async function addMember(formData: { display_name: string email: string phone?: string handle?: string tier: string // 'plus' | 'basic' | 'associate' role: string // 'admin' | 'board' | 'treasurer' | 'member' | 'associate' joined_at?: string has_card_access?: boolean }): Promise<{ data?: SpaceMember; error?: string }> ``` **Role Required**: Admin or Board **Note**: Creates member without user_id (offline member). User can claim via signup later. --- ### `updateMember(memberId, updates)` Updates member details. ```typescript async function updateMember(memberId: string, updates: { display_name?: string email?: string phone?: string handle?: string tier?: string role?: string status?: string has_card_access?: boolean payment_status?: string payment_note?: string }): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Admin or Board --- ### `approveMember(memberId)` Approves a pending member. ```typescript async function approveMember(memberId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Admin or Board **Behavior**: - Sets `status` to 'current' - Sets `approved` to true - Logs activity --- ### `removeMember(memberId)` Removes member from space. ```typescript async function removeMember(memberId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Admin only --- ### `importMembers(rows)` Bulk import members from CSV data. ```typescript async function importMembers(rows: Array<{ display_name: string email: string phone?: string tier?: string joined_at?: string last_paid_at?: string has_card_access?: boolean }>): Promise<{ data?: SpaceMember[]; count?: number; error?: string }> ``` **Role Required**: Admin or Board **Behavior**: - Upserts on `(space_id, email)` to avoid duplicates --- ## 6. Payment Actions ### `logCashPayment(formData)` Records a cash payment. ```typescript async function logCashPayment(formData: { amount: number from_note: string member_id?: string transaction_date?: string }): Promise<{ data?: Payment; error?: string }> ``` **Role Required**: Treasurer, Board, or Admin **Behavior**: - Sets `platform` to 'cash' - If `member_id` provided, sets `link_status` to 'linked' - Updates member's `last_paid_at` if linked - Logs activity --- ### `linkPaymentToMember(paymentId, memberId)` Links an unlinked payment to a member. ```typescript async function linkPaymentToMember( paymentId: string, memberId: string ): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Treasurer, Board, or Admin **Behavior**: - Updates payment `member_id` and `link_status` - Updates member `last_paid_at` and `payment_status` --- ### `importPaymentsCsv(rows)` Bulk import payments. ```typescript async function importPaymentsCsv(rows: Array<{ platform: string amount: number from_identifier: string from_note?: string transaction_date: string }>): Promise<{ data?: Payment[]; count?: number; error?: string }> ``` **Role Required**: Treasurer, Board, or Admin --- ## 7. Knowledge Base Actions ### `createKbEntry(formData)` Creates knowledge base entry. ```typescript async function createKbEntry(formData: { title: string content: string description?: string area?: string visibility?: string // 'all_members' | 'board' | 'admin_only' is_pinned?: boolean tags?: string[] icon?: string }): Promise<{ data?: KnowledgeBase; error?: string }> ``` **Role Required**: Member --- ### `updateKbEntry(entryId, updates)` Updates knowledge base entry. ```typescript async function updateKbEntry(entryId: string, updates: { title?: string content?: string area?: string visibility?: string is_pinned?: boolean tags?: string[] }): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Member --- ### `deleteKbEntry(entryId)` Deletes knowledge base entry. ```typescript async function deleteKbEntry(entryId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Board or Admin (via RLS) --- ### `createSecret(formData)` Creates encrypted secret. ```typescript async function createSecret(formData: { title: string value: string description?: string area?: string icon?: string }): Promise<{ data?: Secret; error?: string }> ``` **Role Required**: Admin or Board --- ### `deleteSecret(secretId)` Deletes secret. ```typescript async function deleteSecret(secretId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Admin only --- ### `upsertAreaLead(formData)` Creates or updates area lead assignment. ```typescript async function upsertAreaLead(formData: { area_code: string area_name: string lead_id?: string lead_handle?: string status?: string // 'active' | 'vacant' | 'handoff' }): Promise<{ data?: AreaLead; error?: string }> ``` **Role Required**: Admin or Board **Behavior**: - Upserts on `(space_id, area_code)` --- ## 8. Contact Actions ### `createContact(formData)` Creates a contact entry. ```typescript async function createContact(formData: { name: string contact_type: string // 'vendor' | 'supplier' | 'partner' | 'landlord' | 'city' email?: string phone?: string details?: string note?: string group_label?: string tags?: string[] }): Promise<{ data?: Contact; error?: string }> ``` **Role Required**: Member **Behavior**: - Auto-generates 3-letter code from name --- ### `updateContact(contactId, updates)` Updates contact. ```typescript async function updateContact(contactId: string, updates: { name?: string contact_type?: string email?: string phone?: string details?: string note?: string group_label?: string tags?: string[] }): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Member --- ### `deleteContact(contactId)` Deletes contact. ```typescript async function deleteContact(contactId: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Member --- ## 9. Settings Actions ### `updateSpaceSettings(updates)` Updates space configuration. ```typescript async function updateSpaceSettings(updates: { name?: string slug?: string city?: string require_approval?: boolean public_member_directory?: boolean }): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Admin only --- ### `saveIntegration(platform, config)` Saves integration configuration. ```typescript async function saveIntegration( platform: string, config: Record ): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Admin only **Behavior**: - Upserts on `(space_id, platform)` - Stores config with `_set` indicators for each field --- ### `disconnectIntegration(platform)` Disconnects/clears integration. ```typescript async function disconnectIntegration(platform: string): Promise<{ success?: boolean; error?: string }> ``` **Role Required**: Admin only --- ### `rotateWebhookSecret()` Generates new webhook secret. ```typescript async function rotateWebhookSecret(): Promise<{ secret?: string; error?: string }> ``` **Role Required**: Admin only **Returns**: New `whsec_` prefixed secret --- ## 10. Data Fetching Patterns ### Server Component Fetch ```typescript // In page.tsx (Server Component) import { createClient } from '@/lib/supabase/server' export default async function TasksPage() { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() const { data: member } = await supabase .from('space_members') .select('space_id, display_name, role') .eq('user_id', user!.id) .eq('status', 'current') .single() const { data: tasks } = await supabase .from('tasks') .select('*') .eq('space_id', member.space_id) .order('created_at', { ascending: false }) return } ``` ### Parallel Fetching ```typescript const [ { data: tasks }, { data: projects }, { count: memberCount } ] = await Promise.all([ supabase.from('tasks').select('*').eq('space_id', spaceId), supabase.from('projects').select('*').eq('space_id', spaceId), supabase.from('space_members').select('*', { count: 'exact', head: true }).eq('space_id', spaceId) ]) ``` --- ## 11. Real-time Subscriptions ### Comms Messages ```typescript 'use client' import { createClient } from '@/lib/supabase/client' import { useEffect, useState } from 'react' function useChannelMessages(channelId: string) { const [messages, setMessages] = useState([]) useEffect(() => { const supabase = createClient() // Initial fetch supabase .from('comms_messages') .select('*') .eq('channel_id', channelId) .order('created_at') .then(({ data }) => setMessages(data ?? [])) // Subscribe to new messages const subscription = supabase .channel(`messages:${channelId}`) .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'comms_messages', filter: `channel_id=eq.${channelId}` }, payload => { setMessages(prev => [...prev, payload.new]) }) .subscribe() return () => { subscription.unsubscribe() } }, [channelId]) return messages } ``` --- ## 12. Error Handling ### Action Return Pattern All actions return a consistent shape: ```typescript // Success with data { data: T } // Success without data { success: true } // Error { error: string } ``` ### Client-side Handling ```typescript const [error, setError] = useState('') const [loading, setLoading] = useState(false) async function handleSubmit() { setLoading(true) setError('') const result = await createTask(formData) if (result.error) { setError(result.error) } else { // Success - close modal, refresh data, etc. } setLoading(false) } ``` ### Common Error Messages | Error | Cause | Solution | |-------|-------|----------| | "Not authenticated" | No session | Redirect to login | | "No active membership" | User has no space | Redirect to signup | | "Admin access required" | Role check failed | Show permission error | | "Treasurer access required" | Payment role check | Show permission error | | "Invalid invite code" | Code not found | Ask user to verify | | "A space with this URL slug already exists" | Duplicate slug | Suggest different slug |