- Add explicit latitude/longitude validation in location submissions - Implement ESLint with TypeScript support and flat config - Auto-fix 621 formatting issues across codebase - Add comprehensive tests for coordinate validation - Update documentation with lint scripts and validation rules - Maintain 128 passing tests with enhanced security 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
162 lines
No EOL
4.9 KiB
TypeScript
162 lines
No EOL
4.9 KiB
TypeScript
import { Database } from 'sqlite3';
|
|
import { Location as LocationInterface } from '../types';
|
|
|
|
export interface LocationCreateInput {
|
|
address: string;
|
|
latitude?: number | undefined;
|
|
longitude?: number | undefined;
|
|
description?: string | undefined;
|
|
}
|
|
|
|
export interface LocationUpdateInput {
|
|
address?: string | undefined;
|
|
latitude?: number | undefined;
|
|
longitude?: number | undefined;
|
|
description?: string | undefined;
|
|
}
|
|
|
|
export interface DatabaseResult {
|
|
changes: number;
|
|
}
|
|
|
|
export interface LocationCreatedResult {
|
|
id: number;
|
|
address: string;
|
|
latitude?: number | undefined;
|
|
longitude?: number | undefined;
|
|
description?: string | undefined;
|
|
}
|
|
|
|
class Location {
|
|
private db: Database;
|
|
|
|
constructor(db: Database) {
|
|
this.db = db;
|
|
}
|
|
|
|
async getActive(hoursThreshold: number = 48): Promise<LocationInterface[]> {
|
|
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: Error | null, rows: LocationInterface[]) => {
|
|
if (err) return reject(err);
|
|
resolve(rows);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async getAll(): Promise<LocationInterface[]> {
|
|
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: Error | null, rows: LocationInterface[]) => {
|
|
if (err) return reject(err);
|
|
resolve(rows);
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async create(location: LocationCreateInput): Promise<LocationCreatedResult> {
|
|
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(this: { lastID: number }, err: Error | null) {
|
|
if (err) return reject(err);
|
|
resolve({ id: this.lastID, ...location });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async update(id: number, location: LocationUpdateInput): Promise<DatabaseResult> {
|
|
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(this: { changes: number }, err: Error | null) {
|
|
if (err) return reject(err);
|
|
resolve({ changes: this.changes });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async togglePersistent(id: number, persistent: boolean): Promise<DatabaseResult> {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(
|
|
'UPDATE locations SET persistent = ? WHERE id = ?',
|
|
[persistent ? 1 : 0, id],
|
|
function(this: { changes: number }, err: Error | null) {
|
|
if (err) return reject(err);
|
|
resolve({ changes: this.changes });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async delete(id: number): Promise<DatabaseResult> {
|
|
return new Promise((resolve, reject) => {
|
|
this.db.run(
|
|
'DELETE FROM locations WHERE id = ?',
|
|
[id],
|
|
function(this: { changes: number }, err: Error | null) {
|
|
if (err) return reject(err);
|
|
resolve({ changes: this.changes });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async cleanupExpired(hoursThreshold: number = 48): Promise<DatabaseResult> {
|
|
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(this: { changes: number }, err: Error | null) {
|
|
if (err) return reject(err);
|
|
resolve({ changes: this.changes });
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async initializeTable(): Promise<void> {
|
|
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: Error | null) => {
|
|
if (err) return reject(err);
|
|
|
|
this.db.run(`
|
|
ALTER TABLE locations ADD COLUMN persistent INTEGER DEFAULT 0
|
|
`, (err: Error | null) => {
|
|
if (err && !err.message.includes('duplicate column name')) {
|
|
return reject(err);
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
export default Location; |