FOUNDRY
C8 Platform
← 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