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
This commit is contained in:
parent
d19cd2766c
commit
c7f39e4939
15 changed files with 5365 additions and 257 deletions
|
@ -319,7 +319,10 @@
|
|||
<div class="admin-container">
|
||||
<!-- Login Section -->
|
||||
<div id="login-section" class="login-section">
|
||||
<h2>🔐 Admin Login</h2>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h2 style="margin: 0;">🔐 Admin Login</h2>
|
||||
<a href="/" class="header-btn btn-home" style="text-decoration: none; font-size: 12px; padding: 8px 12px;">🏠 Back to Homepage</a>
|
||||
</div>
|
||||
<form id="login-form">
|
||||
<div class="form-group">
|
||||
<label for="password">Admin Password:</label>
|
||||
|
@ -387,6 +390,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://iceymi.b-cdn.net/admin.js"></script>
|
||||
<script src="admin.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -453,7 +453,14 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
}),
|
||||
});
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (!res.ok) {
|
||||
return res.json().then(errorData => {
|
||||
throw { status: res.status, data: errorData };
|
||||
});
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(location => {
|
||||
refreshLocations();
|
||||
messageDiv.textContent = 'Location reported successfully!';
|
||||
|
@ -462,17 +469,34 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
})
|
||||
.catch(err => {
|
||||
console.error('Error reporting location:', err);
|
||||
messageDiv.textContent = 'Error reporting location.';
|
||||
messageDiv.className = 'message error';
|
||||
|
||||
// Handle specific profanity rejection
|
||||
if (err.status === 400 && err.data && err.data.error === 'Submission rejected') {
|
||||
messageDiv.textContent = err.data.message;
|
||||
messageDiv.className = 'message error profanity-rejection';
|
||||
|
||||
// Clear the description field to encourage rewriting
|
||||
document.getElementById('description').value = '';
|
||||
document.getElementById('description').focus();
|
||||
} else if (err.data && err.data.error) {
|
||||
messageDiv.textContent = err.data.error;
|
||||
messageDiv.className = 'message error';
|
||||
} else {
|
||||
messageDiv.textContent = 'Error reporting location. Please try again.';
|
||||
messageDiv.className = 'message error';
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
submitBtn.disabled = false;
|
||||
submitText.style.display = 'inline';
|
||||
submitLoading.style.display = 'none';
|
||||
messageDiv.style.display = 'block';
|
||||
|
||||
// Longer timeout for profanity rejection messages
|
||||
const timeout = messageDiv.className.includes('profanity-rejection') ? 15000 : 3000;
|
||||
setTimeout(() => {
|
||||
messageDiv.style.display = 'none';
|
||||
}, 3000);
|
||||
}, timeout);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ placeholder="Enter address, intersection (e.g., Main St & Second St, City), or l
|
|||
<label for="description">Additional Details (Optional)</label>
|
||||
<textarea id="description" name="description" rows="3"
|
||||
placeholder="Number of vehicles, time observed, etc."></textarea>
|
||||
<small class="input-help">Keep descriptions appropriate and relevant to road conditions. Submissions with inappropriate language will be rejected.</small>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submit-btn">
|
||||
|
|
|
@ -182,6 +182,7 @@ input[type="text"], textarea {
|
|||
display: block;
|
||||
}
|
||||
|
||||
|
||||
button[type="submit"] {
|
||||
background-color: var(--button-bg);
|
||||
color: white;
|
||||
|
@ -213,6 +214,26 @@ button[type="submit"]:hover {
|
|||
color: #721c24;
|
||||
}
|
||||
|
||||
.message.error.profanity-rejection {
|
||||
background-color: #f5c6cb;
|
||||
color: #721c24;
|
||||
border: 2px solid #dc3545;
|
||||
font-weight: bold;
|
||||
animation: pulse 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .message.error.profanity-rejection {
|
||||
background-color: #3d1b1c;
|
||||
border-color: #dc3545;
|
||||
color: #f5c6cb;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.02); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
/* Header layout for theme toggle */
|
||||
.header-content {
|
||||
display: flex;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue