UNPKG

id-auto-formalizer

Version:

Konversi teks Bahasa Indonesia dari kasual ke formal untuk surat, email, dan laporan resmi

414 lines (358 loc) • 10.6 kB
// REST API Server untuk Bahasa Indonesia Auto Formalizer const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const IndonesianFormalizer = require('../src/formalizer'); const app = express(); const PORT = process.env.PORT || 3000; const formalizer = new IndonesianFormalizer(); // Middleware app.use(helmet()); app.use(cors()); app.use(express.json({ limit: '1mb' })); app.use(express.urlencoded({ extended: true })); // Rate limiting const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs message: 'Too many requests from this IP, please try again later.' }); const strictLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute max: 10, // limit each IP to 10 requests per minute for batch operations message: 'Too many batch requests, please try again later.' }); app.use('/api/', limiter); app.use('/api/batch', strictLimiter); // Request logging middleware app.use((req, res, next) => { console.log(`${new Date().toISOString()} ${req.method} ${req.path}`); next(); }); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), service: 'id-auto-formalizer-api' }); }); // API Info app.get('/api', (req, res) => { res.json({ name: 'Bahasa Indonesia Auto Formalizer API', version: '1.0.0', endpoints: { 'POST /api/formalize': 'Formalisasi teks tunggal', 'POST /api/batch': 'Formalisasi batch (max 100 teks)', 'POST /api/analyze': 'Analisis formalitas teks', 'POST /api/suggest': 'Dapatkan saran perbaikan', 'GET /api/stats': 'Statistik mapping', 'GET /api/mappings': 'List semua mapping (paginated)', 'POST /api/mappings': 'Tambah mapping baru (requires auth)', 'GET /health': 'Health check' } }); }); // Main formalization endpoint app.post('/api/formalize', (req, res) => { try { const { text, options = {} } = req.body; if (!text) { return res.status(400).json({ error: 'Text is required', code: 'MISSING_TEXT' }); } if (typeof text !== 'string') { return res.status(400).json({ error: 'Text must be a string', code: 'INVALID_TEXT_TYPE' }); } if (text.length > 10000) { return res.status(400).json({ error: 'Text too long. Maximum 10,000 characters', code: 'TEXT_TOO_LONG' }); } const result = formalizer.formalize(text, options); const analysis = formalizer.analyzeFormalityLevel(text); res.json({ success: true, data: { original: text, formalized: result, analysis: analysis, options: options }, timestamp: new Date().toISOString() }); } catch (error) { console.error('Formalize error:', error); res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' }); } }); // Batch formalization endpoint app.post('/api/batch', (req, res) => { try { const { texts, options = {} } = req.body; if (!texts || !Array.isArray(texts)) { return res.status(400).json({ error: 'Texts array is required', code: 'MISSING_TEXTS' }); } if (texts.length === 0) { return res.status(400).json({ error: 'Texts array cannot be empty', code: 'EMPTY_TEXTS' }); } if (texts.length > 100) { return res.status(400).json({ error: 'Too many texts. Maximum 100 texts per batch', code: 'BATCH_TOO_LARGE' }); } // Validate each text for (let i = 0; i < texts.length; i++) { if (typeof texts[i] !== 'string') { return res.status(400).json({ error: `Text at index ${i} must be a string`, code: 'INVALID_TEXT_TYPE' }); } if (texts[i].length > 5000) { return res.status(400).json({ error: `Text at index ${i} is too long. Maximum 5,000 characters per text`, code: 'TEXT_TOO_LONG' }); } } const results = formalizer.formalizeBatch(texts, options); res.json({ success: true, data: { results: results, count: results.length, options: options }, timestamp: new Date().toISOString() }); } catch (error) { console.error('Batch error:', error); res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' }); } }); // Analysis endpoint app.post('/api/analyze', (req, res) => { try { const { text } = req.body; if (!text) { return res.status(400).json({ error: 'Text is required', code: 'MISSING_TEXT' }); } const analysis = formalizer.analyzeFormalityLevel(text); res.json({ success: true, data: analysis, timestamp: new Date().toISOString() }); } catch (error) { console.error('Analyze error:', error); res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' }); } }); // Suggestions endpoint app.post('/api/suggest', (req, res) => { try { const { text } = req.body; if (!text) { return res.status(400).json({ error: 'Text is required', code: 'MISSING_TEXT' }); } const suggestions = formalizer.suggestImprovements(text); res.json({ success: true, data: { text: text, suggestions: suggestions, count: suggestions.length }, timestamp: new Date().toISOString() }); } catch (error) { console.error('Suggest error:', error); res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' }); } }); // Statistics endpoint app.get('/api/stats', (req, res) => { try { const stats = formalizer.getStatistics(); res.json({ success: true, data: stats, timestamp: new Date().toISOString() }); } catch (error) { console.error('Stats error:', error); res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' }); } }); // Mappings endpoint (paginated) app.get('/api/mappings', (req, res) => { try { const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 50; const search = req.query.search || ''; if (limit > 200) { return res.status(400).json({ error: 'Limit too high. Maximum 200 items per page', code: 'LIMIT_TOO_HIGH' }); } const allMappings = formalizer.exportMappings(); const mappingsArray = Object.entries(allMappings); // Filter by search term if provided const filtered = search ? mappingsArray.filter(([informal, formal]) => informal.includes(search.toLowerCase()) || formal.toLowerCase().includes(search.toLowerCase()) ) : mappingsArray; // Paginate const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; const paginatedMappings = filtered.slice(startIndex, endIndex); res.json({ success: true, data: { mappings: Object.fromEntries(paginatedMappings), pagination: { page: page, limit: limit, total: filtered.length, totalPages: Math.ceil(filtered.length / limit), hasNext: endIndex < filtered.length, hasPrev: page > 1 } }, timestamp: new Date().toISOString() }); } catch (error) { console.error('Mappings error:', error); res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' }); } }); // Add mapping endpoint (requires authentication in production) app.post('/api/mappings', (req, res) => { try { // In production, add authentication middleware here const authHeader = req.headers.authorization; if (!authHeader || authHeader !== `Bearer ${process.env.API_KEY}`) { return res.status(401).json({ error: 'Unauthorized', code: 'UNAUTHORIZED' }); } const { informal, formal } = req.body; if (!informal || !formal) { return res.status(400).json({ error: 'Both informal and formal words are required', code: 'MISSING_FIELDS' }); } if (typeof informal !== 'string' || typeof formal !== 'string') { return res.status(400).json({ error: 'Words must be strings', code: 'INVALID_TYPE' }); } formalizer.addMapping(informal.toLowerCase(), formal); res.json({ success: true, data: { informal: informal.toLowerCase(), formal: formal, message: 'Mapping added successfully' }, timestamp: new Date().toISOString() }); } catch (error) { console.error('Add mapping error:', error); res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' }); } }); // 404 handler app.use((req, res) => { res.status(404).json({ error: 'Endpoint not found', code: 'NOT_FOUND', path: req.path }); }); // Global error handler app.use((err, req, res, next) => { console.error('Global error:', err); res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' }); }); // Start server const server = app.listen(PORT, () => { console.log(` šŸ‡®šŸ‡© Bahasa Indonesia Auto Formalizer API ===================================== Server running on port ${PORT} Environment: ${process.env.NODE_ENV || 'development'} API Base URL: http://localhost:${PORT}/api Health Check: http://localhost:${PORT}/health ===================================== `); }); // Graceful shutdown process.on('SIGTERM', () => { console.log('SIGTERM received. Shutting down gracefully...'); server.close(() => { console.log('Server closed'); process.exit(0); }); }); process.on('SIGINT', () => { console.log('SIGINT received. Shutting down gracefully...'); server.close(() => { console.log('Server closed'); process.exit(0); }); }); module.exports = app;