diff --git a/CLAUDE.md b/CLAUDE.md index 3408c14..e594f6a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -213,9 +213,7 @@ Located at `apps/api-service/.env` - used ONLY when running `pnpm dev:api` local ### Protected Endpoints (API Key Required) -- `POST /api/generate` - Generate images from text + optional reference images - `POST /api/text-to-image` - Generate images from text only (JSON) -- `POST /api/enhance` - Enhance and optimize text prompts - `GET /api/images` - List generated images **Authentication**: All protected endpoints require `X-API-Key` header @@ -245,10 +243,13 @@ Located at `apps/api-service/.env` - used ONLY when running `pnpm dev:api` local ```bash # Image generation with project key -curl -X POST http://localhost:3000/api/generate \ +curl -X POST http://localhost:3000/api/text-to-image \ -H "X-API-Key: YOUR_PROJECT_KEY" \ - -F "prompt=a sunset" \ - -F "filename=test_image" + -H "Content-Type: application/json" \ + -d '{ + "prompt": "a sunset", + "filename": "test_image" + }' ``` ### Key Management diff --git a/README.md b/README.md index d01999b..d6d1a63 100644 --- a/README.md +++ b/README.md @@ -144,15 +144,14 @@ See individual app README files for specific environment variables. ### Generate Image ```bash -# Basic text-to-image -curl -X POST http://localhost:3000/api/generate \ - -F "prompt=A magical forest with glowing mushrooms" \ - -F "filename=magical_forest" - -# With reference images -curl -X POST http://localhost:3000/api/generate \ - -F "prompt=Character in medieval armor like the reference" \ - -F "referenceImages=@./reference.jpg" +# Text-to-image +curl -X POST http://localhost:3000/api/text-to-image \ + -H "Content-Type: application/json" \ + -H "X-API-Key: YOUR_API_KEY" \ + -d '{ + "prompt": "A magical forest with glowing mushrooms", + "filename": "magical_forest" + }' ``` See `apps/api-service/README.md` for detailed API documentation. diff --git a/apps/api-service/src/app.ts b/apps/api-service/src/app.ts index 7adeef5..e4031f4 100644 --- a/apps/api-service/src/app.ts +++ b/apps/api-service/src/app.ts @@ -2,8 +2,6 @@ import express, { Application } from 'express'; import cors from 'cors'; import { config } from 'dotenv'; import { Config } from './types/api'; -import { generateRouter } from './routes/generate'; -import { enhanceRouter } from './routes/enhance'; import { textToImageRouter } from './routes/textToImage'; import { imagesRouter } from './routes/images'; import bootstrapRoutes from './routes/bootstrap'; @@ -72,9 +70,7 @@ export const createApp = (): Application => { endpoints: { 'GET /health': 'Health check', 'GET /api/info': 'API information', - 'POST /api/generate': 'Generate images from text prompt with optional reference images', 'POST /api/text-to-image': 'Generate images from text prompt only (JSON)', - 'POST /api/enhance': 'Enhance and optimize prompts for better image generation', }, limits: { maxFileSize: `${appConfig.maxFileSize / (1024 * 1024)}MB`, @@ -120,8 +116,6 @@ export const createApp = (): Application => { app.use('/api/admin/keys', adminKeysRoutes); // Protected API routes (require valid API key) - app.use('/api', generateRouter); - app.use('/api', enhanceRouter); app.use('/api', textToImageRouter); app.use('/api', imagesRouter); diff --git a/apps/api-service/src/routes/enhance.ts b/apps/api-service/src/routes/enhance.ts deleted file mode 100644 index 67e5c40..0000000 --- a/apps/api-service/src/routes/enhance.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { Request, Response, Router } from 'express'; -import type { Router as RouterType } from 'express'; -import { PromptEnhancementService } from '../services/promptEnhancement'; -import { asyncHandler } from '../middleware/errorHandler'; -import { PromptEnhancementRequest, PromptEnhancementResponse } from '../types/api'; -import { body, validationResult } from 'express-validator'; -import { validateApiKey } from '../middleware/auth/validateApiKey'; -import { rateLimitByApiKey } from '../middleware/auth/rateLimiter'; - -export const enhanceRouter: RouterType = Router(); - -let promptEnhancementService: PromptEnhancementService; - -const validateEnhanceRequest = [ - body('prompt') - .notEmpty() - .withMessage('Prompt is required') - .isLength({ min: 1, max: 5000 }) - .withMessage('Prompt must be between 1 and 5000 characters') - .trim(), - - body('options.imageStyle') - .optional() - .isIn(['photorealistic', 'illustration', 'minimalist', 'sticker', 'product', 'comic']) - .withMessage('Invalid image style'), - - body('options.aspectRatio') - .optional() - .isIn(['square', 'portrait', 'landscape', 'wide', 'ultrawide']) - .withMessage('Invalid aspect ratio'), - - body('options.mood') - .optional() - .isLength({ max: 100 }) - .withMessage('Mood description too long') - .trim(), - - body('options.lighting') - .optional() - .isLength({ max: 100 }) - .withMessage('Lighting description too long') - .trim(), - - body('options.cameraAngle') - .optional() - .isLength({ max: 100 }) - .withMessage('Camera angle description too long') - .trim(), - - body('options.outputFormat') - .optional() - .isIn(['text', 'markdown', 'detailed']) - .withMessage('Invalid output format'), - - body('options.negativePrompts') - .optional() - .isArray({ max: 10 }) - .withMessage('Too many negative prompts (max 10)'), - - body('options.negativePrompts.*') - .optional() - .isLength({ max: 100 }) - .withMessage('Negative prompt too long') - .trim(), -]; - -const logEnhanceRequest = (req: Request, _res: Response, next: Function) => { - const timestamp = new Date().toISOString(); - const requestId = req.requestId; - const { prompt, options } = req.body as PromptEnhancementRequest; - - console.log(`[${timestamp}] [${requestId}] POST /api/enhance`); - console.log(`[${timestamp}] [${requestId}] Prompt length: ${prompt?.length || 0} characters`); - console.log(`[${timestamp}] [${requestId}] Options:`, options || 'none'); - - next(); -}; - -enhanceRouter.post( - '/enhance', - // Authentication middleware - validateApiKey, - rateLimitByApiKey, - - validateEnhanceRequest, - logEnhanceRequest, - - asyncHandler(async (req: Request, res: Response) => { - if (!promptEnhancementService) { - const apiKey = process.env['GEMINI_API_KEY']; - if (!apiKey) { - return res.status(500).json({ - success: false, - originalPrompt: '', - error: 'Server configuration error: GEMINI_API_KEY not configured', - } as PromptEnhancementResponse); - } - promptEnhancementService = new PromptEnhancementService(apiKey); - } - - const errors = validationResult(req); - if (!errors.isEmpty()) { - console.log( - `[${new Date().toISOString()}] [${req.requestId}] Validation failed:`, - errors.array(), - ); - return res.status(400).json({ - success: false, - originalPrompt: req.body.prompt || '', - error: `Validation failed: ${errors - .array() - .map((e) => e.msg) - .join(', ')}`, - } as PromptEnhancementResponse); - } - - const timestamp = new Date().toISOString(); - const requestId = req.requestId; - const { prompt, options } = req.body as PromptEnhancementRequest; - - console.log(`[${timestamp}] [${requestId}] Starting prompt enhancement`); - - try { - // Extract orgId and projectId from validated API key - const orgId = req.apiKey?.organizationSlug || 'unknown'; - const projectId = req.apiKey?.projectSlug || 'unknown'; - - const result = await promptEnhancementService.enhancePrompt(prompt, options || {}, { - orgId, - projectId, - }); - - console.log(`[${timestamp}] [${requestId}] Enhancement completed:`, { - success: result.success, - detectedLanguage: result.detectedLanguage, - appliedTemplate: result.appliedTemplate, - enhancementsCount: result.metadata?.enhancements.length || 0, - hasError: !!result.error, - }); - - if (result.success) { - const successResponse: PromptEnhancementResponse = { - success: true, - originalPrompt: result.originalPrompt, - enhancedPrompt: result.enhancedPrompt!, - ...(result.detectedLanguage && { - detectedLanguage: result.detectedLanguage, - }), - ...(result.appliedTemplate && { - appliedTemplate: result.appliedTemplate, - }), - ...(result.metadata && { metadata: result.metadata }), - }; - - console.log(`[${timestamp}] [${requestId}] Sending success response`); - return res.status(200).json(successResponse); - } else { - const errorResponse: PromptEnhancementResponse = { - success: false, - originalPrompt: result.originalPrompt, - error: result.error || 'Unknown error occurred', - }; - - console.log(`[${timestamp}] [${requestId}] Sending error response: ${result.error}`); - return res.status(500).json(errorResponse); - } - } catch (error) { - console.error(`[${timestamp}] [${requestId}] Unhandled error in enhance endpoint:`, error); - - const errorResponse: PromptEnhancementResponse = { - success: false, - originalPrompt: prompt, - error: error instanceof Error ? error.message : 'Unknown error occurred', - }; - - return res.status(500).json(errorResponse); - } - }), -); diff --git a/apps/api-service/src/routes/generate.ts b/apps/api-service/src/routes/generate.ts deleted file mode 100644 index f16fc0a..0000000 --- a/apps/api-service/src/routes/generate.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Response, Router } from 'express'; -import type { Router as RouterType } from 'express'; -import { ImageGenService } from '../services/ImageGenService'; -import { uploadReferenceImages, handleUploadErrors } from '../middleware/upload'; -import { validateGenerateRequest, logRequestDetails } from '../middleware/validation'; -import { autoEnhancePrompt, logEnhancementResult } from '../middleware/promptEnhancement'; -import { asyncHandler } from '../middleware/errorHandler'; -import { validateApiKey } from '../middleware/auth/validateApiKey'; -import { requireProjectKey } from '../middleware/auth/requireProjectKey'; -import { rateLimitByApiKey } from '../middleware/auth/rateLimiter'; -import { GenerateImageResponse } from '../types/api'; -// Create router -export const generateRouter: RouterType = Router(); - -// Initialize ImageGenService (will be created in the route handler to avoid circular dependency) -let imageGenService: ImageGenService; - -/** - * POST /api/generate - Generate image from text prompt with optional reference images - */ -generateRouter.post( - '/generate', - // Authentication middleware - validateApiKey, - requireProjectKey, - rateLimitByApiKey, - - // File upload middleware - uploadReferenceImages, - handleUploadErrors, - - // Validation middleware - logRequestDetails, - validateGenerateRequest, - - // Auto-enhancement middleware (optional) - autoEnhancePrompt, - logEnhancementResult, - - // Main handler - asyncHandler(async (req: any, res: Response) => { - // Initialize service if not already done - if (!imageGenService) { - const apiKey = process.env['GEMINI_API_KEY']; - if (!apiKey) { - return res.status(500).json({ - success: false, - message: 'Server configuration error', - error: 'GEMINI_API_KEY not configured', - } as GenerateImageResponse); - } - imageGenService = new ImageGenService(apiKey); - } - - const timestamp = new Date().toISOString(); - const requestId = req.requestId; - const { prompt, filename, aspectRatio, meta } = req.body; - const files = (req.files as Express.Multer.File[]) || []; - - // Extract org/project slugs from validated API key - const orgId = req.apiKey?.organizationSlug || undefined; - const projectId = req.apiKey?.projectSlug!; // Guaranteed by requireProjectKey middleware - - console.log( - `[${timestamp}] [${requestId}] Starting image generation process for org:${orgId}, project:${projectId}`, - ); - - try { - // Validate reference images if provided - if (files.length > 0) { - const validation = ImageGenService.validateReferenceImages(files); - if (!validation.valid) { - console.log( - `[${timestamp}] [${requestId}] Reference image validation failed: ${validation.error}`, - ); - return res.status(400).json({ - success: false, - message: 'Reference image validation failed', - error: validation.error, - } as GenerateImageResponse); - } - - console.log(`[${timestamp}] [${requestId}] Reference images validation passed`); - } - - // Convert files to reference images - const referenceImages = - files.length > 0 ? ImageGenService.convertFilesToReferenceImages(files) : undefined; - - // Generate the image - console.log(`[${timestamp}] [${requestId}] Calling ImageGenService.generateImage()`); - - const result = await imageGenService.generateImage({ - prompt, - filename, - ...(aspectRatio && { aspectRatio }), - orgId, - projectId, - ...(referenceImages && { referenceImages }), - ...(meta && { meta }), - }); - - // Log the result - console.log(`[${timestamp}] [${requestId}] Image generation completed:`, { - success: result.success, - model: result.model, - filename: result.filename, - hasError: !!result.error, - }); - - // Send response - if (result.success) { - const successResponse: GenerateImageResponse = { - success: true, - message: 'Image generated successfully', - data: { - filename: result.filename!, - filepath: result.filepath!, - ...(result.description && { description: result.description }), - model: result.model, - generatedAt: timestamp, - ...(req.enhancedPrompt && { - promptEnhancement: { - originalPrompt: req.originalPrompt, - enhancedPrompt: req.enhancedPrompt, - detectedLanguage: req.enhancementMetadata?.detectedLanguage, - appliedTemplate: req.enhancementMetadata?.appliedTemplate, - enhancements: req.enhancementMetadata?.enhancements || [], - }, - }), - }, - }; - - console.log(`[${timestamp}] [${requestId}] Sending success response`); - return res.status(200).json(successResponse); - } else { - const errorResponse: GenerateImageResponse = { - success: false, - message: 'Image generation failed', - error: result.error || 'Unknown error occurred', - }; - - console.log(`[${timestamp}] [${requestId}] Sending error response: ${result.error}`); - return res.status(500).json(errorResponse); - } - } catch (error) { - console.error(`[${timestamp}] [${requestId}] Unhandled error in generate endpoint:`, error); - - const errorResponse: GenerateImageResponse = { - success: false, - message: 'Image generation failed', - error: error instanceof Error ? error.message : 'Unknown error occurred', - }; - - return res.status(500).json(errorResponse); - } - }), -); diff --git a/docs/api/api.rest b/docs/api/api.rest index 245c92a..a9a17f2 100644 --- a/docs/api/api.rest +++ b/docs/api/api.rest @@ -46,26 +46,6 @@ DELETE {{base}}/api/admin/keys/KEY_ID_HERE X-API-Key: {{masterKey}} -### Enhance Prompt (Requires API Key) - -POST {{base}}/api/enhance -Content-Type: application/json -X-API-Key: {{apiKey}} - -{ - "prompt": "Два мага сражаются в снежном лесу. У одного из них в руках посох, из которого вырывается молния, а другой маг защищается щитом из льда. Вокруг них падают снежинки, и на заднем плане видны заснеженные деревья и горы.", - "options": { - "imageStyle": "photorealistic", - "aspectRatio": "landscape", - "mood": "serene and peaceful", - "lighting": "golden hour", - "cameraAngle": "wide shot", - "outputFormat": "detailed", - "negativePrompts": ["blurry", "low quality"] - } -} - - ### Generate Image from Text (Requires API Key) POST {{base}}/api/text-to-image @@ -78,37 +58,7 @@ X-API-Key: {{apiKey}} } -### Generate Image with Files (Requires API Key) - -POST {{base}}/api/generate -Content-Type: multipart/form-data; boundary=----WebKitFormBoundary -X-API-Key: {{apiKey}} - -------WebKitFormBoundary -Content-Disposition: form-data; name="prompt" - -A majestic dragon soaring through a crystal cave filled with glowing blue crystals, sunbeams piercing through cracks in the ceiling creating dramatic lighting, highly detailed fantasy art style -------WebKitFormBoundary -Content-Disposition: form-data; name="filename" - -dragon-crystal-cave -------WebKitFormBoundary -Content-Disposition: form-data; name="autoEnhance" - -true -------WebKitFormBoundary -Content-Disposition: form-data; name="enhancementOptions" - -{"imageStyle":"illustration","aspectRatio":"landscape","mood":"mystical and dramatic","lighting":"magical glow with sunbeams","cameraAngle":"wide shot","negativePrompts":["blurry","low quality","amateur"]} -------WebKitFormBoundary -Content-Disposition: form-data; name="referenceImages"; filename="reference.jpg" -Content-Type: image/jpeg - -< ./reference.jpg -------WebKitFormBoundary-- - - -### Generate Image - Text to Image +### Generate Image - Text to Image (alternative format) POST http://localhost:3000/api/text-to-image Content-Type: application/json X-API-Key: bnt_61ba018f01474491cbaacec4509220d7154fffcd011f005eece4dba7889fba99 diff --git a/test-api.sh b/test-api.sh index d10f41d..578bb4c 100755 --- a/test-api.sh +++ b/test-api.sh @@ -16,17 +16,11 @@ echo "2. Testing API info endpoint..." curl -s "$BASE_URL/api/info" | jq '.' || echo "API info endpoint failed" echo -e "\n" -# Test 3: Generate endpoint validation (should fail without prompt) -echo "3. Testing generate endpoint validation..." -curl -s -X POST "$BASE_URL/api/generate" \ - -F "filename=test" | jq '.' || echo "Generate endpoint validation test failed" -echo -e "\n" - -# Test 4: Generate endpoint validation (should fail without API key) -echo "4. Testing generate endpoint with prompt (should fail without valid API key)..." -curl -s -X POST "$BASE_URL/api/generate" \ - -F "prompt=A simple test image" \ - -F "filename=test" | jq '.' || echo "Generate endpoint test failed" +# Test 3: Text-to-image endpoint validation (should fail without API key) +echo "3. Testing text-to-image endpoint (should fail without valid API key)..." +curl -s -X POST "$BASE_URL/api/text-to-image" \ + -H "Content-Type: application/json" \ + -d '{"prompt": "A simple test image", "filename": "test"}' | jq '.' || echo "Text-to-image endpoint test failed" echo -e "\n" echo "✅ API tests completed!" \ No newline at end of file diff --git a/test-enhance-api.sh b/test-enhance-api.sh deleted file mode 100755 index fb4133d..0000000 --- a/test-enhance-api.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/bash - -# Test script for Banatie Prompt Enhancement API endpoints -BASE_URL="http://localhost:3000" - -echo "🧪 Testing Banatie Prompt Enhancement API" -echo "=========================================" - -# Test 1: Health check first -echo "1. Testing health endpoint..." -curl -s "$BASE_URL/health" | jq '.status' || echo "Health endpoint failed" -echo -e "\n" - -# Test 2: API info (should now include enhance endpoint) -echo "2. Testing API info endpoint..." -curl -s "$BASE_URL/api/info" | jq '.endpoints' || echo "API info endpoint failed" -echo -e "\n" - -# Test 3: Basic prompt enhancement -echo "3. Testing basic prompt enhancement..." -curl -s -X POST "$BASE_URL/api/enhance" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "cat" - }' | jq '.' || echo "Basic enhancement test failed" -echo -e "\n" - -# Test 4: Prompt enhancement with options -echo "4. Testing prompt enhancement with options..." -curl -s -X POST "$BASE_URL/api/enhance" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "beautiful sunset", - "options": { - "imageStyle": "photorealistic", - "aspectRatio": "landscape", - "mood": "serene", - "lighting": "golden hour" - } - }' | jq '.' || echo "Enhancement with options test failed" -echo -e "\n" - -# Test 5: Multilingual prompt enhancement (Spanish) -echo "5. Testing Spanish prompt enhancement..." -curl -s -X POST "$BASE_URL/api/enhance" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "un gato hermoso con ojos azules", - "options": { - "imageStyle": "illustration" - } - }' | jq '.' || echo "Spanish enhancement test failed" -echo -e "\n" - -# Test 6: Multilingual prompt enhancement (Chinese) -echo "6. Testing Chinese prompt enhancement..." -curl -s -X POST "$BASE_URL/api/enhance" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "美丽的山景", - "options": { - "imageStyle": "minimalist" - } - }' | jq '.' || echo "Chinese enhancement test failed" -echo -e "\n" - -# Test 7: Sticker style prompt -echo "7. Testing sticker style enhancement..." -curl -s -X POST "$BASE_URL/api/enhance" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "happy panda", - "options": { - "imageStyle": "sticker", - "negativePrompts": ["dark", "scary"] - } - }' | jq '.' || echo "Sticker style test failed" -echo -e "\n" - -# Test 8: Validation error test (empty prompt) -echo "8. Testing validation error (empty prompt)..." -curl -s -X POST "$BASE_URL/api/enhance" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "" - }' | jq '.' || echo "Validation error test failed" -echo -e "\n" - -# Test 9: Validation error test (invalid image style) -echo "9. Testing validation error (invalid style)..." -curl -s -X POST "$BASE_URL/api/enhance" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "test", - "options": { - "imageStyle": "invalid_style" - } - }' | jq '.' || echo "Invalid style test failed" -echo -e "\n" - -# Test 10: Auto-enhancement in generate endpoint -echo "10. Testing auto-enhancement in generate endpoint..." -curl -s -X POST "$BASE_URL/api/generate" \ - -F "prompt=gato bonito" \ - -F "filename=test_autoenhance" \ - -F "autoEnhance=true" \ - -F "enhancementOptions[imageStyle]=photorealistic" | jq '.' || echo "Auto-enhancement test failed" -echo -e "\n" - -# Test 11: Generate without auto-enhancement (normal behavior) -echo "11. Testing generate without auto-enhancement..." -curl -s -X POST "$BASE_URL/api/generate" \ - -F "prompt=a simple cat" \ - -F "filename=test_normal" | jq '.' || echo "Normal generate test failed" -echo -e "\n" - -echo "✅ Enhancement API tests completed!" -echo "" -echo "📋 Test Summary:" -echo "- Basic enhancement functionality" -echo "- Enhancement with styling options" -echo "- Multilingual support (Spanish, Chinese)" -echo "- Different image styles (photorealistic, illustration, minimalist, sticker)" -echo "- Validation error handling" -echo "- Auto-enhancement integration with generate endpoint" -echo "" -echo "💡 To run with API key, ensure GEMINI_API_KEY is set in your environment" \ No newline at end of file