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>
131 lines
No EOL
3.6 KiB
JavaScript
131 lines
No EOL
3.6 KiB
JavaScript
class Location {
|
|
constructor(db) {
|
|
this.db = db;
|
|
}
|
|
|
|
async getActive(hoursThreshold = 48) {
|
|
const cutoffTime = new Date(Date.now() - hoursThreshold * 60 * 60 * 1000).toISOString();
|
|
return new Promise((resolve, reject) => {
|
|
this.db.all(
|
|
'SELECT * FROM locations WHERE created_at > ? OR persistent = 1 ORDER BY created_at DESC',
|
|
[cutoffTime],
|
|
(err, rows) => {
|
|
if (err) return reject(err);
|
|
resolve(rows);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async getAll() {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.all(
|
|
'SELECT id, address, description, latitude, longitude, persistent, created_at FROM locations ORDER BY created_at DESC',
|
|
[],
|
|
(err, rows) => {
|
|
if (err) return reject(err);
|
|
resolve(rows);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async create(location) {
|
|
const { address, latitude, longitude, description } = location;
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(
|
|
'INSERT INTO locations (address, latitude, longitude, description) VALUES (?, ?, ?, ?)',
|
|
[address, latitude, longitude, description],
|
|
function(err) {
|
|
if (err) return reject(err);
|
|
resolve({ id: this.lastID, ...location });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async update(id, location) {
|
|
const { address, latitude, longitude, description } = location;
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(
|
|
'UPDATE locations SET address = ?, latitude = ?, longitude = ?, description = ? WHERE id = ?',
|
|
[address, latitude, longitude, description, id],
|
|
function(err) {
|
|
if (err) return reject(err);
|
|
resolve({ changes: this.changes });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async togglePersistent(id, persistent) {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(
|
|
'UPDATE locations SET persistent = ? WHERE id = ?',
|
|
[persistent ? 1 : 0, id],
|
|
function(err) {
|
|
if (err) return reject(err);
|
|
resolve({ changes: this.changes });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async delete(id) {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(
|
|
'DELETE FROM locations WHERE id = ?',
|
|
[id],
|
|
function(err) {
|
|
if (err) return reject(err);
|
|
resolve({ changes: this.changes });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async cleanupExpired(hoursThreshold = 48) {
|
|
const cutoffTime = new Date(Date.now() - hoursThreshold * 60 * 60 * 1000).toISOString();
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(
|
|
'DELETE FROM locations WHERE created_at < ? AND persistent = 0',
|
|
[cutoffTime],
|
|
function(err) {
|
|
if (err) return reject(err);
|
|
resolve({ changes: this.changes });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async initializeTable() {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.serialize(() => {
|
|
this.db.run(`
|
|
CREATE TABLE IF NOT EXISTS locations (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
address TEXT NOT NULL,
|
|
latitude REAL,
|
|
longitude REAL,
|
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
description TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
`, (err) => {
|
|
if (err) return reject(err);
|
|
|
|
this.db.run(`
|
|
ALTER TABLE locations ADD COLUMN persistent INTEGER DEFAULT 0
|
|
`, (err) => {
|
|
if (err && !err.message.includes('duplicate column name')) {
|
|
return reject(err);
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = Location; |