feat: Add Express.js server foundation with TypeScript

This commit is contained in:
Oleg Proskurin 2025-09-15 00:42:42 +07:00
parent 8aaa22ab99
commit bb8405f568
8 changed files with 1110 additions and 7 deletions

View File

@ -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"
} }
} }
} }
} }

View File

@ -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"

File diff suppressed because it is too large Load Diff

View File

@ -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);
} }
} }
} }

85
src/server/app.ts Normal file
View File

@ -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;
}
}
}

88
src/server/server.ts Normal file
View File

@ -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();

66
src/server/types/api.ts Normal file
View File

@ -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;
}

43
tsconfig.json Normal file
View File

@ -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"
}
}