FOUNDRY
C8 Platform
← Tasks

[CP-API-01] Voters API module (CRUD for voters table) (retry 1) (retry 2)

completedcode_genP0

Description

## CP-API-01: Voters API module (CRUD for voters table) Section: API | Milestone: M3 | Owner: AG Lu ### Failing Tests - **voters_api_file** (file_exists): File `campaignpro/src/api/voters.ts` must exist - **voters_get** (grep): Pattern `getVoters|listVoters` in `campaignpro/src/api/voters.ts` (need 1+ matches) - **voters_create** (grep): Pattern `createVoter|insertVoter` in `campaignpro/src/api/voters.ts` (need 1+ matches) - **voters_update** (grep): Pattern `updateVoter` in `campaignpro/src/api/voters.ts` (need 1+ matches) ### Acceptance Criteria - File `campaignpro/src/api/voters.ts` must exist - File matching `campaignpro/src/api/voters.ts` must contain pattern `getVoters|listVoters` - File matching `campaignpro/src/api/voters.ts` must contain pattern `createVoter|insertVoter` - File matching `campaignpro/src/api/voters.ts` must contain pattern `updateVoter` ### Codebase Conventions - Trilingual comments: // EN: ... // VI: ... // ES: ... - SPEC section refs in comments - Zod at module level with .safeParse() - (supabase as any) with eslint-disable - NextResponse.json() responses - Error format: { error: 'message' }
Assignee
-
Claimed By
Cloud Lu → Claude Sonnet
Created
1d ago
Completed
1d ago

Quality

Overall
100%
Pass
PASS
Graded By
-
Graded At
-

Cost

Input Tokens
-
Output Tokens
-
Est. USD
-

Result

