ice/routes/locations.js
Deco Vander c7f39e4939 feat: isolate profanity filter with separate database
- Create dedicated ProfanityFilter class with isolated SQLite database
- Separate profanity.db from main application database to prevent SQLITE_MISUSE errors
- Add comprehensive custom word management (CRUD operations)
- Implement advanced profanity detection with leetspeak and pattern matching
- Add admin UI for managing custom profanity words
- Add extensive test suites for both profanity filter and API routes
- Update server.js to use isolated profanity filter
- Add proper database initialization and cleanup methods
- Support in-memory databases for testing

Breaking changes:
- Profanity filter now uses separate database file
- Updated admin API endpoints for profanity management
- Enhanced profanity detection capabilities
2025-07-04 00:03:24 -04:00

108 lines
4.4 KiB
JavaScript

const express = require('express');
const router = express.Router();
module.exports = (db, profanityFilter) => {
// Get all active locations (within 48 hours OR persistent)
router.get('/', (req, res) => {
console.log('Fetching active locations');
const fortyEightHoursAgo = new Date(Date.now() - 48 * 60 * 60 * 1000).toISOString();
db.all(
'SELECT * FROM locations WHERE created_at > ? OR persistent = 1 ORDER BY created_at DESC',
[fortyEightHoursAgo],
(err, rows) => {
if (err) {
console.error('Error fetching locations:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
console.log(`Fetched ${rows.length} active locations (including persistent)`);
res.json(rows);
}
);
});
// Add a new location
router.post('/', (req, res) => {
const { address, latitude, longitude } = req.body;
let { description } = req.body;
console.log(`Attempt to add new location: ${address}`);
if (!address) {
console.warn('Failed to add location: Address is required');
res.status(400).json({ error: 'Address is required' });
return;
}
// Check for profanity in description and reject if any is found
if (description && profanityFilter) {
try {
const analysis = profanityFilter.analyzeProfanity(description);
if (analysis.hasProfanity) {
console.warn(`Submission rejected due to inappropriate language (${analysis.count} word${analysis.count > 1 ? 's' : ''}, severity: ${analysis.severity}) - Original: "${req.body.description}"`);
// Reject any submission with profanity
const wordText = analysis.count === 1 ? 'word' : 'words';
const detectedWords = analysis.matches.map(m => m.word).join(', ');
return res.status(400).json({
error: 'Submission rejected',
message: `Your description contains inappropriate language and cannot be posted. Please revise your description to focus on road conditions and keep it professional.\n\nExample: "Multiple vehicles stuck, black ice present" or "Road very slippery, saw 3 accidents"`,
details: {
severity: analysis.severity,
wordCount: analysis.count,
detectedCategories: [...new Set(analysis.matches.map(m => m.category))]
}
});
}
} catch (filterError) {
console.error('Error checking profanity:', filterError);
// Continue with original description if filter fails
}
}
db.run(
'INSERT INTO locations (address, latitude, longitude, description) VALUES (?, ?, ?, ?)',
[address, latitude, longitude, description],
function(err) {
if (err) {
console.error('Error inserting location:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
console.log(`Location added successfully: ${address}`);
res.json({
id: this.lastID,
address,
latitude,
longitude,
description,
created_at: new Date().toISOString()
});
}
);
});
// Legacy delete route (keeping for backwards compatibility)
router.delete('/:id', (req, res) => {
const { id } = req.params;
db.run('DELETE FROM locations WHERE id = ?', [id], function(err) {
if (err) {
console.error('Error deleting location:', err);
res.status(500).json({ error: 'Internal server error' });
return;
}
if (this.changes === 0) {
res.status(404).json({ error: 'Location not found' });
return;
}
res.json({ message: 'Location deleted successfully' });
});
});
return router;
};