Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions src/server/migrations/001_v37_baseline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,14 +481,25 @@ export const migration = {
db.exec(`
CREATE TABLE IF NOT EXISTS user_map_preferences (
id INTEGER PRIMARY KEY AUTOINCREMENT,
userId INTEGER NOT NULL,
user_id INTEGER NOT NULL,
centerLat REAL,
centerLng REAL,
zoom REAL,
selectedLayer TEXT,
map_tileset TEXT,
show_paths INTEGER DEFAULT 0,
show_neighbor_info INTEGER DEFAULT 0,
show_route INTEGER DEFAULT 1,
show_motion INTEGER DEFAULT 1,
show_mqtt_nodes INTEGER DEFAULT 1,
show_meshcore_nodes INTEGER DEFAULT 1,
show_animations INTEGER DEFAULT 0,
show_accuracy_regions INTEGER DEFAULT 0,
show_estimated_positions INTEGER DEFAULT 0,
position_history_hours INTEGER,
createdAt INTEGER NOT NULL,
updatedAt INTEGER NOT NULL,
FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
`);

Expand Down Expand Up @@ -900,6 +911,7 @@ export async function runMigration001Postgres(client: PoolClient): Promise<void>
"routeBack" TEXT,
"snrTowards" TEXT,
"snrBack" TEXT,
"routePositions" TEXT,
timestamp BIGINT NOT NULL,
"createdAt" BIGINT NOT NULL
);
Expand All @@ -912,6 +924,10 @@ export async function runMigration001Postgres(client: PoolClient): Promise<void>
"toNodeId" TEXT NOT NULL,
"distanceKm" REAL NOT NULL,
"isRecordHolder" BOOLEAN DEFAULT false,
"fromLatitude" DOUBLE PRECISION,
"fromLongitude" DOUBLE PRECISION,
"toLatitude" DOUBLE PRECISION,
"toLongitude" DOUBLE PRECISION,
timestamp BIGINT NOT NULL,
"createdAt" BIGINT NOT NULL
);
Expand Down Expand Up @@ -1521,6 +1537,7 @@ export async function runMigration001Mysql(pool: MySQLPool): Promise<void> {
routeBack TEXT,
snrTowards TEXT,
snrBack TEXT,
routePositions TEXT,
timestamp BIGINT NOT NULL,
createdAt BIGINT NOT NULL,
INDEX idx_traceroutes_from_to (fromNodeNum, toNodeNum),
Expand All @@ -1535,6 +1552,10 @@ export async function runMigration001Mysql(pool: MySQLPool): Promise<void> {
toNodeId VARCHAR(32) NOT NULL,
distanceKm DOUBLE NOT NULL,
isRecordHolder BOOLEAN DEFAULT false,
fromLatitude DOUBLE,
fromLongitude DOUBLE,
toLatitude DOUBLE,
toLongitude DOUBLE,
timestamp BIGINT NOT NULL,
createdAt BIGINT NOT NULL,
INDEX idx_route_segments_from_to (fromNodeNum, toNodeNum)
Expand Down
39 changes: 36 additions & 3 deletions src/server/migrations/007_add_missing_map_preference_columns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,42 @@ import { PoolClient } from 'pg';
import { Pool as MySQLPool } from 'mysql2/promise';
import { logger } from '../../utils/logger.js';

export function runMigration083Sqlite(_db: Database.Database): void {
// SQLite already has all columns from migration 030 — nothing to do
logger.debug('Migration 083: SQLite already has all map preference columns, skipping.');
export function runMigration083Sqlite(db: Database.Database): void {
// v3.7 baseline may not have these columns — add idempotently
const columnsToAdd = [
{ name: 'map_tileset', type: 'TEXT' },
{ name: 'show_paths', type: 'INTEGER DEFAULT 0' },
{ name: 'show_neighbor_info', type: 'INTEGER DEFAULT 0' },
{ name: 'show_route', type: 'INTEGER DEFAULT 1' },
{ name: 'show_motion', type: 'INTEGER DEFAULT 1' },
{ name: 'show_mqtt_nodes', type: 'INTEGER DEFAULT 1' },
{ name: 'show_meshcore_nodes', type: 'INTEGER DEFAULT 1' },
{ name: 'show_animations', type: 'INTEGER DEFAULT 0' },
{ name: 'show_accuracy_regions', type: 'INTEGER DEFAULT 0' },
{ name: 'show_estimated_positions', type: 'INTEGER DEFAULT 0' },
{ name: 'position_history_hours', type: 'INTEGER' },
];

for (const col of columnsToAdd) {
try {
db.exec(`ALTER TABLE user_map_preferences ADD COLUMN ${col.name} ${col.type}`);
logger.debug(`✅ Added ${col.name} column to user_map_preferences`);
} catch {
// Column already exists — ignore
}
}

// Also ensure user_id column exists (baseline may have userId instead)
try {
db.exec(`ALTER TABLE user_map_preferences ADD COLUMN user_id INTEGER REFERENCES users(id) ON DELETE CASCADE`);
// If we added user_id, populate from userId
db.exec(`UPDATE user_map_preferences SET user_id = userId WHERE user_id IS NULL`);
logger.debug('✅ Added user_id column to user_map_preferences');
} catch {
// Column already exists — ignore
}

logger.debug('Migration 083: SQLite map preference columns ensured.');
}

export async function runMigration083Postgres(client: PoolClient): Promise<void> {
Expand Down
43 changes: 18 additions & 25 deletions src/server/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,38 +395,31 @@ export class UserModel {
* Get user's map preferences
*/
getMapPreferences(userId: number): Record<string, any> | null {
// Detect which user ID column exists (userId vs user_id depending on migration state)
const columns = this.db.prepare("PRAGMA table_info(user_map_preferences)").all() as any[];
const userIdCol = columns.find((c: any) => c.name === 'user_id') ? 'user_id' : 'userId';

const stmt = this.db.prepare(`
SELECT
map_tileset as mapTileset,
show_paths as showPaths,
show_neighbor_info as showNeighborInfo,
show_route as showRoute,
show_motion as showMotion,
show_mqtt_nodes as showMqttNodes,
show_meshcore_nodes as showMeshCoreNodes,
show_animations as showAnimations,
show_accuracy_regions as showAccuracyRegions,
show_estimated_positions as showEstimatedPositions,
position_history_hours as positionHistoryHours
FROM user_map_preferences
WHERE user_id = ?
SELECT * FROM user_map_preferences
WHERE ${userIdCol} = ?
LIMIT 1
`);

const row = stmt.get(userId) as any;
if (!row) return null;

return {
mapTileset: row.mapTileset || null,
showPaths: Boolean(row.showPaths),
showNeighborInfo: Boolean(row.showNeighborInfo),
showRoute: Boolean(row.showRoute),
showMotion: Boolean(row.showMotion),
showMqttNodes: Boolean(row.showMqttNodes),
showMeshCoreNodes: Boolean(row.showMeshCoreNodes),
showAnimations: Boolean(row.showAnimations),
showAccuracyRegions: Boolean(row.showAccuracyRegions),
showEstimatedPositions: Boolean(row.showEstimatedPositions),
positionHistoryHours: row.positionHistoryHours ?? null,
mapTileset: row.map_tileset || row.mapTileset || null,
showPaths: Boolean(row.show_paths ?? row.showPaths ?? false),
showNeighborInfo: Boolean(row.show_neighbor_info ?? row.showNeighborInfo ?? false),
showRoute: Boolean(row.show_route ?? row.showRoute ?? true),
showMotion: Boolean(row.show_motion ?? row.showMotion ?? true),
showMqttNodes: Boolean(row.show_mqtt_nodes ?? row.showMqttNodes ?? true),
showMeshCoreNodes: Boolean(row.show_meshcore_nodes ?? row.showMeshCoreNodes ?? true),
showAnimations: Boolean(row.show_animations ?? row.showAnimations ?? false),
showAccuracyRegions: Boolean(row.show_accuracy_regions ?? row.showAccuracyRegions ?? false),
showEstimatedPositions: Boolean(row.show_estimated_positions ?? row.showEstimatedPositions ?? false),
positionHistoryHours: row.position_history_hours ?? row.positionHistoryHours ?? null,
};
}

Expand Down
17 changes: 10 additions & 7 deletions src/server/services/systemBackupService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,8 +361,11 @@ class SystemBackupService {
*/
async listBackups(): Promise<SystemBackupInfo[]> {
try {
const dbType = databaseService.drizzleDbType;
const col = (name: string) => dbType === 'postgres' ? `"${name}"` : name;

const rows = await this.queryRows(`
SELECT dirname, timestamp, type, size, table_count, meshmonitor_version, schema_version
SELECT ${col('backupPath')}, timestamp, ${col('backupType')}, ${col('totalSize')}, ${col('tableCount')}, ${col('appVersion')}, ${col('schemaVersion')}
FROM system_backup_history
ORDER BY timestamp DESC
`);
Expand All @@ -371,14 +374,14 @@ class SystemBackupService {
// PostgreSQL returns bigint as strings, so we need to parse them
const timestampNum = typeof row.timestamp === 'string' ? parseInt(row.timestamp, 10) : row.timestamp;
return {
dirname: row.dirname,
dirname: row.backupPath,
timestamp: new Date(timestampNum).toISOString(),
timestampUnix: timestampNum,
type: row.type,
size: typeof row.size === 'string' ? parseInt(row.size, 10) : row.size,
tableCount: row.table_count,
meshmonitorVersion: row.meshmonitor_version,
schemaVersion: row.schema_version
type: row.backupType,
size: typeof row.totalSize === 'string' ? parseInt(row.totalSize, 10) : row.totalSize,
tableCount: row.tableCount,
meshmonitorVersion: row.appVersion,
schemaVersion: row.schemaVersion
};
});
} catch (error) {
Expand Down
Loading
Loading