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.048945,
    "output_tokens": 3221
  },
  "model": "claude-sonnet-4-20250514",
  "status": "success",
  "transport": "anthropic_api",
  "latency_ms": 34637,
  "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 with CRUD operations for managing voter data\n// VI: Module API cử tri với các thao tác CRUD để quản lý dữ liệu cử tri  \n// ES: Módulo API de votantes con operaciones CRUD para gestionar datos de votantes\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { createClient } from '@supabase/supabase-js';\nimport { z } from 'zod';\n\n// SPEC: CP-API-01 - Voters table CRUD operations\n// EN: Zod schema for voter data validation\n// VI: Schema Zod để xác thực dữ liệu cử tri\n// ES: Schema Zod para validación de datos de votantes\nconst VoterSchema = z.object({\n  id: z.string().uuid().optional(),\n  first_name: z.string().min(1).max(100),\n  last_name: z.string().min(1).max(100),\n  email: z.string().email().optional().nullable(),\n  phone: z.string().max(20).optional().nullable(),\n  address: z.string().max(255).optional().nullable(),\n  city: z.string().max(100).optional().nullable(),\n  state: z.string().max(50).optional().nullable(),\n  zip_code: z.string().max(10).optional().nullable(),\n  date_of_birth: z.string().optional().nullable(),\n  party_affiliation: z.enum(['Democrat', 'Republican', 'Independent', 'Other']).optional().nullable(),\n  voting_history: z.array(z.string()).optional().nullable(),\n  tags: z.array(z.string()).optional().nullable(),\n  notes: z.string().optional().nullable(),\n  created_at: z.string().optional(),\n  updated_at: z.string().optional()\n});\n\nconst VoterUpdateSchema = VoterSchema.partial();\nconst VotersListSchema = z.object({\n  page: z.number().min(1).default(1),\n  limit: z.number().min(1).max(100).default(20),\n  search: z.string().optional(),\n  party_affiliation: z.enum(['Democrat', 'Republican', 'Independent', 'Other']).optional(),\n  city: z.string().optional(),\n  state: z.string().optional()\n});\n\n// EN: Initialize Supabase client\n// VI: Khởi tạo client Supabase\n// ES: Inicializar cliente Supabase\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: Create a new voter\n// VI: Tạo cử tri mới\n// ES: Crear un nuevo votante\nexport async function createVoter(voterData: unknown) {\n  const validation = VoterSchema.safeParse(voterData);\n  \n  if (!validation.success) {\n    return NextResponse.json(\n      { error: 'Invalid voter data provided' },\n      { status: 400 }\n    );\n  }\n\n  try {\n    const { data, error } = await supabase\n      .from('voters')\n      .insert([validation.data])\n      .select()\n      .single();\n\n    if (error) {\n      return NextResponse.json(\n        { error: 'Failed to create voter' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json(data, { status: 201 });\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Get voter by ID\n// VI: Lấy thông tin cử tri theo ID\n// ES: Obtener votante por ID\nexport async function getVoterById(id: string) {\n  if (!id) {\n    return NextResponse.json(\n      { error: 'Voter ID is required' },\n      { status: 400 }\n    );\n  }\n\n  try {\n    const { data, error } = await supabase\n      .from('voters')\n      .select('*')\n      .eq('id', id)\n      .single();\n\n    if (error || !data) {\n      return NextResponse.json(\n        { error: 'Voter not found' },\n        { status: 404 }\n      );\n    }\n\n    return NextResponse.json(data);\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Get all voters with filtering and pagination\n// VI: Lấy tất cả cử tri với lọc và phân trang\n// ES: Obtener todos los votantes con filtrado y paginación\nexport async function getVoters(queryParams: unknown) {\n  const validation = VotersListSchema.safeParse(queryParams);\n  \n  if (!validation.success) {\n    return NextResponse.json(\n      { error: 'Invalid query parameters' },\n      { status: 400 }\n    );\n  }\n\n  const { page, limit, search, party_affiliation, city, state } = validation.data;\n  const offset = (page - 1) * limit;\n\n  try {\n    let query = supabase\n      .from('voters')\n      .select('*', { count: 'exact' });\n\n    // EN: Apply search filter\n    // VI: Áp dụng bộ lọc tìm kiếm\n    // ES: Aplicar filtro de búsqueda\n    if (search) {\n      query = query.or(`first_name.ilike.%${search}%,last_name.ilike.%${search}%,email.ilike.%${search}%`);\n    }\n\n    // EN: Apply party affiliation filter\n    // VI: Áp dụng bộ lọc theo đảng phái\n    // ES: Aplicar filtro de afiliación partidaria\n    if (party_affiliation) {\n      query = query.eq('party_affiliation', party_affiliation);\n    }\n\n    // EN: Apply city filter\n    // VI: Áp dụng bộ lọc theo thành phố\n    // ES: Aplicar filtro de ciudad\n    if (city) {\n      query = query.eq('city', city);\n    }\n\n    // EN: Apply state filter\n    // VI: Áp dụng bộ lọc theo bang\n    // ES: Aplicar filtro de estado\n    if (state) {\n      query = query.eq('state', state);\n    }\n\n    const { data, error, count } = await query\n      .range(offset, offset + limit - 1)\n      .order('created_at', { ascending: false });\n\n    if (error) {\n      return NextResponse.json(\n        { error: 'Failed to fetch voters' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({\n      data: data || [],\n      pagination: {\n        page,\n        limit,\n        total: count || 0,\n        totalPages: Math.ceil((count || 0) / limit)\n      }\n    });\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Update voter by ID\n// VI: Cập nhật thông tin cử tri theo ID\n// ES: Actualizar votante por ID\nexport async function updateVoter(id: string, voterData: unknown) {\n  if (!id) {\n    return NextResponse.json(\n      { error: 'Voter ID is required' },\n      { status: 400 }\n    );\n  }\n\n  const validation = VoterUpdateSchema.safeParse(voterData);\n  \n  if (!validation.success) {\n    return NextResponse.json(\n      { error: 'Invalid voter data provided' },\n      { status: 400 }\n    );\n  }\n\n  try {\n    const { data, 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 || !data) {\n      return NextResponse.json(\n        { error: 'Failed to update voter or voter not found' },\n        { status: 404 }\n      );\n    }\n\n    return NextResponse.json(data);\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Delete voter by ID\n// VI: Xóa cử tri theo ID\n// ES: Eliminar votante por ID\nexport async function deleteVoter(id: string) {\n  if (!id) {\n    return NextResponse.json(\n      { error: 'Voter ID is required' },\n      { status: 400 }\n    );\n  }\n\n  try {\n    const { error } = await supabase\n      .from('voters')\n      .delete()\n      .eq('id', id);\n\n    if (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  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Bulk operations for voters\n// VI: Các thao tác hàng loạt cho cử tri\n// ES: Operaciones masivas para votantes\nexport async function bulkCreateVoters(votersData: unknown) {\n  if (!Array.isArray(votersData)) {\n    return NextResponse.json(\n      { error: 'Expected array of voter data' },\n      { status: 400 }\n    );\n  }\n\n  const validatedVoters = [];\n  const errors = [];\n\n  for (let i = 0; i < votersData.length; i++) {\n    const validation = VoterSchema.safeParse(votersData[i]);\n    if (validation.success) {\n      validatedVoters.push(validation.data);\n    } else {\n      errors.push({ index: i, error: 'Invalid voter data' });\n    }\n  }\n\n  if (errors.length > 0) {\n    return NextResponse.json(\n      { error: 'Some voter records are invalid', details: errors },\n      { status: 400 }\n    );\n  }\n\n  try {\n    const { data, error } = await supabase\n      .from('voters')\n      .insert(validatedVoters)\n      .select();\n\n    if (error) {\n      return NextResponse.json(\n        { error: 'Failed to create voters' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({\n      message: `Successfully created ${data.length} voters`,\n      data\n    }, { status: 201 });\n  } catch (error) {\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Export functions for use in Next.js API routes\n// VI: Xuất các hàm để sử dụng trong các tuyến đường API Next.js\n// ES: Exportar funciones para usar en rutas API de Next.js\nexport {\n  VoterSchema,\n  VoterUpdateSchema,\n  VotersListSchema\n};\n```\n\nThis voters API module provides:\n\n1. **CRUD Operations**: Create, read, update, and delete voters\n2. **Data Validation**: Zod schemas for request validation with `.safeParse()`\n3. **Filtering & Pagination**: Search, filter by party/location, with pagination\n4. **Bulk Operations**: Support for creating multiple voters at once\n5. **Error Handling**: Consistent error responses with `{ error: 'message' }` format\n6. **Trilingual Comments**: EN/VI/ES comments throughout\n7. **Supabase Integration**: Uses `(supabase as any)` with eslint-disable\n8. **NextResponse**: All responses use `NextResponse.json()`\n\nThe module follows all specified conventions and provides comprehensive voter management functionality for the campaign platform."
}
Task ID: cf8ae4a4-dd62-477e-9aeb-7cdca7251a7b