-
- Service Status
-
+
Service Status
API Service
@@ -159,8 +140,8 @@ export default function AdminDashboard() {
- This admin dashboard is being developed to provide comprehensive
- monitoring and management capabilities for the Banatie service.
+ This admin dashboard is being developed to provide comprehensive monitoring
+ and management capabilities for the Banatie service.
@@ -170,5 +151,5 @@ export default function AdminDashboard() {
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json
index a26cf70..3a3c3c4 100644
--- a/apps/admin/tsconfig.json
+++ b/apps/admin/tsconfig.json
@@ -28,4 +28,4 @@
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
-}
\ No newline at end of file
+}
diff --git a/apps/api-service/.prettier.config.js b/apps/api-service/.prettier.config.js
index 3692e10..899fe7a 100644
--- a/apps/api-service/.prettier.config.js
+++ b/apps/api-service/.prettier.config.js
@@ -12,4 +12,4 @@ module.exports = {
quoteProps: 'as-needed',
jsxSingleQuote: true,
proseWrap: 'preserve',
-};
\ No newline at end of file
+};
diff --git a/apps/api-service/eslint.config.js b/apps/api-service/eslint.config.js
index ff48600..6694cda 100644
--- a/apps/api-service/eslint.config.js
+++ b/apps/api-service/eslint.config.js
@@ -13,9 +13,7 @@ module.exports = [
'@typescript-eslint': require('@typescript-eslint/eslint-plugin'),
prettier: require('eslint-plugin-prettier'),
},
- extends: [
- require('eslint-config-prettier'),
- ],
+ extends: [require('eslint-config-prettier')],
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
@@ -30,12 +28,6 @@ module.exports = [
},
},
{
- ignores: [
- 'node_modules/**',
- 'dist/**',
- '*.js',
- '*.mjs',
- 'eslint.config.js'
- ],
+ ignores: ['node_modules/**', 'dist/**', '*.js', '*.mjs', 'eslint.config.js'],
},
-];
\ No newline at end of file
+];
diff --git a/apps/api-service/package.json b/apps/api-service/package.json
index f6a1e3e..7c95e8a 100644
--- a/apps/api-service/package.json
+++ b/apps/api-service/package.json
@@ -71,4 +71,4 @@
"tsx": "^4.20.5",
"typescript": "^5.9.2"
}
-}
\ No newline at end of file
+}
diff --git a/apps/api-service/src/app.ts b/apps/api-service/src/app.ts
index bd685ed..7adeef5 100644
--- a/apps/api-service/src/app.ts
+++ b/apps/api-service/src/app.ts
@@ -1,24 +1,24 @@
-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";
-import adminKeysRoutes from "./routes/admin/keys";
-import { errorHandler, notFoundHandler } from "./middleware/errorHandler";
+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';
+import adminKeysRoutes from './routes/admin/keys';
+import { errorHandler, notFoundHandler } from './middleware/errorHandler';
// Load environment variables
config();
// Application configuration
export const appConfig: Config = {
- port: parseInt(process.env["PORT"] || "3000"),
- geminiApiKey: process.env["GEMINI_API_KEY"] || "",
- resultsDir: "./results",
- uploadsDir: "./uploads/temp",
+ port: parseInt(process.env['PORT'] || '3000'),
+ geminiApiKey: process.env['GEMINI_API_KEY'] || '',
+ resultsDir: './results',
+ uploadsDir: './uploads/temp',
maxFileSize: 5 * 1024 * 1024, // 5MB
maxFiles: 3,
};
@@ -32,30 +32,30 @@ export const createApp = (): Application => {
cors({
origin: true, // Allow all origins
credentials: true,
- methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
- allowedHeaders: ["Content-Type", "Authorization", "X-API-Key"],
- exposedHeaders: ["X-Request-ID"],
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+ allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
+ exposedHeaders: ['X-Request-ID'],
}),
);
- app.use(express.json({ limit: "10mb" }));
- app.use(express.urlencoded({ extended: true, limit: "10mb" }));
+ app.use(express.json({ limit: '10mb' }));
+ app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Request ID middleware for logging
app.use((req, res, next) => {
req.requestId = Math.random().toString(36).substr(2, 9);
- res.setHeader("X-Request-ID", req.requestId);
+ res.setHeader('X-Request-ID', req.requestId);
next();
});
// Health check endpoint
- app.get("/health", (_req, res) => {
+ app.get('/health', (_req, res) => {
const health = {
- status: "healthy",
+ status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
- environment: process.env["NODE_ENV"] || "development",
- version: process.env["npm_package_version"] || "1.0.0",
+ environment: process.env['NODE_ENV'] || 'development',
+ version: process.env['npm_package_version'] || '1.0.0',
};
console.log(`[${health.timestamp}] Health check - ${health.status}`);
@@ -63,34 +63,31 @@ export const createApp = (): Application => {
});
// API info endpoint
- app.get("/api/info", async (req: any, res) => {
+ app.get('/api/info', async (req: any, res) => {
const info: any = {
- name: "Banatie - Nano Banana Image Generation API",
- version: "1.0.0",
+ name: 'Banatie - Nano Banana Image Generation API',
+ version: '1.0.0',
description:
- "REST API service for AI-powered image generation using Gemini Flash Image model",
+ 'REST API service for AI-powered image generation using Gemini Flash Image model',
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",
+ '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`,
maxFiles: appConfig.maxFiles,
- supportedFormats: ["PNG", "JPEG", "JPG", "WebP"],
+ supportedFormats: ['PNG', 'JPEG', 'JPG', 'WebP'],
},
};
// If API key is provided, validate and return key info
- const providedKey = req.headers["x-api-key"] as string;
+ const providedKey = req.headers['x-api-key'] as string;
if (providedKey) {
try {
- const { ApiKeyService } = await import("./services/ApiKeyService");
+ const { ApiKeyService } = await import('./services/ApiKeyService');
const apiKeyService = new ApiKeyService();
const apiKey = await apiKeyService.validateKey(providedKey);
@@ -117,16 +114,16 @@ export const createApp = (): Application => {
// Public routes (no authentication)
// Bootstrap route (no auth, but works only once)
- app.use("/api/bootstrap", bootstrapRoutes);
+ app.use('/api/bootstrap', bootstrapRoutes);
// Admin routes (require master key)
- app.use("/api/admin/keys", adminKeysRoutes);
+ 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);
+ app.use('/api', generateRouter);
+ app.use('/api', enhanceRouter);
+ app.use('/api', textToImageRouter);
+ app.use('/api', imagesRouter);
// Error handling middleware (must be last)
app.use(notFoundHandler);
diff --git a/apps/api-service/src/db.ts b/apps/api-service/src/db.ts
index e084852..0800e4c 100644
--- a/apps/api-service/src/db.ts
+++ b/apps/api-service/src/db.ts
@@ -1,8 +1,11 @@
import { createDbClient } from '@banatie/database';
-const DATABASE_URL = process.env['DATABASE_URL'] ||
+const DATABASE_URL =
+ process.env['DATABASE_URL'] ||
'postgresql://banatie_user:banatie_secure_password@localhost:5434/banatie_db';
export const db = createDbClient(DATABASE_URL);
-console.log(`[${new Date().toISOString()}] Database client initialized - ${new URL(DATABASE_URL).host}`);
\ No newline at end of file
+console.log(
+ `[${new Date().toISOString()}] Database client initialized - ${new URL(DATABASE_URL).host}`,
+);
diff --git a/apps/api-service/src/middleware/auth/rateLimiter.ts b/apps/api-service/src/middleware/auth/rateLimiter.ts
index c4ecebf..911573f 100644
--- a/apps/api-service/src/middleware/auth/rateLimiter.ts
+++ b/apps/api-service/src/middleware/auth/rateLimiter.ts
@@ -37,7 +37,7 @@ class RateLimiter {
return {
allowed: true,
remaining: this.limit - record.count,
- resetAt: record.resetAt
+ resetAt: record.resetAt,
};
}
@@ -57,11 +57,7 @@ const rateLimiter = new RateLimiter(100, 60 * 60 * 1000); // 100 requests per ho
* Rate limiting middleware
* Must be used AFTER validateApiKey middleware
*/
-export function rateLimitByApiKey(
- req: Request,
- res: Response,
- next: NextFunction
-): void {
+export function rateLimitByApiKey(req: Request, res: Response, next: NextFunction): void {
if (!req.apiKey) {
next();
return;
@@ -77,9 +73,12 @@ export function rateLimitByApiKey(
if (!result.allowed) {
const retryAfter = Math.ceil((result.resetAt - Date.now()) / 1000);
- console.warn(`[${new Date().toISOString()}] Rate limit exceeded: ${req.apiKey.id} (${req.apiKey.keyType}) - reset: ${new Date(result.resetAt).toISOString()}`);
+ console.warn(
+ `[${new Date().toISOString()}] Rate limit exceeded: ${req.apiKey.id} (${req.apiKey.keyType}) - reset: ${new Date(result.resetAt).toISOString()}`,
+ );
- res.status(429)
+ res
+ .status(429)
.setHeader('Retry-After', retryAfter.toString())
.json({
error: 'Rate limit exceeded',
@@ -90,4 +89,4 @@ export function rateLimitByApiKey(
}
next();
-}
\ No newline at end of file
+}
diff --git a/apps/api-service/src/middleware/auth/requireMasterKey.ts b/apps/api-service/src/middleware/auth/requireMasterKey.ts
index f6d7402..8d58350 100644
--- a/apps/api-service/src/middleware/auth/requireMasterKey.ts
+++ b/apps/api-service/src/middleware/auth/requireMasterKey.ts
@@ -4,11 +4,7 @@ import { Request, Response, NextFunction } from 'express';
* Middleware to ensure the API key is a master key
* Must be used AFTER validateApiKey middleware
*/
-export function requireMasterKey(
- req: Request,
- res: Response,
- next: NextFunction
-): void {
+export function requireMasterKey(req: Request, res: Response, next: NextFunction): void {
if (!req.apiKey) {
res.status(401).json({
error: 'Authentication required',
@@ -18,7 +14,9 @@ export function requireMasterKey(
}
if (req.apiKey.keyType !== 'master') {
- console.warn(`[${new Date().toISOString()}] Non-master key attempted admin action: ${req.apiKey.id} (${req.apiKey.keyType}) - ${req.path}`);
+ console.warn(
+ `[${new Date().toISOString()}] Non-master key attempted admin action: ${req.apiKey.id} (${req.apiKey.keyType}) - ${req.path}`,
+ );
res.status(403).json({
error: 'Master key required',
@@ -28,4 +26,4 @@ export function requireMasterKey(
}
next();
-}
\ No newline at end of file
+}
diff --git a/apps/api-service/src/middleware/auth/requireProjectKey.ts b/apps/api-service/src/middleware/auth/requireProjectKey.ts
index e65818b..7dd2498 100644
--- a/apps/api-service/src/middleware/auth/requireProjectKey.ts
+++ b/apps/api-service/src/middleware/auth/requireProjectKey.ts
@@ -4,11 +4,7 @@ import { Request, Response, NextFunction } from 'express';
* Middleware to ensure only project keys can access generation endpoints
* Master keys are for admin purposes only
*/
-export function requireProjectKey(
- req: Request,
- res: Response,
- next: NextFunction
-): void {
+export function requireProjectKey(req: Request, res: Response, next: NextFunction): void {
// This middleware assumes validateApiKey has already run and attached req.apiKey
if (!req.apiKey) {
res.status(401).json({
@@ -22,7 +18,8 @@ export function requireProjectKey(
if (req.apiKey.keyType === 'master') {
res.status(403).json({
error: 'Forbidden',
- message: 'Master keys cannot be used for image generation. Please use a project-specific API key.',
+ message:
+ 'Master keys cannot be used for image generation. Please use a project-specific API key.',
});
return;
}
@@ -36,7 +33,9 @@ export function requireProjectKey(
return;
}
- console.log(`[${new Date().toISOString()}] Project key validated for generation: ${req.apiKey.id}`);
+ console.log(
+ `[${new Date().toISOString()}] Project key validated for generation: ${req.apiKey.id}`,
+ );
next();
}
diff --git a/apps/api-service/src/middleware/auth/validateApiKey.ts b/apps/api-service/src/middleware/auth/validateApiKey.ts
index 6652d8c..a69db91 100644
--- a/apps/api-service/src/middleware/auth/validateApiKey.ts
+++ b/apps/api-service/src/middleware/auth/validateApiKey.ts
@@ -18,7 +18,7 @@ const apiKeyService = new ApiKeyService();
export async function validateApiKey(
req: Request,
res: Response,
- next: NextFunction
+ next: NextFunction,
): Promise
{
const providedKey = req.headers['x-api-key'] as string;
@@ -44,7 +44,9 @@ export async function validateApiKey(
// Attach to request for use in routes
req.apiKey = apiKey;
- console.log(`[${new Date().toISOString()}] API key validated: ${apiKey.id} (${apiKey.keyType})`);
+ console.log(
+ `[${new Date().toISOString()}] API key validated: ${apiKey.id} (${apiKey.keyType})`,
+ );
next();
} catch (error) {
@@ -54,4 +56,4 @@ export async function validateApiKey(
message: 'An error occurred during authentication',
});
}
-}
\ No newline at end of file
+}
diff --git a/apps/api-service/src/middleware/errorHandler.ts b/apps/api-service/src/middleware/errorHandler.ts
index 81fd625..70e2114 100644
--- a/apps/api-service/src/middleware/errorHandler.ts
+++ b/apps/api-service/src/middleware/errorHandler.ts
@@ -1,17 +1,12 @@
-import { Request, Response, NextFunction } from "express";
-import { GenerateImageResponse } from "../types/api";
+import { Request, Response, NextFunction } from 'express';
+import { GenerateImageResponse } from '../types/api';
/**
* Global error handler for the Express application
*/
-export const errorHandler = (
- error: Error,
- req: Request,
- res: Response,
- next: NextFunction,
-) => {
+export const errorHandler = (error: Error, req: Request, res: Response, next: NextFunction) => {
const timestamp = new Date().toISOString();
- const requestId = req.requestId || "unknown";
+ const requestId = req.requestId || 'unknown';
// Log the error
console.error(`[${timestamp}] [${requestId}] ERROR:`, {
@@ -30,52 +25,40 @@ export const errorHandler = (
// Determine error type and status code
let statusCode = 500;
- let errorMessage = "Internal server error";
- let errorType = "INTERNAL_ERROR";
+ let errorMessage = 'Internal server error';
+ let errorType = 'INTERNAL_ERROR';
- if (error.name === "ValidationError") {
+ if (error.name === 'ValidationError') {
statusCode = 400;
errorMessage = error.message;
- errorType = "VALIDATION_ERROR";
- } else if (
- error.message.includes("API key") ||
- error.message.includes("authentication")
- ) {
+ errorType = 'VALIDATION_ERROR';
+ } else if (error.message.includes('API key') || error.message.includes('authentication')) {
statusCode = 401;
- errorMessage = "Authentication failed";
- errorType = "AUTH_ERROR";
- } else if (
- error.message.includes("not found") ||
- error.message.includes("404")
- ) {
+ errorMessage = 'Authentication failed';
+ errorType = 'AUTH_ERROR';
+ } else if (error.message.includes('not found') || error.message.includes('404')) {
statusCode = 404;
- errorMessage = "Resource not found";
- errorType = "NOT_FOUND";
- } else if (
- error.message.includes("timeout") ||
- error.message.includes("503")
- ) {
+ errorMessage = 'Resource not found';
+ errorType = 'NOT_FOUND';
+ } else if (error.message.includes('timeout') || error.message.includes('503')) {
statusCode = 503;
- errorMessage = "Service temporarily unavailable";
- errorType = "SERVICE_UNAVAILABLE";
- } else if (
- error.message.includes("overloaded") ||
- error.message.includes("rate limit")
- ) {
+ errorMessage = 'Service temporarily unavailable';
+ errorType = 'SERVICE_UNAVAILABLE';
+ } else if (error.message.includes('overloaded') || error.message.includes('rate limit')) {
statusCode = 429;
- errorMessage = "Service overloaded, please try again later";
- errorType = "RATE_LIMITED";
+ errorMessage = 'Service overloaded, please try again later';
+ errorType = 'RATE_LIMITED';
}
// Create error response
const errorResponse: GenerateImageResponse = {
success: false,
- message: "Request failed",
+ message: 'Request failed',
error: errorMessage,
};
// Add additional debug info in development
- if (process.env["NODE_ENV"] === "development") {
+ if (process.env['NODE_ENV'] === 'development') {
(errorResponse as any).debug = {
originalError: error.message,
errorType,
@@ -96,15 +79,13 @@ export const errorHandler = (
*/
export const notFoundHandler = (req: Request, res: Response) => {
const timestamp = new Date().toISOString();
- const requestId = req.requestId || "unknown";
+ const requestId = req.requestId || 'unknown';
- console.log(
- `[${timestamp}] [${requestId}] 404 - Route not found: ${req.method} ${req.path}`,
- );
+ console.log(`[${timestamp}] [${requestId}] 404 - Route not found: ${req.method} ${req.path}`);
const notFoundResponse: GenerateImageResponse = {
success: false,
- message: "Route not found",
+ message: 'Route not found',
error: `The requested endpoint ${req.method} ${req.path} does not exist`,
};
@@ -114,7 +95,6 @@ export const notFoundHandler = (req: Request, res: Response) => {
/**
* Async error wrapper to catch errors in async route handlers
*/
-export const asyncHandler =
- (fn: Function) => (req: Request, res: Response, next: NextFunction) => {
- Promise.resolve(fn(req, res, next)).catch(next);
- };
+export const asyncHandler = (fn: Function) => (req: Request, res: Response, next: NextFunction) => {
+ Promise.resolve(fn(req, res, next)).catch(next);
+};
diff --git a/apps/api-service/src/middleware/jsonValidation.ts b/apps/api-service/src/middleware/jsonValidation.ts
index 1693f91..651d5d7 100644
--- a/apps/api-service/src/middleware/jsonValidation.ts
+++ b/apps/api-service/src/middleware/jsonValidation.ts
@@ -1,5 +1,5 @@
-import { Response, NextFunction } from "express";
-import { sanitizeFilename } from "./validation";
+import { Response, NextFunction } from 'express';
+import { sanitizeFilename } from './validation';
// Validation rules (same as existing validation but for JSON)
const VALIDATION_RULES = {
@@ -18,16 +18,16 @@ const VALIDATION_RULES = {
// Valid aspect ratios supported by Gemini SDK
const VALID_ASPECT_RATIOS = [
- "1:1", // Square (1024x1024)
- "2:3", // Portrait (832x1248)
- "3:2", // Landscape (1248x832)
- "3:4", // Portrait (864x1184)
- "4:3", // Landscape (1184x864)
- "4:5", // Portrait (896x1152)
- "5:4", // Landscape (1152x896)
- "9:16", // Vertical (768x1344)
- "16:9", // Widescreen (1344x768)
- "21:9", // Ultrawide (1536x672)
+ '1:1', // Square (1024x1024)
+ '2:3', // Portrait (832x1248)
+ '3:2', // Landscape (1248x832)
+ '3:4', // Portrait (864x1184)
+ '4:3', // Landscape (1184x864)
+ '4:5', // Portrait (896x1152)
+ '5:4', // Landscape (1152x896)
+ '9:16', // Vertical (768x1344)
+ '16:9', // Widescreen (1344x768)
+ '21:9', // Ultrawide (1536x672)
] as const;
/**
@@ -42,16 +42,14 @@ export const validateTextToImageRequest = (
const { prompt, filename, aspectRatio, autoEnhance, enhancementOptions } = req.body;
const errors: string[] = [];
- console.log(
- `[${timestamp}] [${req.requestId}] Validating text-to-image JSON request`,
- );
+ console.log(`[${timestamp}] [${req.requestId}] Validating text-to-image JSON request`);
// Validate that request body exists
- if (!req.body || typeof req.body !== "object") {
+ if (!req.body || typeof req.body !== 'object') {
return res.status(400).json({
success: false,
- error: "Request body must be valid JSON",
- message: "Invalid request format",
+ error: 'Request body must be valid JSON',
+ message: 'Invalid request format',
});
}
@@ -63,67 +61,54 @@ export const validateTextToImageRequest = (
// Default template to "photorealistic" in enhancementOptions
if (req.body.enhancementOptions && !req.body.enhancementOptions.template) {
- req.body.enhancementOptions.template = "photorealistic";
+ req.body.enhancementOptions.template = 'photorealistic';
} else if (!req.body.enhancementOptions && req.body.autoEnhance !== false) {
// If autoEnhance is true (default) and no enhancementOptions, create it with default template
- req.body.enhancementOptions = { template: "photorealistic" };
+ req.body.enhancementOptions = { template: 'photorealistic' };
}
// Validate prompt
if (!prompt) {
- errors.push("Prompt is required");
- } else if (typeof prompt !== "string") {
- errors.push("Prompt must be a string");
+ errors.push('Prompt is required');
+ } else if (typeof prompt !== 'string') {
+ errors.push('Prompt must be a string');
} else if (prompt.trim().length < VALIDATION_RULES.prompt.minLength) {
- errors.push(
- `Prompt must be at least ${VALIDATION_RULES.prompt.minLength} characters`,
- );
+ errors.push(`Prompt must be at least ${VALIDATION_RULES.prompt.minLength} characters`);
} else if (prompt.length > VALIDATION_RULES.prompt.maxLength) {
- errors.push(
- `Prompt must be less than ${VALIDATION_RULES.prompt.maxLength} characters`,
- );
+ errors.push(`Prompt must be less than ${VALIDATION_RULES.prompt.maxLength} characters`);
}
// Validate filename
if (!filename) {
- errors.push("Filename is required");
- } else if (typeof filename !== "string") {
- errors.push("Filename must be a string");
+ errors.push('Filename is required');
+ } else if (typeof filename !== 'string') {
+ errors.push('Filename must be a string');
} else if (filename.trim().length < VALIDATION_RULES.filename.minLength) {
- errors.push("Filename cannot be empty");
+ errors.push('Filename cannot be empty');
} else if (filename.length > VALIDATION_RULES.filename.maxLength) {
- errors.push(
- `Filename must be less than ${VALIDATION_RULES.filename.maxLength} characters`,
- );
+ errors.push(`Filename must be less than ${VALIDATION_RULES.filename.maxLength} characters`);
} else if (!VALIDATION_RULES.filename.pattern.test(filename)) {
- errors.push(
- "Filename can only contain letters, numbers, underscores, and hyphens",
- );
+ errors.push('Filename can only contain letters, numbers, underscores, and hyphens');
}
// Validate aspectRatio (optional, defaults to "1:1")
if (aspectRatio !== undefined) {
- if (typeof aspectRatio !== "string") {
- errors.push("aspectRatio must be a string");
+ if (typeof aspectRatio !== 'string') {
+ errors.push('aspectRatio must be a string');
} else if (!VALID_ASPECT_RATIOS.includes(aspectRatio as any)) {
- errors.push(
- `Invalid aspectRatio. Must be one of: ${VALID_ASPECT_RATIOS.join(", ")}`
- );
+ errors.push(`Invalid aspectRatio. Must be one of: ${VALID_ASPECT_RATIOS.join(', ')}`);
}
}
// Validate autoEnhance (optional boolean)
- if (autoEnhance !== undefined && typeof autoEnhance !== "boolean") {
- errors.push("autoEnhance must be a boolean");
+ if (autoEnhance !== undefined && typeof autoEnhance !== 'boolean') {
+ errors.push('autoEnhance must be a boolean');
}
// Validate enhancementOptions (optional object)
if (enhancementOptions !== undefined) {
- if (
- typeof enhancementOptions !== "object" ||
- Array.isArray(enhancementOptions)
- ) {
- errors.push("enhancementOptions must be an object");
+ if (typeof enhancementOptions !== 'object' || Array.isArray(enhancementOptions)) {
+ errors.push('enhancementOptions must be an object');
} else {
const { template } = enhancementOptions;
@@ -131,17 +116,17 @@ export const validateTextToImageRequest = (
if (
template !== undefined &&
![
- "photorealistic",
- "illustration",
- "minimalist",
- "sticker",
- "product",
- "comic",
- "general",
+ 'photorealistic',
+ 'illustration',
+ 'minimalist',
+ 'sticker',
+ 'product',
+ 'comic',
+ 'general',
].includes(template)
) {
errors.push(
- "Invalid template in enhancementOptions. Must be one of: photorealistic, illustration, minimalist, sticker, product, comic, general",
+ 'Invalid template in enhancementOptions. Must be one of: photorealistic, illustration, minimalist, sticker, product, comic, general',
);
}
}
@@ -149,19 +134,16 @@ export const validateTextToImageRequest = (
// Validate meta (optional object)
if (req.body.meta !== undefined) {
- if (
- typeof req.body.meta !== "object" ||
- Array.isArray(req.body.meta)
- ) {
- errors.push("meta must be an object");
+ if (typeof req.body.meta !== 'object' || Array.isArray(req.body.meta)) {
+ errors.push('meta must be an object');
} else if (req.body.meta.tags !== undefined) {
if (!Array.isArray(req.body.meta.tags)) {
- errors.push("meta.tags must be an array");
+ errors.push('meta.tags must be an array');
} else {
// Validate each tag is a string
for (const tag of req.body.meta.tags) {
- if (typeof tag !== "string") {
- errors.push("Each tag in meta.tags must be a string");
+ if (typeof tag !== 'string') {
+ errors.push('Each tag in meta.tags must be a string');
break;
}
}
@@ -170,28 +152,19 @@ export const validateTextToImageRequest = (
}
// Check for XSS attempts in prompt
- const xssPatterns = [
- /