← Tasks
[API-02] Campaigns API (CRUD for campaigns)
completedcode_genP0
Description
## API-02: Campaigns API (CRUD for campaigns)
Section: API Routes | Milestone: M1 | Owner: None
### Failing Tests
- **campaigns_api_file** (file_exists): File `src/api/campaigns.ts` must exist
- **campaigns_api_handlers** (grep): Pattern `createCampaign|getCampaigns|updateCampaign` in `src/api/campaigns.ts` (need 2+ matches)
### Acceptance Criteria
- File `src/api/campaigns.ts` must exist
- File matching `src/api/campaigns.ts` must contain pattern `createCampaign|getCampaigns|updateCampaign`
### 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-01: FAIL
Assignee
-
Claimed By
Cloud Lu → Claude Sonnet
Created
23d ago
Completed
19d ago
Quality
Overall
79%
Pass
PASS
Graded By
-
Graded At
-
Cost
Input Tokens
-
Output Tokens
-
Est. USD
-
Result
{
"cost": {
"input_tokens": 21,
"estimated_usd": 0.073038,
"output_tokens": 4865
},
"model": "claude-sonnet-4-20250514",
"status": "success",
"transport": "anthropic_api",
"latency_ms": 50160,
"response_text": "I'll design a comprehensive Campaigns API with full CRUD operations. Here's the complete implementation:\n\n## API Specification\n\n### Base URL\n```\n/api/v1/campaigns\n```\n\n### Data Models\n\n```typescript\ninterface Campaign {\n id: string;\n name: string;\n description?: string;\n type: 'email' | 'sms' | 'push' | 'social';\n status: 'draft' | 'scheduled' | 'active' | 'paused' | 'completed' | 'cancelled';\n budget?: {\n amount: number;\n currency: string;\n spent?: number;\n };\n targeting: {\n audience_segments: string[];\n demographics?: {\n age_range?: { min: number; max: number; };\n gender?: string[];\n location?: string[];\n };\n custom_filters?: Record<string, any>;\n };\n content: {\n subject?: string;\n message: string;\n media_urls?: string[];\n call_to_action?: {\n text: string;\n url: string;\n };\n };\n schedule?: {\n start_date: string;\n end_date?: string;\n timezone: string;\n frequency?: 'once' | 'daily' | 'weekly' | 'monthly';\n };\n metrics?: {\n sent: number;\n delivered: number;\n opened: number;\n clicked: number;\n converted: number;\n unsubscribed: number;\n };\n created_at: string;\n updated_at: string;\n created_by: string;\n tags?: string[];\n}\n\ninterface CreateCampaignRequest {\n name: string;\n description?: string;\n type: 'email' | 'sms' | 'push' | 'social';\n targeting: Campaign['targeting'];\n content: Campaign['content'];\n schedule?: Campaign['schedule'];\n budget?: Omit<Campaign['budget'], 'spent'>;\n tags?: string[];\n}\n\ninterface UpdateCampaignRequest extends Partial<CreateCampaignRequest> {\n status?: Campaign['status'];\n}\n```\n\n## API Endpoints\n\n### 1. Create Campaign\n```http\nPOST /api/v1/campaigns\n```\n\n**Request Body:**\n```json\n{\n \"name\": \"Summer Sale Email Campaign\",\n \"description\": \"Promotional campaign for summer products\",\n \"type\": \"email\",\n \"targeting\": {\n \"audience_segments\": [\"premium_customers\", \"frequent_buyers\"],\n \"demographics\": {\n \"age_range\": { \"min\": 25, \"max\": 45 },\n \"location\": [\"US\", \"CA\"]\n }\n },\n \"content\": {\n \"subject\": \"🌞 Summer Sale - Up to 50% Off!\",\n \"message\": \"Don't miss our biggest summer sale...\",\n \"call_to_action\": {\n \"text\": \"Shop Now\",\n \"url\": \"https://example.com/summer-sale\"\n }\n },\n \"schedule\": {\n \"start_date\": \"2024-06-01T09:00:00Z\",\n \"end_date\": \"2024-06-30T23:59:59Z\",\n \"timezone\": \"America/New_York\"\n },\n \"budget\": {\n \"amount\": 5000,\n \"currency\": \"USD\"\n },\n \"tags\": [\"summer\", \"sale\", \"email\"]\n}\n```\n\n**Response (201):**\n```json\n{\n \"success\": true,\n \"data\": {\n \"id\": \"camp_1234567890\",\n \"name\": \"Summer Sale Email Campaign\",\n \"description\": \"Promotional campaign for summer products\",\n \"type\": \"email\",\n \"status\": \"draft\",\n \"targeting\": {\n \"audience_segments\": [\"premium_customers\", \"frequent_buyers\"],\n \"demographics\": {\n \"age_range\": { \"min\": 25, \"max\": 45 },\n \"location\": [\"US\", \"CA\"]\n }\n },\n \"content\": {\n \"subject\": \"🌞 Summer Sale - Up to 50% Off!\",\n \"message\": \"Don't miss our biggest summer sale...\",\n \"call_to_action\": {\n \"text\": \"Shop Now\",\n \"url\": \"https://example.com/summer-sale\"\n }\n },\n \"schedule\": {\n \"start_date\": \"2024-06-01T09:00:00Z\",\n \"end_date\": \"2024-06-30T23:59:59Z\",\n \"timezone\": \"America/New_York\"\n },\n \"budget\": {\n \"amount\": 5000,\n \"currency\": \"USD\",\n \"spent\": 0\n },\n \"metrics\": {\n \"sent\": 0,\n \"delivered\": 0,\n \"opened\": 0,\n \"clicked\": 0,\n \"converted\": 0,\n \"unsubscribed\": 0\n },\n \"created_at\": \"2024-01-15T10:30:00Z\",\n \"updated_at\": \"2024-01-15T10:30:00Z\",\n \"created_by\": \"user_123\",\n \"tags\": [\"summer\", \"sale\", \"email\"]\n }\n}\n```\n\n### 2. Get All Campaigns\n```http\nGET /api/v1/campaigns\n```\n\n**Query Parameters:**\n- `page` (number): Page number (default: 1)\n- `limit` (number): Items per page (default: 20, max: 100)\n- `status` (string): Filter by status\n- `type` (string): Filter by campaign type\n- `tags` (string): Comma-separated tags to filter by\n- `search` (string): Search in name and description\n- `sort` (string): Sort field (name, created_at, updated_at)\n- `order` (string): Sort order (asc, desc)\n- `created_after` (string): Filter campaigns created after date\n- `created_before` (string): Filter campaigns created before date\n\n**Example Request:**\n```http\nGET /api/v1/campaigns?page=1&limit=10&status=active&type=email&sort=created_at&order=desc\n```\n\n**Response (200):**\n```json\n{\n \"success\": true,\n \"data\": {\n \"campaigns\": [\n {\n \"id\": \"camp_1234567890\",\n \"name\": \"Summer Sale Email Campaign\",\n \"type\": \"email\",\n \"status\": \"active\",\n \"budget\": {\n \"amount\": 5000,\n \"currency\": \"USD\",\n \"spent\": 1250\n },\n \"metrics\": {\n \"sent\": 10000,\n \"delivered\": 9850,\n \"opened\": 3200,\n \"clicked\": 480,\n \"converted\": 96,\n \"unsubscribed\": 12\n },\n \"created_at\": \"2024-01-15T10:30:00Z\",\n \"updated_at\": \"2024-01-16T14:20:00Z\",\n \"tags\": [\"summer\", \"sale\", \"email\"]\n }\n ],\n \"pagination\": {\n \"current_page\": 1,\n \"total_pages\": 5,\n \"total_items\": 48,\n \"items_per_page\": 10,\n \"has_next\": true,\n \"has_previous\": false\n }\n }\n}\n```\n\n### 3. Get Campaign by ID\n```http\nGET /api/v1/campaigns/{campaign_id}\n```\n\n**Response (200):**\n```json\n{\n \"success\": true,\n \"data\": {\n \"id\": \"camp_1234567890\",\n \"name\": \"Summer Sale Email Campaign\",\n \"description\": \"Promotional campaign for summer products\",\n \"type\": \"email\",\n \"status\": \"active\",\n \"targeting\": {\n \"audience_segments\": [\"premium_customers\", \"frequent_buyers\"],\n \"demographics\": {\n \"age_range\": { \"min\": 25, \"max\": 45 },\n \"location\": [\"US\", \"CA\"]\n }\n },\n \"content\": {\n \"subject\": \"🌞 Summer Sale - Up to 50% Off!\",\n \"message\": \"Don't miss our biggest summer sale...\",\n \"call_to_action\": {\n \"text\": \"Shop Now\",\n \"url\": \"https://example.com/summer-sale\"\n }\n },\n \"schedule\": {\n \"start_date\": \"2024-06-01T09:00:00Z\",\n \"end_date\": \"2024-06-30T23:59:59Z\",\n \"timezone\": \"America/New_York\"\n },\n \"budget\": {\n \"amount\": 5000,\n \"currency\": \"USD\",\n \"spent\": 1250\n },\n \"metrics\": {\n \"sent\": 10000,\n \"delivered\": 9850,\n \"opened\": 3200,\n \"clicked\": 480,\n \"converted\": 96,\n \"unsubscribed\": 12\n },\n \"created_at\": \"2024-01-15T10:30:00Z\",\n \"updated_at\": \"2024-01-16T14:20:00Z\",\n \"created_by\": \"user_123\",\n \"tags\": [\"summer\", \"sale\", \"email\"]\n }\n}\n```\n\n### 4. Update Campaign\n```http\nPUT /api/v1/campaigns/{campaign_id}\n```\n\n**Request Body:**\n```json\n{\n \"name\": \"Updated Summer Sale Campaign\",\n \"status\": \"paused\",\n \"content\": {\n \"subject\": \"🌞 Last Chance - Summer Sale Ends Soon!\",\n \"message\": \"Final days of our summer sale...\",\n \"call_to_action\": {\n \"text\": \"Shop Before It's Gone\",\n \"url\": \"https://example.com/summer-sale\"\n }\n },\n \"tags\": [\"summer\", \"sale\", \"email\", \"urgent\"]\n}\n```\n\n**Response (200):**\n```json\n{\n \"success\": true,\n \"data\": {\n \"id\": \"camp_1234567890\",\n \"name\": \"Updated Summer Sale Campaign\",\n \"status\": \"paused\",\n \"content\": {\n \"subject\": \"🌞 Last Chance - Summer Sale Ends Soon!\",\n \"message\": \"Final days of our summer sale...\",\n \"call_to_action\": {\n \"text\": \"Shop Before It's Gone\",\n \"url\": \"https://example.com/summer-sale\"\n }\n },\n \"updated_at\": \"2024-01-17T09:15:00Z\",\n \"tags\": [\"summer\", \"sale\", \"email\", \"urgent\"]\n }\n}\n```\n\n### 5. Delete Campaign\n```http\nDELETE /api/v1/campaigns/{campaign_id}\n```\n\n**Response (200):**\n```json\n{\n \"success\": true,\n \"message\": \"Campaign deleted successfully\"\n}\n```\n\n## Additional Endpoints\n\n### 6. Bulk Operations\n```http\nPOST /api/v1/campaigns/bulk\n```\n\n**Request Body:**\n```json\n{\n \"operation\": \"update_status\",\n \"campaign_ids\": [\"camp_1\", \"camp_2\", \"camp_3\"],\n \"data\": {\n \"status\": \"paused\"\n }\n}\n```\n\n### 7. Duplicate Campaign\n```http\nPOST /api/v1/campaigns/{campaign_id}/duplicate\n```\n\n**Request Body:**\n```json\n{\n \"name\": \"Copy of Summer Sale Campaign\",\n \"copy_metrics\": false\n}\n```\n\n### 8. Campaign Analytics\n```http\nGET /api/v1/campaigns/{campaign_id}/analytics\n```\n\n**Query Parameters:**\n- `date_from` (string): Start date for analytics\n- `date_to` (string): End date for analytics\n- `granularity` (string): hour, day, week, month\n\n### 9. Campaign Preview\n```http\nPOST /api/v1/campaigns/{campaign_id}/preview\n```\n\n**Request Body:**\n```json\n{\n \"preview_type\": \"email\",\n \"test_recipient\": \"test@example.com\"\n}\n```\n\n## Implementation Example (Node.js/Express)\n\n```javascript\nconst express = require('express');\nconst router = express.Router();\n\n// Middleware for validation\nconst validateCampaign = (req, res, next) => {\n const { name, type, targeting, content } = req.body;\n \n if (!name || !type || !targeting || !content) {\n return res.status(400).json({\n success: false,\n error: {\n code: 'VALIDATION_ERROR',\n message: 'Missing required fields',\n details: {\n required: ['name', 'type', 'targeting', 'content']\n }\n }\n });\n }\n \n next();\n};\n\n// Create Campaign\nrouter.post('/', validateCampaign, async (req, res) => {\n try {\n const campaign = {\n id: generateCampaignId(),\n ...req.body,\n status: 'draft',\n metrics: {\n sent: 0,\n delivered: 0,\n opened: 0,\n clicked: 0,\n converted: 0,\n unsubscribed: 0\n },\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString(),\n created_by: req.user.id\n };\n\n const savedCampaign = await campaignService.create(campaign);\n \n res.status(201).json({\n success: true,\n data: savedCampaign\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: {\n code: 'INTERNAL_ERROR',\n message: error.message\n }\n });\n }\n});\n\n// Get All Campaigns\nrouter.get('/', async (req, res) => {\n try {\n const {\n page = 1,\n limit = 20,\n status,\n type,\n search,\n sort = 'created_at',\n order = 'desc',\n tags\n } = req.query;\n\n const filters = {};\n if (status) filters.status = status;\n if (type) filters.type = type;\n if (tags) filters.tags = { $in: tags.split(',') };\n if (search) {\n filters.$or = [\n { name: { $regex: search, $options: 'i' } },\n { description: { $regex: search, $options: 'i' } }\n ];\n }\n\n const result = await campaignService.findMany({\n filters,\n page: parseInt(page),\n limit: Math.min(parseInt(limit), 100),\n sort: { [sort]: order === 'desc' ? -1 : 1 }\n });\n\n res.json({\n success: true,\n data: result\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: {\n code: 'INTERNAL_ERROR',\n message: error.message\n }\n });\n }\n});\n\n// Get Campaign by ID\nrouter.get('/:id', async (req, res) => {\n try {\n const campaign = await campaignService.findById(req.params.id);\n \n if (!campaign) {\n return res.status(404).json({\n success: false,\n error: {\n code: 'CAMPAIGN_NOT_FOUND',\n message: 'Campaign not found'\n }\n });\n }\n\n res.json({\n success: true,\n data: campaign\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: {\n code: 'INTERNAL_ERROR',\n message: error.message\n }\n });\n }\n});\n\n// Update Campaign\nrouter.put('/:id', async (req, res) => {\n try {\n const campaign = await campaignService.findById(req.params.id);\n \n if (!campaign) {\n return res.status(404).json({\n success: false,\n error: {\n code: 'CAMPAIGN_NOT_FOUND',\n message: 'Campaign not found'\n }\n });\n }\n\n // Check if campaign can be updated\n if (campaign.status === 'completed') {\n return res.status(400).json({\n success: false,\n error: {\n code: 'INVALID_OPERATION',\n message: 'Cannot update completed campaign'\n }\n });\n }\n\n const updates = {\n ...req.body,\n updated_at: new Date().toISOString()\n };\n\n const updatedCampaign = await campaignService.update(req.params.id, updates);\n\n res.json({\n success: true,\n data: updatedCampaign\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: {\n code: 'INTERNAL_ERROR',\n message: error.message\n }\n });\n }\n});\n\n// Delete Campaign\nrouter.delete('/:id', async (req, res) => {\n try {\n const campaign = await campaignService.findById(req.params.id);\n \n if (!campaign) {\n return res.status(404).json({\n success: false,\n error: {\n code: 'CAMPAIGN_NOT_FOUND',\n message: 'Campaign not found'\n }\n });\n }\n\n // Check if campaign can be deleted\n if (campaign.status === 'active') {\n return res.status(400).json({\n success: false,\n error: {\n code: 'INVALID_OPERATION',\n message: 'Cannot delete active campaign. Please pause it first.'\n }\n });\n }\n\n await campaignService.delete(req.params.id);\n\n res.json({\n success: true,\n message: 'Campaign deleted successfully'\n });\n } catch (error) {\n res.status(500).json({\n success: false,\n error: {\n code: 'INTERNAL_ERROR',\n message: error.message\n }\n });\n }\n});\n\nmodule.exports = router;\n```\n\n## Error Responses\n\n### Validation Error (400)\n```json\n{\n \"success\": false,\n \"error\": {\n \"code\": \"VALIDATION_ERROR\",\n \"message\": \"Invalid request data\",\n \"details\": {\n \"field\": \"email\",\n \"reason\": \"Invalid email format\"\n }\n }\n}\n```\n\n### Not Found (404)\n```json\n{\n \"success\": false,\n \"error\": {\n \"code\": \"CAMPAIGN_NOT_FOUND\",\n \"message\": \"Campaign not found\"\n }\n}\n```\n\n### Conflict (409)\n```json\n{\n \"success\": false,\n \"error\": {\n \"code\": \"CAMPAIGN_CONFLICT\",\n \"message\": \"Campaign with this name already exists\"\n }\n}\n```\n\nThis comprehensive Campaigns API provides full CRUD functionality with proper validation, error handling, filtering, pagination, and additional features like bulk operations and analytics."
}Task ID: ef55d91e-6f11-4fd4-8544-56a038056cd6