{
  "cost": {
    "input_tokens": 433,
    "estimated_usd": 0.051954,
    "output_tokens": 3377
  },
  "model": "claude-sonnet-4-20250514",
  "status": "success",
  "transport": "anthropic_api",
  "latency_ms": 36200,
  "response_text": "I'll create the voters API module with CRUD operations for the voters table.\n\n```typescript\n// campaignpro/src/api/voters.ts\n// EN: Voters API module providing CRUD operations for voter management\n// VI: Module API cử tri cung cấp các thao tác CRUD để quản lý cử tri\n// ES: Módulo API de votantes que proporciona operaciones CRUD para gestión de votantes\n// SPEC: CP-API-01 - Voters API module (CRUD for voters table)\n\nimport { createClient } from '@supabase/supabase-js';\nimport { NextResponse } from 'next/server';\nimport { z } from 'zod';\n\n// EN: Supabase client setup with environment variables\n// VI: Thiết lập client Supabase với các biến môi trường\n// ES: Configuración del cliente Supabase con variables de entorno\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!) as any;\n\n// EN: Voter data validation schema\n// VI: Schema xác thực dữ liệu cử tri\n// ES: Esquema de validación de datos del votante\nconst voterSchema = z.object({\n  first_name: z.string().min(1, 'First name is required'),\n  last_name: z.string().min(1, 'Last name is required'),\n  email: z.string().email('Valid email is required').optional(),\n  phone: z.string().optional(),\n  address: z.string().optional(),\n  city: z.string().optional(),\n  state: z.string().optional(),\n  zip_code: z.string().optional(),\n  date_of_birth: z.string().optional(),\n  party_affiliation: z.string().optional(),\n  voter_registration_number: z.string().optional(),\n  voting_district: z.string().optional(),\n  notes: z.string().optional(),\n  tags: z.array(z.string()).optional()\n});\n\nconst voterUpdateSchema = voterSchema.partial();\n\n// EN: Get all voters with optional filtering and pagination\n// VI: Lấy tất cả cử tri với tùy chọn lọc và phân trang\n// ES: Obtener todos los votantes con filtrado opcional y paginación\nexport async function getVoters(params?: {\n  page?: number;\n  limit?: number;\n  search?: string;\n  party_affiliation?: string;\n  voting_district?: string;\n}) {\n  try {\n    const { page = 1, limit = 50, search, party_affiliation, voting_district } = params || {};\n    const offset = (page - 1) * limit;\n\n    let query = supabase\n      .from('voters')\n      .select('*', { count: 'exact' })\n      .range(offset, offset + limit - 1)\n      .order('created_at', { ascending: false });\n\n    // EN: Apply search filter if provided\n    // VI: Áp dụng bộ lọc tìm kiếm nếu được cung cấp\n    // ES: Aplicar filtro de búsqueda si se proporciona\n    if (search) {\n      query = query.or(`first_name.ilike.%${search}%,last_name.ilike.%${search}%,email.ilike.%${search}%`);\n    }\n\n    if (party_affiliation) {\n      query = query.eq('party_affiliation', party_affiliation);\n    }\n\n    if (voting_district) {\n      query = query.eq('voting_district', voting_district);\n    }\n\n    const { data, error, count } = await query;\n\n    if (error) {\n      console.error('Error fetching voters:', error);\n      return NextResponse.json(\n        { error: 'Failed to fetch voters' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({\n      voters: data || [],\n      pagination: {\n        page,\n        limit,\n        total: count || 0,\n        totalPages: Math.ceil((count || 0) / limit)\n      }\n    });\n\n  } catch (error) {\n    console.error('Unexpected error in getVoters:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Alternative function name for listing voters\n// VI: Tên hàm thay thế để liệt kê cử tri\n// ES: Nombre alternativo de función para listar votantes\nexport const listVoters = getVoters;\n\n// EN: Create a new voter record\n// VI: Tạo bản ghi cử tri mới\n// ES: Crear un nuevo registro de votante\nexport async function createVoter(voterData: unknown) {\n  try {\n    // EN: Validate voter data using Zod schema\n    // VI: Xác thực dữ liệu cử tri bằng schema Zod\n    // ES: Validar datos del votante usando esquema Zod\n    const validation = voterSchema.safeParse(voterData);\n    \n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid voter data', details: validation.error.errors },\n        { status: 400 }\n      );\n    }\n\n    const { data, error } = await supabase\n      .from('voters')\n      .insert([validation.data])\n      .select()\n      .single();\n\n    if (error) {\n      console.error('Error creating voter:', error);\n      return NextResponse.json(\n        { error: 'Failed to create voter' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({ voter: data }, { status: 201 });\n\n  } catch (error) {\n    console.error('Unexpected error in createVoter:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Alternative function name for inserting a voter\n// VI: Tên hàm thay thế để chèn cử tri\n// ES: Nombre alternativo de función para insertar votante\nexport const insertVoter = createVoter;\n\n// EN: Update an existing voter record\n// VI: Cập nhật bản ghi cử tri hiện có\n// ES: Actualizar un registro de votante existente\nexport async function updateVoter(voterId: string, voterData: unknown) {\n  try {\n    // EN: Validate partial voter data for updates\n    // VI: Xác thực dữ liệu cử tri một phần cho cập nhật\n    // ES: Validar datos parciales del votante para actualizaciones\n    const validation = voterUpdateSchema.safeParse(voterData);\n    \n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid voter data', details: validation.error.errors },\n        { status: 400 }\n      );\n    }\n\n    // EN: Check if voter exists before updating\n    // VI: Kiểm tra cử tri có tồn tại trước khi cập nhật\n    // ES: Verificar si el votante existe antes de actualizar\n    const { data: existingVoter, error: fetchError } = await supabase\n      .from('voters')\n      .select('id')\n      .eq('id', voterId)\n      .single();\n\n    if (fetchError || !existingVoter) {\n      return NextResponse.json(\n        { error: 'Voter not found' },\n        { status: 404 }\n      );\n    }\n\n    const { data, error } = await supabase\n      .from('voters')\n      .update({\n        ...validation.data,\n        updated_at: new Date().toISOString()\n      })\n      .eq('id', voterId)\n      .select()\n      .single();\n\n    if (error) {\n      console.error('Error updating voter:', error);\n      return NextResponse.json(\n        { error: 'Failed to update voter' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({ voter: data });\n\n  } catch (error) {\n    console.error('Unexpected error in updateVoter:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Get a single voter by ID\n// VI: Lấy một cử tri theo ID\n// ES: Obtener un votante por ID\nexport async function getVoterById(voterId: string) {\n  try {\n    const { data, error } = await supabase\n      .from('voters')\n      .select('*')\n      .eq('id', voterId)\n      .single();\n\n    if (error) {\n      if (error.code === 'PGRST116') {\n        return NextResponse.json(\n          { error: 'Voter not found' },\n          { status: 404 }\n        );\n      }\n      console.error('Error fetching voter:', error);\n      return NextResponse.json(\n        { error: 'Failed to fetch voter' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({ voter: data });\n\n  } catch (error) {\n    console.error('Unexpected error in getVoterById:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Delete a voter record\n// VI: Xóa bản ghi cử tri\n// ES: Eliminar registro de votante\nexport async function deleteVoter(voterId: string) {\n  try {\n    const { error } = await supabase\n      .from('voters')\n      .delete()\n      .eq('id', voterId);\n\n    if (error) {\n      console.error('Error deleting voter:', error);\n      return NextResponse.json(\n        { error: 'Failed to delete voter' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({ message: 'Voter deleted successfully' });\n\n  } catch (error) {\n    console.error('Unexpected error in deleteVoter:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Get voter statistics and counts\n// VI: Lấy thống kê và số lượng cử tri\n// ES: Obtener estadísticas y recuentos de votantes\nexport async function getVoterStats() {\n  try {\n    const { data: totalCount, error: totalError } = await supabase\n      .from('voters')\n      .select('id', { count: 'exact', head: true });\n\n    if (totalError) {\n      console.error('Error fetching voter stats:', totalError);\n      return NextResponse.json(\n        { error: 'Failed to fetch voter statistics' },\n        { status: 500 }\n      );\n    }\n\n    // EN: Get party affiliation breakdown\n    // VI: Lấy phân tích theo đảng phái\n    // ES: Obtener desglose por afiliación partidaria\n    const { data: partyStats, error: partyError } = await supabase\n      .from('voters')\n      .select('party_affiliation')\n      .not('party_affiliation', 'is', null);\n\n    if (partyError) {\n      console.error('Error fetching party stats:', partyError);\n    }\n\n    const partyBreakdown = partyStats?.reduce((acc: Record<string, number>, voter) => {\n      const party = voter.party_affiliation || 'Unknown';\n      acc[party] = (acc[party] || 0) + 1;\n      return acc;\n    }, {}) || {};\n\n    return NextResponse.json({\n      totalVoters: totalCount || 0,\n      partyBreakdown\n    });\n\n  } catch (error) {\n    console.error('Unexpected error in getVoterStats:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n```\n\nThis voters API module provides comprehensive CRUD operations for the voters table, including:\n\n**Key Features:**\n- **getVoters/listVoters**: Retrieve voters with filtering, search, and pagination\n- **createVoter/insertVoter**: Create new voter records with validation\n- **updateVoter**: Update existing voter records\n- **getVoterById**: Retrieve a specific voter by ID\n- **deleteVoter**: Remove voter records\n- **getVoterStats**: Get voter statistics and party breakdown\n\n**Following Conventions:**\n- ✅ Trilingual comments (EN/VI/ES)\n- ✅ SPEC section reference (CP-API-01)\n- ✅ Zod validation with .safeParse()\n- ✅ (supabase as any) with eslint-disable\n- ✅ NextResponse.json() for all responses\n- ✅ Consistent error format: { error: 'message' }\n\nThe module satisfies all acceptance criteria by including the required function patterns and providing a robust API for voter management."
}
Task ID: 7c737896-bf3c-4d3d-8568-9f13857649be