/** * Queue API Endpoints * * Provides HTTP interface for queue operations: * - POST /api/queue - Enqueue Instagram URL for processing * - GET /api/queue - List all queue items with optional status filtering */ import { json } from '@sveltejs/kit'; import { queueManager } from '$lib/server/queue/QueueManager'; import { validateInstagramUrl } from '$lib/server/validation/instagram-url'; import { handleApiError } from '$lib/server/api/errorHandler'; import { ValidationError } from '$lib/server/api/errors'; import type { RequestHandler } from './$types'; /** * POST /api/queue - Enqueue Instagram URL * * Body: { url: string } * Returns: { id: string, url: string, status: string, enqueuedAt: string } * * Validates Instagram URL format and enqueues for processing. * Returns 400 for invalid URLs, 500 for server errors. */ export const POST: RequestHandler = async ({ request }) => { try { // Parse JSON body with proper error handling let body; try { body = await request.json(); } catch (jsonError) { throw new ValidationError('Invalid JSON in request body'); } // Validate request body if (!body || typeof body !== 'object') { throw new ValidationError('Request body must be JSON object'); } const { url } = body; // Validate URL presence if (!url || typeof url !== 'string') { throw new ValidationError('URL is required and must be a string'); } // Validate Instagram URL format using utility const validation = validateInstagramUrl(url); if (!validation.valid) { throw new ValidationError(validation.error || 'Invalid Instagram URL'); } // Check for duplicate before enqueueing const existingItem = queueManager.findByUrl(url); if (existingItem) { // Return info response for duplicate return json({ duplicate: true, message: 'This recipe is already in the queue', item: { id: existingItem.id, url: existingItem.url, status: existingItem.status, enqueuedAt: existingItem.enqueuedAt } }, { status: 200 }); // 200 OK, not an error } // Enqueue new URL const queueItem = queueManager.enqueue(url); // Return success response return json({ duplicate: false, item: { id: queueItem.id, url: queueItem.url, status: queueItem.status, enqueuedAt: queueItem.enqueuedAt } }); } catch (error) { return handleApiError(error); } }; /** * GET /api/queue - List queue items * * Query params: * - status?: string - Filter by status (pending, in_progress, success, unhealthy, error) * - limit?: number - Maximum items to return (default: 50, max: 200) * - offset?: number - Pagination offset (default: 0) * * Returns: { items: QueueItem[], total: number, hasMore: boolean } */ export const GET: RequestHandler = async ({ url }) => { try { const searchParams = url.searchParams; // Parse query parameters const statusFilter = searchParams.get('status'); const limitParam = searchParams.get('limit'); const offsetParam = searchParams.get('offset'); // Validate and parse limit let limit = 50; // default if (limitParam) { const parsedLimit = parseInt(limitParam, 10); if (isNaN(parsedLimit) || parsedLimit < 1) { throw new ValidationError('Limit must be a positive integer'); } if (parsedLimit > 200) { throw new ValidationError('Limit cannot exceed 200'); } limit = parsedLimit; } // Validate and parse offset let offset = 0; // default if (offsetParam) { const parsedOffset = parseInt(offsetParam, 10); if (isNaN(parsedOffset) || parsedOffset < 0) { throw new ValidationError('Offset must be a non-negative integer'); } offset = parsedOffset; } // Validate status filter const validStatuses = ['pending', 'in_progress', 'success', 'unhealthy', 'error']; if (statusFilter && !validStatuses.includes(statusFilter)) { throw new ValidationError( `Invalid status filter. Must be one of: ${validStatuses.join(', ')}` ); } // Get all items let items = queueManager.getAll(); const totalCount = items.length; // Apply status filter if (statusFilter) { items = items.filter((item) => item.status === statusFilter); } // Sort by enqueued time (newest first) items.sort((a, b) => new Date(b.enqueuedAt).getTime() - new Date(a.enqueuedAt).getTime()); // Apply pagination const paginatedItems = items.slice(offset, offset + limit); const hasMore = offset + limit < items.length; return json({ items: paginatedItems, total: statusFilter ? items.length : totalCount, hasMore, pagination: { offset, limit, count: paginatedItems.length } }); } catch (error) { return handleApiError(error); } };