feat: Add Express.js server foundation with TypeScript
This commit is contained in:
parent
8aaa22ab99
commit
bb8405f568
31
.mcp.json
31
.mcp.json
|
|
@ -1,14 +1,39 @@
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"nano-banana-mcp": {
|
"context7": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
"command": "npx",
|
"command": "npx",
|
||||||
"args": [
|
"args": [
|
||||||
"nano-banana-mcp"
|
"-y",
|
||||||
|
"@upstash/context7-mcp",
|
||||||
|
"--api-key",
|
||||||
|
"ctx7sk-48cb1995-935a-4cc5-b9b0-535d600ea5e6"
|
||||||
],
|
],
|
||||||
|
"env": {}
|
||||||
|
},
|
||||||
|
"brave-search": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@modelcontextprotocol/server-brave-search"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"BRAVE_API_KEY": "BSAcRGGikEzY4B2j3NZ8Qy5NYh9l4HZ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browsermcp": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@browsermcp/mcp@latest"],
|
||||||
|
"env": {}
|
||||||
|
},
|
||||||
|
"nano-banana-mcp": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["nano-banana-mcp"],
|
||||||
"env": {
|
"env": {
|
||||||
"GEMINI_API_KEY": "AIzaSyAWAy39gqmnrYA0kUC3Vh8uadu-WlqJuO4"
|
"GEMINI_API_KEY": "AIzaSyAWAy39gqmnrYA0kUC3Vh8uadu-WlqJuO4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
package.json
15
package.json
|
|
@ -5,7 +5,12 @@
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ttm": "tsx src/generation/text-to-image.ts",
|
"ttm": "tsx src/generation/text-to-image.ts",
|
||||||
"ttt": "tsx src/generation/text-to-text.ts"
|
"ttt": "tsx src/generation/text-to-text.ts",
|
||||||
|
"server": "tsx src/server/server.ts",
|
||||||
|
"server:dev": "tsx --watch src/server/server.ts",
|
||||||
|
"server:log": "tsx src/server/server.ts > logs/server.log 2>&1",
|
||||||
|
"build": "tsc",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
@ -13,10 +18,16 @@
|
||||||
"packageManager": "pnpm@10.11.0",
|
"packageManager": "pnpm@10.11.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^1.17.0",
|
"@google/genai": "^1.17.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.2",
|
"dotenv": "^17.2.2",
|
||||||
"mime": "^4.1.0"
|
"express": "^5.1.0",
|
||||||
|
"mime": "^4.1.0",
|
||||||
|
"multer": "^2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
|
"@types/express": "^5.0.3",
|
||||||
|
"@types/multer": "^2.0.0",
|
||||||
"@types/node": "^24.3.1",
|
"@types/node": "^24.3.1",
|
||||||
"tsx": "^4.20.5",
|
"tsx": "^4.20.5",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
|
|
|
||||||
785
pnpm-lock.yaml
785
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -78,7 +78,7 @@ async function main() {
|
||||||
}
|
}
|
||||||
console.log('Image generation complete!');
|
console.log('Image generation complete!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Primary model failed:', error.message || error);
|
console.error('Primary model failed:', error instanceof Error ? error.message : error);
|
||||||
|
|
||||||
// Try fallback model (Imagen 4)
|
// Try fallback model (Imagen 4)
|
||||||
console.log('Trying fallback model (Imagen 4)...');
|
console.log('Trying fallback model (Imagen 4)...');
|
||||||
|
|
@ -111,7 +111,7 @@ async function main() {
|
||||||
}
|
}
|
||||||
} catch (altError) {
|
} catch (altError) {
|
||||||
console.error('Both models failed. Please try again later.');
|
console.error('Both models failed. Please try again later.');
|
||||||
console.error('Fallback error:', altError.message || altError);
|
console.error('Fallback error:', altError instanceof Error ? altError.message : altError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
import express, { Application } from 'express';
|
||||||
|
import cors from 'cors';
|
||||||
|
import { config } from 'dotenv';
|
||||||
|
import { Config } from './types/api';
|
||||||
|
|
||||||
|
// Load environment variables
|
||||||
|
config();
|
||||||
|
|
||||||
|
// Application configuration
|
||||||
|
export const appConfig: Config = {
|
||||||
|
port: parseInt(process.env.PORT || '3000'),
|
||||||
|
geminiApiKey: process.env.GEMINI_API_KEY || '',
|
||||||
|
resultsDir: './src/results',
|
||||||
|
uploadsDir: './uploads/temp',
|
||||||
|
maxFileSize: 5 * 1024 * 1024, // 5MB
|
||||||
|
maxFiles: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Express application
|
||||||
|
export const createApp = (): Application => {
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
app.use(cors({
|
||||||
|
origin: process.env.CORS_ORIGIN || '*',
|
||||||
|
credentials: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
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', (req, res) => {
|
||||||
|
const info = {
|
||||||
|
name: 'Magic Building Image Generation API',
|
||||||
|
version: '1.0.0',
|
||||||
|
description: 'Express.js server for Gemini AI image generation',
|
||||||
|
endpoints: {
|
||||||
|
'GET /health': 'Health check',
|
||||||
|
'GET /api/info': 'API information',
|
||||||
|
'POST /api/generate': 'Generate images from text prompt with optional reference images'
|
||||||
|
},
|
||||||
|
limits: {
|
||||||
|
maxFileSize: `${appConfig.maxFileSize / (1024 * 1024)}MB`,
|
||||||
|
maxFiles: appConfig.maxFiles,
|
||||||
|
supportedFormats: ['PNG', 'JPEG', 'JPG', 'WebP']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`[${new Date().toISOString()}] API info requested`);
|
||||||
|
res.json(info);
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extend Express Request type to include requestId
|
||||||
|
declare global {
|
||||||
|
namespace Express {
|
||||||
|
interface Request {
|
||||||
|
requestId: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { createApp, appConfig } from './app';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// Ensure required directories exist
|
||||||
|
const ensureDirectoriesExist = () => {
|
||||||
|
const directories = [
|
||||||
|
appConfig.resultsDir,
|
||||||
|
appConfig.uploadsDir,
|
||||||
|
'./logs'
|
||||||
|
];
|
||||||
|
|
||||||
|
directories.forEach(dir => {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
console.log(`[${new Date().toISOString()}] Created directory: ${dir}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enhanced logging function
|
||||||
|
const log = (level: 'INFO' | 'ERROR' | 'WARN' | 'DEBUG', message: string) => {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const logMessage = `[${timestamp}] [${level}] ${message}`;
|
||||||
|
console.log(logMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate environment
|
||||||
|
const validateEnvironment = () => {
|
||||||
|
if (!appConfig.geminiApiKey) {
|
||||||
|
log('ERROR', 'GEMINI_API_KEY environment variable is required');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('INFO', 'Environment validation passed');
|
||||||
|
log('INFO', `Server configuration: port=${appConfig.port}, maxFileSize=${appConfig.maxFileSize}bytes, maxFiles=${appConfig.maxFiles}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
|
const setupGracefulShutdown = (server: any) => {
|
||||||
|
const shutdown = (signal: string) => {
|
||||||
|
log('INFO', `Received ${signal}, shutting down gracefully...`);
|
||||||
|
|
||||||
|
server.close((err: Error) => {
|
||||||
|
if (err) {
|
||||||
|
log('ERROR', `Error during shutdown: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
log('INFO', 'Server closed successfully');
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||||
|
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
const startServer = async () => {
|
||||||
|
try {
|
||||||
|
log('INFO', '🚀 Starting Magic Building Image Generation Server...');
|
||||||
|
|
||||||
|
// Validate environment and create directories
|
||||||
|
validateEnvironment();
|
||||||
|
ensureDirectoriesExist();
|
||||||
|
|
||||||
|
// Create Express app
|
||||||
|
const app = createApp();
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
const server = app.listen(appConfig.port, () => {
|
||||||
|
log('INFO', `✅ Server running on port ${appConfig.port}`);
|
||||||
|
log('INFO', `📋 Health check: http://localhost:${appConfig.port}/health`);
|
||||||
|
log('INFO', `📖 API info: http://localhost:${appConfig.port}/api/info`);
|
||||||
|
log('INFO', `🎨 Generate endpoint: http://localhost:${appConfig.port}/api/generate`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup graceful shutdown
|
||||||
|
setupGracefulShutdown(server);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log('ERROR', `Failed to start server: ${error instanceof Error ? error.message : error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
startServer();
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { Request } from 'express';
|
||||||
|
|
||||||
|
// API Request/Response types
|
||||||
|
export interface GenerateImageRequest {
|
||||||
|
prompt: string;
|
||||||
|
filename: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GenerateImageResponse {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
data?: {
|
||||||
|
filename: string;
|
||||||
|
filepath: string;
|
||||||
|
description?: string;
|
||||||
|
model: string;
|
||||||
|
generatedAt: string;
|
||||||
|
};
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extended Express Request with file uploads
|
||||||
|
export interface GenerateImageRequestWithFiles extends Request {
|
||||||
|
body: GenerateImageRequest;
|
||||||
|
files?: Express.Multer.File[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image generation service types
|
||||||
|
export interface ImageGenerationOptions {
|
||||||
|
prompt: string;
|
||||||
|
filename: string;
|
||||||
|
referenceImages?: ReferenceImage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReferenceImage {
|
||||||
|
buffer: Buffer;
|
||||||
|
mimetype: string;
|
||||||
|
originalname: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ImageGenerationResult {
|
||||||
|
success: boolean;
|
||||||
|
filename?: string;
|
||||||
|
filepath?: string;
|
||||||
|
description?: string;
|
||||||
|
model: string;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logging types
|
||||||
|
export interface LogContext {
|
||||||
|
requestId: string;
|
||||||
|
endpoint: string;
|
||||||
|
method: string;
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment configuration
|
||||||
|
export interface Config {
|
||||||
|
port: number;
|
||||||
|
geminiApiKey: string;
|
||||||
|
resultsDir: string;
|
||||||
|
uploadsDir: string;
|
||||||
|
maxFileSize: number;
|
||||||
|
maxFiles: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"removeComments": false,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": false,
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["*"],
|
||||||
|
"@server/*": ["server/*"],
|
||||||
|
"@generation/*": ["generation/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.spec.ts"
|
||||||
|
],
|
||||||
|
"ts-node": {
|
||||||
|
"esm": true,
|
||||||
|
"experimentalSpecifierResolution": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue