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
JavaScript
// 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;