ice/routes/locations.js
Claude Code a0fffcf4f0 Refactor architecture: Add models/services layer and refactor frontend
Major architectural improvements:
- Created models/services layer for better separation of concerns
  - Location model with async methods for database operations
  - ProfanityWord model for content moderation
  - DatabaseService for centralized database management
  - ProfanityFilterService refactored to use models
- Refactored frontend map implementations to share common code
  - MapBase class extracts 60-70% of duplicate functionality
  - Refactored implementations extend MapBase for specific features
  - Maintained unique geocoding capabilities per implementation
- Updated server.js to use new service architecture
- All routes now use async/await with models instead of raw queries
- Enhanced error handling and maintainability

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-05 19:21:51 -04:00

97 lines
No EOL
3.9 KiB
JavaScript

const express = require('express');
const router = express.Router();
module.exports = (locationModel, profanityFilter) => {
// Get all active locations (within 48 hours OR persistent)
router.get('/', async (req, res) => {
console.log('Fetching active locations');
try {
const locations = await locationModel.getActive();
console.log(`Fetched ${locations.length} active locations (including persistent)`);
res.json(locations);
} catch (err) {
console.error('Error fetching locations:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
// Add a new location
router.post('/', async (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
}
}
try {
const newLocation = await locationModel.create({
address,
latitude,
longitude,
description
});
console.log(`Location added successfully: ${address}`);
res.json({
...newLocation,
created_at: new Date().toISOString()
});
} catch (err) {
console.error('Error inserting location:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
// Legacy delete route (keeping for backwards compatibility)
router.delete('/:id', async (req, res) => {
const { id } = req.params;
try {
const result = await locationModel.delete(id);
if (result.changes === 0) {
res.status(404).json({ error: 'Location not found' });
return;
}
res.json({ message: 'Location deleted successfully' });
} catch (err) {
console.error('Error deleting location:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
return router;
};