From 52549836e465dd79fdcadb1ded32f2f523020a55 Mon Sep 17 00:00:00 2001 From: Oleg Proskurin Date: Sun, 21 Sep 2025 22:51:51 +0700 Subject: [PATCH] style: Apply Prettier formatting to all source files - Consistent code formatting across the entire codebase - Double quotes for strings, semicolons, and proper indentation - Improved readability and code consistency --- .prettier.config.js | 2 +- eslint.config.js | 18 ++-- package.json | 9 +- pnpm-lock.yaml | 3 + src/middleware/errorHandler.ts | 83 ++++++++++------- src/middleware/upload.ts | 70 +++++++++------ src/middleware/validation.ts | 84 +++++++++++------- src/routes/generate.ts | 92 +++++++++++-------- src/services/ImageGenService.ts | 152 ++++++++++++++++++++------------ src/types/api.ts | 4 +- 10 files changed, 313 insertions(+), 204 deletions(-) diff --git a/.prettier.config.js b/.prettier.config.js index abed446..3692e10 100644 --- a/.prettier.config.js +++ b/.prettier.config.js @@ -1,4 +1,4 @@ -export default { +module.exports = { semi: true, trailingComma: 'es5', singleQuote: true, diff --git a/eslint.config.js b/eslint.config.js index abcc209..1e9f69f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,14 +1,8 @@ -import js from '@eslint/js'; -import typescript from '@typescript-eslint/eslint-plugin'; -import typescriptParser from '@typescript-eslint/parser'; -import prettier from 'eslint-plugin-prettier'; - -export default [ - js.configs.recommended, +module.exports = [ { files: ['**/*.ts', '**/*.tsx'], languageOptions: { - parser: typescriptParser, + parser: require('@typescript-eslint/parser'), parserOptions: { ecmaVersion: 2022, sourceType: 'module', @@ -16,12 +10,10 @@ export default [ }, }, plugins: { - '@typescript-eslint': typescript, - prettier: prettier, + '@typescript-eslint': require('@typescript-eslint/eslint-plugin'), + prettier: require('eslint-plugin-prettier'), }, rules: { - ...typescript.configs.recommended.rules, - ...typescript.configs['recommended-requiring-type-checking'].rules, 'prettier/prettier': 'error', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], '@typescript-eslint/explicit-function-return-type': 'warn', @@ -34,6 +26,8 @@ export default [ 'object-shorthand': 'error', 'prefer-template': 'error', }, + }, + { ignores: [ 'node_modules/**', 'dist/**', diff --git a/package.json b/package.json index 879ec93..71b999f 100644 --- a/package.json +++ b/package.json @@ -40,19 +40,20 @@ "cors": "^2.8.5", "dotenv": "^17.2.2", "express": "^5.1.0", - "mime": "^4.1.0", - "multer": "^2.0.2", - "helmet": "^8.0.0", "express-rate-limit": "^7.4.1", "express-validator": "^7.2.0", + "helmet": "^8.0.0", + "mime": "^4.1.0", + "multer": "^2.0.2", "winston": "^3.17.0" }, "devDependencies": { + "@eslint/js": "^9.36.0", "@types/cors": "^2.8.19", "@types/express": "^5.0.3", + "@types/jest": "^29.5.14", "@types/multer": "^2.0.0", "@types/node": "^24.3.1", - "@types/jest": "^29.5.14", "@types/supertest": "^6.0.2", "@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/parser": "^8.18.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4f3c93..7f11c9d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,6 +39,9 @@ importers: specifier: ^3.17.0 version: 3.17.0 devDependencies: + '@eslint/js': + specifier: ^9.36.0 + version: 9.36.0 '@types/cors': specifier: ^2.8.19 version: 2.8.19 diff --git a/src/middleware/errorHandler.ts b/src/middleware/errorHandler.ts index 3e089b3..81fd625 100644 --- a/src/middleware/errorHandler.ts +++ b/src/middleware/errorHandler.ts @@ -1,5 +1,5 @@ -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 @@ -8,10 +8,10 @@ export const errorHandler = ( error: Error, req: Request, res: Response, - next: NextFunction + 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:`, { @@ -20,7 +20,7 @@ export const errorHandler = ( path: req.path, method: req.method, body: req.body, - query: req.query + query: req.query, }); // Don't send error response if headers already sent @@ -30,49 +30,63 @@ 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', - error: errorMessage + 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, requestId, - timestamp + timestamp, }; } - console.log(`[${timestamp}] [${requestId}] Sending error response: ${statusCode} - ${errorMessage}`); + console.log( + `[${timestamp}] [${requestId}] Sending error response: ${statusCode} - ${errorMessage}`, + ); res.status(statusCode).json(errorResponse); }; @@ -82,14 +96,16 @@ 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', - error: `The requested endpoint ${req.method} ${req.path} does not exist` + message: "Route not found", + error: `The requested endpoint ${req.method} ${req.path} does not exist`, }; res.status(404).json(notFoundResponse); @@ -98,6 +114,7 @@ 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); -}; \ No newline at end of file +export const asyncHandler = + (fn: Function) => (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; diff --git a/src/middleware/upload.ts b/src/middleware/upload.ts index e557f22..899ee6f 100644 --- a/src/middleware/upload.ts +++ b/src/middleware/upload.ts @@ -1,24 +1,31 @@ -import multer from 'multer'; -import { Request, RequestHandler } from 'express'; +import multer from "multer"; +import { Request, RequestHandler } from "express"; // Configure multer for memory storage (we'll process files in memory) const storage = multer.memoryStorage(); // File filter for image types only -const fileFilter = (_req: Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => { - const allowedTypes = [ - 'image/png', - 'image/jpeg', - 'image/jpg', - 'image/webp' - ]; +const fileFilter = ( + _req: Request, + file: Express.Multer.File, + cb: multer.FileFilterCallback, +) => { + const allowedTypes = ["image/png", "image/jpeg", "image/jpg", "image/webp"]; if (allowedTypes.includes(file.mimetype)) { - console.log(`[${new Date().toISOString()}] Accepted file: ${file.originalname} (${file.mimetype})`); + console.log( + `[${new Date().toISOString()}] Accepted file: ${file.originalname} (${file.mimetype})`, + ); cb(null, true); } else { - console.log(`[${new Date().toISOString()}] Rejected file: ${file.originalname} (${file.mimetype})`); - cb(new Error(`Unsupported file type: ${file.mimetype}. Allowed: PNG, JPEG, WebP`)); + console.log( + `[${new Date().toISOString()}] Rejected file: ${file.originalname} (${file.mimetype})`, + ); + cb( + new Error( + `Unsupported file type: ${file.mimetype}. Allowed: PNG, JPEG, WebP`, + ), + ); } }; @@ -31,59 +38,68 @@ export const upload = multer({ storage: storage, limits: { fileSize: MAX_FILE_SIZE, // 5MB per file - files: MAX_FILES, // Maximum 3 files + files: MAX_FILES, // Maximum 3 files }, - fileFilter: fileFilter + fileFilter: fileFilter, }); // Middleware for handling reference images -export const uploadReferenceImages: RequestHandler = upload.array('referenceImages', MAX_FILES); +export const uploadReferenceImages: RequestHandler = upload.array( + "referenceImages", + MAX_FILES, +); // Error handler for multer errors -export const handleUploadErrors = (error: any, _req: Request, res: any, next: any) => { +export const handleUploadErrors = ( + error: any, + _req: Request, + res: any, + next: any, +) => { if (error instanceof multer.MulterError) { const timestamp = new Date().toISOString(); console.error(`[${timestamp}] Multer error:`, error.message); switch (error.code) { - case 'LIMIT_FILE_SIZE': + case "LIMIT_FILE_SIZE": return res.status(400).json({ success: false, error: `File too large. Maximum size: ${MAX_FILE_SIZE / (1024 * 1024)}MB`, - message: 'File upload failed' + message: "File upload failed", }); - case 'LIMIT_FILE_COUNT': + case "LIMIT_FILE_COUNT": return res.status(400).json({ success: false, error: `Too many files. Maximum: ${MAX_FILES} files`, - message: 'File upload failed' + message: "File upload failed", }); - case 'LIMIT_UNEXPECTED_FILE': + case "LIMIT_UNEXPECTED_FILE": return res.status(400).json({ success: false, - error: 'Unexpected file field. Use "referenceImages" for image uploads', - message: 'File upload failed' + error: + 'Unexpected file field. Use "referenceImages" for image uploads', + message: "File upload failed", }); default: return res.status(400).json({ success: false, error: error.message, - message: 'File upload failed' + message: "File upload failed", }); } } - if (error.message.includes('Unsupported file type')) { + if (error.message.includes("Unsupported file type")) { return res.status(400).json({ success: false, error: error.message, - message: 'File validation failed' + message: "File validation failed", }); } // Pass other errors to the next error handler next(error); -}; \ No newline at end of file +}; diff --git a/src/middleware/validation.ts b/src/middleware/validation.ts index b868e4f..9c1c3b4 100644 --- a/src/middleware/validation.ts +++ b/src/middleware/validation.ts @@ -1,18 +1,18 @@ -import { Response, NextFunction } from 'express'; +import { Response, NextFunction } from "express"; // Validation rules const VALIDATION_RULES = { prompt: { minLength: 3, maxLength: 2000, - required: true + required: true, }, filename: { minLength: 1, maxLength: 100, required: true, - pattern: /^[a-zA-Z0-9_-]+$/ // Only alphanumeric, underscore, hyphen - } + pattern: /^[a-zA-Z0-9_-]+$/, // Only alphanumeric, underscore, hyphen + }, }; /** @@ -20,10 +20,10 @@ const VALIDATION_RULES = { */ export const sanitizeFilename = (filename: string): string => { return filename - .replace(/[^a-zA-Z0-9_-]/g, '_') // Replace invalid chars with underscore - .replace(/_{2,}/g, '_') // Replace multiple underscores with single - .replace(/^_+|_+$/g, '') // Remove leading/trailing underscores - .substring(0, 100); // Limit length + .replace(/[^a-zA-Z0-9_-]/g, "_") // Replace invalid chars with underscore + .replace(/_{2,}/g, "_") // Replace multiple underscores with single + .replace(/^_+|_+$/g, "") // Remove leading/trailing underscores + .substring(0, 100); // Limit length }; /** @@ -32,7 +32,7 @@ export const sanitizeFilename = (filename: string): string => { export const validateGenerateRequest = ( req: any, res: Response, - next: NextFunction + next: NextFunction, ): void | Response => { const timestamp = new Date().toISOString(); const { prompt, filename } = req.body; @@ -42,26 +42,34 @@ export const validateGenerateRequest = ( // 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", + ); } // Check for XSS attempts in prompt @@ -71,20 +79,22 @@ export const validateGenerateRequest = ( /on\w+\s*=/i, /