← 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