From 606481103caab09092ec8a088d083958742175d8 Mon Sep 17 00:00:00 2001 From: 4aiman Date: Mon, 25 May 2026 02:42:52 +0300 Subject: [PATCH] Faster migrations, recalculate fixes point order, egg seed guard Migrations: - Batch PRAGMA table_info per table instead of N separate hasColumn calls - Reduced IPC calls from ~22 to ~10 per startup - Egg product_units seed only runs once (checks COUNT first) - Removed unused hasColumn function Recalculation: - recalculateSession now re-orders points by timestamp - Assigns seq = 1,2,3... in timestamp order - Only updates if order is wrong (skips if already correct) - Fixes tracks where points were recorded out of order --- frontend/js/db.js | 86 +++++++++++++++++----------------------------- frontend/js/gps.js | 17 +++++++++ 2 files changed, 49 insertions(+), 54 deletions(-) diff --git a/frontend/js/db.js b/frontend/js/db.js index fadbf90..b27b2a7 100644 --- a/frontend/js/db.js +++ b/frontend/js/db.js @@ -807,25 +807,15 @@ window.DB.updateSessionBike = updateSessionBike; // JS-side migrations — safe for any DB state (checks before altering) async function runMigrations() { - async function hasColumn(table, col) { - const info = await select(`PRAGMA table_info(${table})`); - return info.some(r => r.name === col); - } - if (!(await hasColumn("products", "composition"))) { - await execute("ALTER TABLE products ADD COLUMN composition TEXT"); - } - if (!(await hasColumn("products", "name_lower"))) { - await execute("ALTER TABLE products ADD COLUMN name_lower TEXT"); - } - if (!(await hasColumn("gps_points", "seq"))) { - await execute("ALTER TABLE gps_points ADD COLUMN seq INTEGER"); - } + // Batch column checks — one PRAGMA per table instead of N separate IPC calls + const productCols = (await select("PRAGMA table_info(products)")).map(r => r.name); + const addProdCol = async (col, type) => { if (!productCols.includes(col)) await execute(`ALTER TABLE products ADD COLUMN ${col} ${type}`); }; + await addProdCol("composition", "TEXT"); + await addProdCol("name_lower", "TEXT"); + await addProdCol("nutrients_guessed", "INTEGER DEFAULT 0"); - if (!(await hasColumn("products", "nutrients_guessed"))) { - await execute("ALTER TABLE products ADD COLUMN nutrients_guessed INTEGER DEFAULT 0"); - } - - console.log("[db.js] base migrations done, starting cooking_methods..."); + const gpsCols = (await select("PRAGMA table_info(gps_points)")).map(r => r.name); + if (!gpsCols.includes("seq")) await execute("ALTER TABLE gps_points ADD COLUMN seq INTEGER"); // --- Cooking methods table --- await execute(`CREATE TABLE IF NOT EXISTS cooking_methods ( @@ -865,23 +855,14 @@ async function runMigrations() { console.log("[db.js] Seeded 6 cooking methods"); } - // --- Recipes table: add missing columns --- - if (!(await hasColumn("recipes", "cooking_method_id"))) { - await execute("ALTER TABLE recipes ADD COLUMN cooking_method_id INTEGER REFERENCES cooking_methods(id)"); - } - if (!(await hasColumn("recipes", "created_at"))) { - await execute("ALTER TABLE recipes ADD COLUMN created_at TEXT DEFAULT (datetime('now'))"); - } - // Ensure servings column exists (Rust creates it as INTEGER, we need REAL for half-portions) - // Column already exists from Rust, so we leave it as is. + // --- Recipes + ingredients: batch column checks --- + const recipeCols = (await select("PRAGMA table_info(recipes)")).map(r => r.name); + if (!recipeCols.includes("cooking_method_id")) await execute("ALTER TABLE recipes ADD COLUMN cooking_method_id INTEGER"); + if (!recipeCols.includes("created_at")) await execute("ALTER TABLE recipes ADD COLUMN created_at TEXT DEFAULT (datetime('now'))"); - // --- Recipe ingredients: add missing columns --- - if (!(await hasColumn("recipe_ingredients", "custom_name"))) { - await execute("ALTER TABLE recipe_ingredients ADD COLUMN custom_name TEXT"); - } - if (!(await hasColumn("recipe_ingredients", "cooking_method_id"))) { - await execute("ALTER TABLE recipe_ingredients ADD COLUMN cooking_method_id INTEGER REFERENCES cooking_methods(id)"); - } + const riCols = (await select("PRAGMA table_info(recipe_ingredients)")).map(r => r.name); + if (!riCols.includes("custom_name")) await execute("ALTER TABLE recipe_ingredients ADD COLUMN custom_name TEXT"); + if (!riCols.includes("cooking_method_id")) await execute("ALTER TABLE recipe_ingredients ADD COLUMN cooking_method_id INTEGER"); // --- Serving units table --- await execute(`CREATE TABLE IF NOT EXISTS serving_units ( @@ -902,12 +883,9 @@ async function runMigrations() { )`); // --- Add unit columns to food_log --- - if (!(await hasColumn("food_log", "unit_id"))) { - await execute("ALTER TABLE food_log ADD COLUMN unit_id INTEGER"); - } - if (!(await hasColumn("food_log", "unit_amount"))) { - await execute("ALTER TABLE food_log ADD COLUMN unit_amount REAL"); - } + const flCols = (await select("PRAGMA table_info(food_log)")).map(r => r.name); + if (!flCols.includes("unit_id")) await execute("ALTER TABLE food_log ADD COLUMN unit_id INTEGER"); + if (!flCols.includes("unit_amount")) await execute("ALTER TABLE food_log ADD COLUMN unit_amount REAL"); // Seed serving_units if empty const suCnt = await select("SELECT COUNT(*) as cnt FROM serving_units"); @@ -928,19 +906,20 @@ async function runMigrations() { console.log("[db.js] Seeded 5 serving units"); } - // Seed product_units for eggs: link products with "яйц" AND "кур" to unit 1 (шт), 60g each + // Seed product_units for eggs (one-time) try { - const eggProducts = await select( - `SELECT id FROM products WHERE name_lower LIKE '%яйц%' AND name_lower LIKE '%кур%'` - ); - for (const ep of eggProducts) { - await execute( - `INSERT OR IGNORE INTO product_units (product_id, unit_id, grams_per_unit) - VALUES (?1, 1, 60)`, [ep.id] + const puCnt = await select("SELECT COUNT(*) as cnt FROM product_units"); + if (puCnt[0].cnt === 0) { + const eggProducts = await select( + `SELECT id FROM products WHERE name_lower LIKE '%яйц%' AND name_lower LIKE '%кур%'` ); - } - if (eggProducts.length > 0) { - console.log(`[db.js] Linked ${eggProducts.length} egg products to шт unit`); + for (const ep of eggProducts) { + await execute( + `INSERT OR IGNORE INTO product_units (product_id, unit_id, grams_per_unit) + VALUES (?1, 1, 60)`, [ep.id] + ); + } + if (eggProducts.length > 0) console.log(`[db.js] Linked ${eggProducts.length} egg products`); } } catch (e) { console.error("[db.js] egg unit seeding error:", e); } @@ -959,9 +938,8 @@ async function runMigrations() { )`); // Add bike_id column to gps_sessions - if (!(await hasColumn("gps_sessions", "bike_id"))) { - await execute("ALTER TABLE gps_sessions ADD COLUMN bike_id INTEGER"); - } + const gsCols = (await select("PRAGMA table_info(gps_sessions)")).map(r => r.name); + if (!gsCols.includes("bike_id")) await execute("ALTER TABLE gps_sessions ADD COLUMN bike_id INTEGER"); // Seed default bike if table is empty const bikeCnt = await select("SELECT COUNT(*) as cnt FROM bikes"); diff --git a/frontend/js/gps.js b/frontend/js/gps.js index 3a9dda3..16d891b 100644 --- a/frontend/js/gps.js +++ b/frontend/js/gps.js @@ -876,6 +876,23 @@ GPS.recalculateSession = async function(sessionId) { const points = await DB.getGpsPoints(sessionId); if (points.length < 2) return null; + // Fix point ordering: re-assign seq by timestamp + const sorted = points.slice().sort((a, b) => new Date(a.ts) - new Date(b.ts)); + let needsReorder = false; + for (let i = 0; i < sorted.length; i++) { + if (sorted[i].seq !== i + 1) { needsReorder = true; break; } + } + if (needsReorder) { + for (let i = 0; i < sorted.length; i++) { + await window.__TAURI__.core.invoke("plugin:sql|execute", { + db: "sqlite:baldav.db", + query: "UPDATE gps_points SET seq = ?1 WHERE session_id = ?2 AND ts = ?3", + values: [i + 1, sessionId, sorted[i].ts], + }); + } + console.log(`[GPS] Reordered ${sorted.length} points for session ${sessionId}`); + } + // Get user weight: latest weight log, or profile, or 80 const latestW = await DB.getLatestWeight(); const profile = await DB.getFullProfile();