FOUNDRY
C8 Platform
← Tasks

[API-07] Communications API (send messages, manage templates) (retry 1)

completedcode_genP0

Description

## API-07: Communications API (send messages, manage templates) Section: API Routes | Milestone: M2 | Owner: None ### Failing Tests - **messaging_api_file** (file_exists): File `src/api/messaging.ts` must exist - **messaging_api_handlers** (grep): Pattern `sendMessage|createTemplate|getTemplates` in `src/api/messaging.ts` (need 2+ matches) ### Acceptance Criteria - File `src/api/messaging.ts` must exist - File matching `src/api/messaging.ts` must contain pattern `sendMessage|createTemplate|getTemplates` ### 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' } ### Dependencies - DM-06: FAIL
Assignee
-
Claimed By
Cloud Lu → Claude Sonnet
Created
1d ago
Completed
1d ago

Quality

Overall
93%
Pass
PASS
Graded By
-
Graded At
-

Cost

Input Tokens
-
Output Tokens
-
Est. USD
-

Result

{
  "cost": {
    "input_tokens": 272,
    "estimated_usd": 0.060651,
    "output_tokens": 3989
  },
  "model": "claude-sonnet-4-20250514",
  "status": "success",
  "transport": "anthropic_api",
  "latency_ms": 42301,
  "response_text": "I'll create the Communications API to handle sending messages and managing templates.\n\n```typescript\n// src/api/messaging.ts\n// EN: Communications API for sending messages and managing templates\n// VI: API truyền thông để gửi tin nhắn và quản lý mẫu\n// ES: API de comunicaciones para enviar mensajes y gestionar plantillas\n// SPEC: API-07 - Communications API (send messages, manage templates)\n\nimport { NextRequest, NextResponse } from 'next/server';\nimport { createClient } from '@supabase/supabase-js';\nimport { z } from 'zod';\n\n// EN: Validation schemas for messaging operations\n// VI: Lược đồ xác thực cho các hoạt động nhắn tin\n// ES: Esquemas de validación para operaciones de mensajería\nconst SendMessageSchema = z.object({\n  recipient_id: z.string().uuid(),\n  content: z.string().min(1).max(5000),\n  template_id: z.string().uuid().optional(),\n  type: z.enum(['direct', 'broadcast', 'notification']).default('direct')\n});\n\nconst CreateTemplateSchema = z.object({\n  name: z.string().min(1).max(100),\n  content: z.string().min(1).max(5000),\n  category: z.string().min(1).max(50),\n  variables: z.array(z.string()).optional().default([])\n});\n\nconst GetTemplatesSchema = z.object({\n  category: z.string().optional(),\n  page: z.coerce.number().min(1).default(1),\n  limit: z.coerce.number().min(1).max(100).default(20)\n});\n\n// EN: Send message to user or group\n// VI: Gửi tin nhắn cho người dùng hoặc nhóm\n// ES: Enviar mensaje a usuario o grupo\nexport async function sendMessage(req: NextRequest) {\n  try {\n    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;\n    const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const supabase = createClient(supabaseUrl, supabaseKey) as any;\n\n    const body = await req.json();\n    const validation = SendMessageSchema.safeParse(body);\n    \n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid message data' },\n        { status: 400 }\n      );\n    }\n\n    const { recipient_id, content, template_id, type } = validation.data;\n\n    // EN: Get sender information from session\n    // VI: Lấy thông tin người gửi từ phiên\n    // ES: Obtener información del remitente de la sesión\n    const authHeader = req.headers.get('authorization');\n    if (!authHeader) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const { data: user, error: authError } = await supabase.auth.getUser(\n      authHeader.replace('Bearer ', '')\n    );\n\n    if (authError || !user) {\n      return NextResponse.json(\n        { error: 'Invalid authentication' },\n        { status: 401 }\n      );\n    }\n\n    // EN: If using template, merge content with variables\n    // VI: Nếu sử dụng mẫu, hợp nhất nội dung với biến\n    // ES: Si se usa plantilla, fusionar contenido con variables\n    let finalContent = content;\n    if (template_id) {\n      const { data: template } = await supabase\n        .from('message_templates')\n        .select('content, variables')\n        .eq('id', template_id)\n        .single();\n\n      if (template) {\n        finalContent = template.content;\n        // EN: Replace template variables with actual values\n        // VI: Thay thế biến mẫu bằng giá trị thực tế\n        // ES: Reemplazar variables de plantilla con valores reales\n        template.variables?.forEach((variable: string, index: number) => {\n          finalContent = finalContent.replace(`{{${variable}}}`, body[variable] || '');\n        });\n      }\n    }\n\n    // EN: Create message record\n    // VI: Tạo bản ghi tin nhắn\n    // ES: Crear registro de mensaje\n    const { data: message, error: messageError } = await supabase\n      .from('messages')\n      .insert({\n        sender_id: user.user.id,\n        recipient_id,\n        content: finalContent,\n        template_id,\n        type,\n        status: 'sent',\n        sent_at: new Date().toISOString()\n      })\n      .select()\n      .single();\n\n    if (messageError) {\n      return NextResponse.json(\n        { error: 'Failed to send message' },\n        { status: 500 }\n      );\n    }\n\n    // EN: Create notification for recipient\n    // VI: Tạo thông báo cho người nhận\n    // ES: Crear notificación para el destinatario\n    await supabase\n      .from('notifications')\n      .insert({\n        user_id: recipient_id,\n        type: 'message',\n        title: 'New Message',\n        content: finalContent.substring(0, 100) + (finalContent.length > 100 ? '...' : ''),\n        reference_id: message.id,\n        created_at: new Date().toISOString()\n      });\n\n    return NextResponse.json({\n      message: 'Message sent successfully',\n      data: message\n    });\n\n  } catch (error) {\n    console.error('Send message error:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Create message template for reuse\n// VI: Tạo mẫu tin nhắn để tái sử dụng\n// ES: Crear plantilla de mensaje para reutilizar\nexport async function createTemplate(req: NextRequest) {\n  try {\n    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;\n    const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const supabase = createClient(supabaseUrl, supabaseKey) as any;\n\n    const body = await req.json();\n    const validation = CreateTemplateSchema.safeParse(body);\n    \n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid template data' },\n        { status: 400 }\n      );\n    }\n\n    const { name, content, category, variables } = validation.data;\n\n    // EN: Check authentication\n    // VI: Kiểm tra xác thực\n    // ES: Verificar autenticación\n    const authHeader = req.headers.get('authorization');\n    if (!authHeader) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const { data: user, error: authError } = await supabase.auth.getUser(\n      authHeader.replace('Bearer ', '')\n    );\n\n    if (authError || !user) {\n      return NextResponse.json(\n        { error: 'Invalid authentication' },\n        { status: 401 }\n      );\n    }\n\n    // EN: Create template record\n    // VI: Tạo bản ghi mẫu\n    // ES: Crear registro de plantilla\n    const { data: template, error: templateError } = await supabase\n      .from('message_templates')\n      .insert({\n        name,\n        content,\n        category,\n        variables,\n        created_by: user.user.id,\n        is_active: true,\n        created_at: new Date().toISOString()\n      })\n      .select()\n      .single();\n\n    if (templateError) {\n      return NextResponse.json(\n        { error: 'Failed to create template' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({\n      message: 'Template created successfully',\n      data: template\n    });\n\n  } catch (error) {\n    console.error('Create template error:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Get available message templates with filtering\n// VI: Lấy các mẫu tin nhắn có sẵn với bộ lọc\n// ES: Obtener plantillas de mensajes disponibles con filtrado\nexport async function getTemplates(req: NextRequest) {\n  try {\n    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;\n    const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const supabase = createClient(supabaseUrl, supabaseKey) as any;\n\n    const { searchParams } = new URL(req.url);\n    const queryParams = {\n      category: searchParams.get('category'),\n      page: searchParams.get('page'),\n      limit: searchParams.get('limit')\n    };\n\n    const validation = GetTemplatesSchema.safeParse(queryParams);\n    \n    if (!validation.success) {\n      return NextResponse.json(\n        { error: 'Invalid query parameters' },\n        { status: 400 }\n      );\n    }\n\n    const { category, page, limit } = validation.data;\n\n    // EN: Check authentication\n    // VI: Kiểm tra xác thực\n    // ES: Verificar autenticación\n    const authHeader = req.headers.get('authorization');\n    if (!authHeader) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const { error: authError } = await supabase.auth.getUser(\n      authHeader.replace('Bearer ', '')\n    );\n\n    if (authError) {\n      return NextResponse.json(\n        { error: 'Invalid authentication' },\n        { status: 401 }\n      );\n    }\n\n    // EN: Build query with filters\n    // VI: Xây dựng truy vấn với bộ lọc\n    // ES: Construir consulta con filtros\n    let query = supabase\n      .from('message_templates')\n      .select('*, created_by_user:profiles!created_by(full_name)')\n      .eq('is_active', true);\n\n    if (category) {\n      query = query.eq('category', category);\n    }\n\n    // EN: Add pagination\n    // VI: Thêm phân trang\n    // ES: Agregar paginación\n    const from = (page - 1) * limit;\n    const to = from + limit - 1;\n\n    const { data: templates, error: templatesError, count } = await query\n      .range(from, to)\n      .order('created_at', { ascending: false });\n\n    if (templatesError) {\n      return NextResponse.json(\n        { error: 'Failed to fetch templates' },\n        { status: 500 }\n      );\n    }\n\n    // EN: Get available categories\n    // VI: Lấy các danh mục có sẵn\n    // ES: Obtener categorías disponibles\n    const { data: categories } = await supabase\n      .from('message_templates')\n      .select('category')\n      .eq('is_active', true)\n      .order('category');\n\n    const uniqueCategories = [...new Set(categories?.map(c => c.category) || [])];\n\n    return NextResponse.json({\n      message: 'Templates retrieved successfully',\n      data: {\n        templates: templates || [],\n        pagination: {\n          page,\n          limit,\n          total: count || 0,\n          pages: Math.ceil((count || 0) / limit)\n        },\n        categories: uniqueCategories\n      }\n    });\n\n  } catch (error) {\n    console.error('Get templates error:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n\n// EN: Update template status (activate/deactivate)\n// VI: Cập nhật trạng thái mẫu (kích hoạt/vô hiệu hóa)\n// ES: Actualizar estado de plantilla (activar/desactivar)\nexport async function updateTemplate(req: NextRequest) {\n  try {\n    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;\n    const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const supabase = createClient(supabaseUrl, supabaseKey) as any;\n\n    const { searchParams } = new URL(req.url);\n    const templateId = searchParams.get('id');\n    \n    if (!templateId) {\n      return NextResponse.json(\n        { error: 'Template ID required' },\n        { status: 400 }\n      );\n    }\n\n    const body = await req.json();\n    const { is_active } = body;\n\n    // EN: Check authentication\n    // VI: Kiểm tra xác thực\n    // ES: Verificar autenticación\n    const authHeader = req.headers.get('authorization');\n    if (!authHeader) {\n      return NextResponse.json(\n        { error: 'Authentication required' },\n        { status: 401 }\n      );\n    }\n\n    const { data: user, error: authError } = await supabase.auth.getUser(\n      authHeader.replace('Bearer ', '')\n    );\n\n    if (authError || !user) {\n      return NextResponse.json(\n        { error: 'Invalid authentication' },\n        { status: 401 }\n      );\n    }\n\n    // EN: Update template\n    // VI: Cập nhật mẫu\n    // ES: Actualizar plantilla\n    const { data: template, error: updateError } = await supabase\n      .from('message_templates')\n      .update({ \n        is_active,\n        updated_at: new Date().toISOString()\n      })\n      .eq('id', templateId)\n      .eq('created_by', user.user.id)\n      .select()\n      .single();\n\n    if (updateError) {\n      return NextResponse.json(\n        { error: 'Failed to update template' },\n        { status: 500 }\n      );\n    }\n\n    return NextResponse.json({\n      message: 'Template updated successfully',\n      data: template\n    });\n\n  } catch (error) {\n    console.error('Update template error:', error);\n    return NextResponse.json(\n      { error: 'Internal server error' },\n      { status: 500 }\n    );\n  }\n}\n```\n\nThis Communications API provides:\n\n1. **sendMessage** - Send messages with optional template support\n2. **createTemplate** - Create reusable message templates\n3. **getTemplates** - Retrieve templates with filtering and pagination\n4. **updateTemplate** - Update template status\n\nKey features:\n- Template variable replacement\n- Message notifications\n- Authentication verification\n- Comprehensive error handling\n- Trilingual documentation\n- Zod validation at module level\n\nThe API integrates with the database models for messages, templates, and notifications, supporting the communication workflow in the application."
}
Task ID: c8e4953c-1959-4ba1-bd71-29a1bcef1f66