143 lines
4.3 KiB
TypeScript
143 lines
4.3 KiB
TypeScript
import express, { Application } from 'express';
|
|
import cors from 'cors';
|
|
import { config } from 'dotenv';
|
|
import { Config } from './types/api';
|
|
import { textToImageRouter } from './routes/textToImage';
|
|
import { imagesRouter } from './routes/images';
|
|
import { uploadRouter } from './routes/upload';
|
|
import bootstrapRoutes from './routes/bootstrap';
|
|
import adminKeysRoutes from './routes/admin/keys';
|
|
import { v1Router } from './routes/v1';
|
|
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',
|
|
maxFileSize: 5 * 1024 * 1024, // 5MB
|
|
maxFiles: 3,
|
|
};
|
|
|
|
// Create Express application
|
|
export const createApp = (): Application => {
|
|
const app = express();
|
|
|
|
// Middleware - CORS configuration (allow all origins)
|
|
app.use(
|
|
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'],
|
|
}),
|
|
);
|
|
|
|
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);
|
|
next();
|
|
});
|
|
|
|
// Health check endpoint
|
|
app.get('/health', (_req, res) => {
|
|
const health = {
|
|
status: 'healthy',
|
|
timestamp: new Date().toISOString(),
|
|
uptime: process.uptime(),
|
|
environment: process.env['NODE_ENV'] || 'development',
|
|
version: process.env['npm_package_version'] || '1.0.0',
|
|
};
|
|
|
|
console.log(`[${health.timestamp}] Health check - ${health.status}`);
|
|
res.json(health);
|
|
});
|
|
|
|
// API info endpoint
|
|
app.get('/api/info', async (req: any, res) => {
|
|
const info: any = {
|
|
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',
|
|
endpoints: {
|
|
'GET /health': 'Health check',
|
|
'GET /api/info': 'API information',
|
|
'POST /api/text-to-image': 'Generate images from text prompt only (JSON)',
|
|
},
|
|
limits: {
|
|
maxFileSize: `${appConfig.maxFileSize / (1024 * 1024)}MB`,
|
|
maxFiles: appConfig.maxFiles,
|
|
supportedFormats: ['PNG', 'JPEG', 'JPG', 'WebP'],
|
|
},
|
|
};
|
|
|
|
// If API key is provided, validate and return key info
|
|
const providedKey = req.headers['x-api-key'] as string;
|
|
if (providedKey) {
|
|
try {
|
|
const { ApiKeyService } = await import('./services/ApiKeyService');
|
|
const apiKeyService = new ApiKeyService();
|
|
const apiKey = await apiKeyService.validateKey(providedKey);
|
|
|
|
if (apiKey) {
|
|
// Use slugs from validated API key (already fetched via LEFT JOIN)
|
|
info.authenticated = true;
|
|
info.keyInfo = {
|
|
type: apiKey.keyType,
|
|
organizationId: apiKey.organizationId,
|
|
organizationSlug: apiKey.organizationSlug,
|
|
projectId: apiKey.projectId,
|
|
projectSlug: apiKey.projectSlug,
|
|
expiresAt: apiKey.expiresAt,
|
|
};
|
|
}
|
|
} catch (error) {
|
|
// Ignore errors, just don't add key info
|
|
}
|
|
}
|
|
|
|
console.log(`[${new Date().toISOString()}] API info requested`);
|
|
res.json(info);
|
|
});
|
|
|
|
// Public routes (no authentication)
|
|
// Bootstrap route (no auth, but works only once)
|
|
app.use('/api/bootstrap', bootstrapRoutes);
|
|
|
|
// Admin routes (require master key)
|
|
app.use('/api/admin/keys', adminKeysRoutes);
|
|
|
|
// API v1 routes (versioned, require valid API key)
|
|
app.use('/api/v1', v1Router);
|
|
|
|
// Protected API routes (require valid API key) - Legacy
|
|
app.use('/api', textToImageRouter);
|
|
app.use('/api', imagesRouter);
|
|
app.use('/api', uploadRouter);
|
|
|
|
// Error handling middleware (must be last)
|
|
app.use(notFoundHandler);
|
|
app.use(errorHandler);
|
|
|
|
return app;
|
|
};
|
|
|
|
// Extend Express Request type to include requestId
|
|
declare global {
|
|
namespace Express {
|
|
interface Request {
|
|
requestId: string;
|
|
}
|
|
}
|
|
}
|