FOUNDRY
C8 Platform
← Tasks

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

completedcode_genP1

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 ### Acceptance Criteria - File `campaignpro/src/api/voters.ts` must exist ### 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": 210,
    "estimated_usd": 0.0456,
    "output_tokens": 2998
  },
  "model": "claude-sonnet-4-20250514",
  "status": "success",
  "transport": "anthropic_api",
  "latency_ms": 34758,
  "response_text": "I'll create the voters API module following the codebase conventions and patterns.\n\n```typescript\n// campaignpro/src/api/voters.ts\n\n// EN: Voters API module - CRUD operations for voters table\n// VI: Module API cử tri - các thao tác CRUD cho bảng voters  \n// ES: Módulo API de votantes - operaciones CRUD para tabla voters\n\nimport { z } from 'zod';\nimport { NextRequest, NextResponse } from 'next/server';\nimport { createClient } from '@supabase/supabase-js';\n\n// EN: Voter schema validation - SPEC: CP-DB-02 voters table structure\n// VI: Validation schema cử tri - SPEC: CP-DB-02 cấu trúc bảng voters\n// ES: Validación esquema votante - SPEC: CP-DB-02 estructura tabla voters\nconst VoterSchema = z.object({\n  id: z.string().uuid().optional(),\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  voter_id: z.string().optional(),\n  registration_status: z.enum(['registered', 'unregistered', 'pending']).default('registered'),\n  party_affiliation: z.string().optional(),\n  voting_history: z.array(z.string()).default([]),\n  tags: z.array(z.string()).default([]),\n  notes: z.string().optional(),\n  created_at: z.string().optional(),\n  updated_at: z.string().optional()\n});\n\nconst VoterUpdateSchema = VoterSchema.partial().omit({ id: true, created_at: true });\n\n// EN: Initialize Supabase client - SPEC: CP-API-01 database integration\n// VI: Khởi tạo Supabase client - SPEC: CP-API-01 tích hợp cơ sở dữ liệu\n// ES: Inicializar cliente Supabase - SPEC: CP-API-01 integración base datos\nconst supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;\nconst supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst supabase = createClient(supabaseUrl, supabaseKey) as any;\n\n// EN: GET - Retrieve voters with optional filtering and pagination\n// VI: GET - Lấy danh sách cử tri với lọc và phân trang tùy chọn\n// ES: GET - Obtener votantes con filtrado y paginación opcional\nexport async function GET(request: NextRequest) {\n  try {\n    const { searchParams } = new URL(request.url);\n    const page = parseInt(searchParams.get('page') || '1');\n    const limit = parseInt(searchParams.get('limit') || '50');\n    const search = searchParams.get('search');\n    const city = searchParams.get('city');\n    const state = searchParams.get('state');\n    const party = searchParams.get('party');\n    const status = searchParams.get('status');\n\n    let query = supabase\n      .from('voters')\n      .select('*', { count: 'exact' });\n\n    // EN: Apply search filters - SPEC: CP-API-01 voter search functionality\n    // VI: Áp dụng bộ lọc tìm kiếm - SPEC: CP-API-01 chức năng tìm kiếm cử tri\n    // ES: Aplicar filtros búsqueda - SPEC: CP-API-01 funcionalidad búsqueda votantes\n    if (search) {\n      query = query.or(`first_name.ilike.%${search}%,last_name.ilike.%${search}%,email.ilike.%${search}%`);\n    }\n    \n    if (city) {\n      query = query.eq('city', city);\n    }\n    \n    if (state) {\n      query = query.eq('state', state);\n    }\n    \n    if (party) {\n      query = query.eq('party_affiliation', party);\n    }\n    \n    if (status) {\n      query = query.eq('registration_status', status);\n    }\n\n    // EN: Apply pagination - SPEC: CP-API-01 paginated responses\n    // VI: Áp dụng phân trang - SPEC: CP-API-01 phản hồi được phân trang\n    // ES: Aplicar paginación - SPEC: CP-API-01 respuestas paginadas\n    const from = (page - 1) * limit;\n    const to = from + limit - 1;\n    \n    query = query.range(from, to).order('created_at', { ascending: false });\n\n    const { data: voters, error, count } = await query;\n\n    if (error) {\n      return NextResponse.json({ error: error.message }, { status: 400 });\n    }\n\n    return NextResponse.json({\n      voters,\n      pagination: {\n        page,\n        limit,\n        total: count || 0,\n        totalPages: Math.ceil((count || 0) / limit)\n      }\n    });\n\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Failed to fetch voters' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: POST - Create new voter record\n// VI: POST - Tạo bản ghi cử tri mới  \n// ES: POST - Crear nuevo registro votante\nexport async function POST(request: NextRequest) {\n  try {\n    const body = await request.json();\n    \n    // EN: Validate input data - SPEC: CP-API-01 input validation\n    // VI: Validate dữ liệu đầu vào - SPEC: CP-API-01 validation đầu vào\n    // ES: Validar datos entrada - SPEC: CP-API-01 validación entrada\n    const validation = VoterSchema.safeParse(body);\n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid voter data', details: validation.error.issues },\n        { status: 400 }\n      );\n    }\n\n    const { data: voter, error } = await supabase\n      .from('voters')\n      .insert([validation.data])\n      .select()\n      .single();\n\n    if (error) {\n      return NextResponse.json({ error: error.message }, { status: 400 });\n    }\n\n    return NextResponse.json(voter, { status: 201 });\n\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Failed to create voter' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: PUT - Update existing voter record\n// VI: PUT - Cập nhật bản ghi cử tri hiện tại\n// ES: PUT - Actualizar registro votante existente  \nexport async function PUT(request: NextRequest) {\n  try {\n    const body = await request.json();\n    const { id, ...updateData } = body;\n\n    if (!id) {\n      return NextResponse.json({ error: 'Voter ID is required' }, { status: 400 });\n    }\n\n    // EN: Validate update data - SPEC: CP-API-01 data validation\n    // VI: Validate dữ liệu cập nhật - SPEC: CP-API-01 validation dữ liệu  \n    // ES: Validar datos actualización - SPEC: CP-API-01 validación datos\n    const validation = VoterUpdateSchema.safeParse(updateData);\n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid voter data', details: validation.error.issues },\n        { status: 400 }\n      );\n    }\n\n    const { data: voter, error } = await supabase\n      .from('voters')\n      .update({ ...validation.data, updated_at: new Date().toISOString() })\n      .eq('id', id)\n      .select()\n      .single();\n\n    if (error) {\n      return NextResponse.json({ error: error.message }, { status: 400 });\n    }\n\n    if (!voter) {\n      return NextResponse.json({ error: 'Voter not found' }, { status: 404 });\n    }\n\n    return NextResponse.json(voter);\n\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Failed to update voter' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: DELETE - Remove voter record  \n// VI: DELETE - Xóa bản ghi cử tri\n// ES: DELETE - Eliminar registro votante\nexport async function DELETE(request: NextRequest) {\n  try {\n    const { searchParams } = new URL(request.url);\n    const id = searchParams.get('id');\n\n    if (!id) {\n      return NextResponse.json({ error: 'Voter ID is required' }, { status: 400 });\n    }\n\n    const { error } = await supabase\n      .from('voters')\n      .delete()\n      .eq('id', id);\n\n    if (error) {\n      return NextResponse.json({ error: error.message }, { status: 400 });\n    }\n\n    return NextResponse.json({ message: 'Voter deleted successfully' });\n\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Failed to delete voter' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: GET by ID - Retrieve single voter record\n// VI: GET theo ID - Lấy một bản ghi cử tri  \n// ES: GET por ID - Obtener registro votante único\nexport async function getVoterById(id: string) {\n  try {\n    const { data: voter, error } = await supabase\n      .from('voters')\n      .select('*')\n      .eq('id', id)\n      .single();\n\n    if (error) {\n      throw new Error(error.message);\n    }\n\n    return voter;\n\n  } catch (error) {\n    throw new Error('Failed to fetch voter');\n  }\n}\n\n// EN: Export voter data utilities - SPEC: CP-API-01 data export features\n// VI: Tiện ích xuất dữ liệu cử tri - SPEC: CP-API-01 tính năng xuất dữ liệu\n// ES: Utilidades exportar datos votante - SPEC: CP-API-01 funciones exportar datos\nexport const voterUtils = {\n  validateVoter: (data: unknown) => VoterSchema.safeParse(data),\n  validateVoterUpdate: (data: unknown) => VoterUpdateSchema.safeParse(data)\n};\n```\n\nThis voters API module provides:\n\n1. **Complete CRUD operations** for the voters table\n2. **Zod schema validation** at module level with `.safeParse()`\n3. **Trilingual comments** in EN/VI/ES\n4. **SPEC section references** in comments\n5. **Supabase integration** with `(supabase as any)` pattern and eslint-disable\n6. **NextResponse.json()** for all responses\n7. **Consistent error format** `{ error: 'message' }`\n8. **Search and filtering** capabilities\n9. **Pagination support** for GET requests\n10. **Utility functions** for data validation\n11. **Proper HTTP status codes** for different scenarios\n\nThe file is located at `campaignpro/src/api/voters.ts` as required by the failing test."
}
Task ID: 16e5f4db-3811-4781-a227-cb81df3e66c1