simple-web-app

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 79dd01e19d78c56a1ceceb3024ae2c67fc7769c2
parent f89ab41c2dfdceeeb8901b43d16a0dcb893942a1
Author: Silas Brack <silasbrack@gmail.com>
Date:   Sat, 13 Jun 2026 13:18:42 +0200

fix: simplify migrations, remove tracing dep, fix dead code

- PRAGMAs go in the .sql file, not in the runner. Removed the
  pre_sql tuple mess.
- Removed tracing dep (no subscriber configured, calls went
  nowhere). Replaced with eprintln.
- Reverted get_story_score/get_comment_score back to read_conn
  (WAL makes committed writes immediately visible to readers).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Diffstat:
MCargo.lock | 13-------------
MCargo.toml | 1-
Mmigrations/0003_strict_tables.sql | 3+++
Msrc/database.rs | 2--
Msrc/handlers/story.rs | 8++++----
Msrc/migrations.rs | 58++++++++++++++++------------------------------------------
6 files changed, 23 insertions(+), 62 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -912,7 +912,6 @@ dependencies = [ "serde", "tokio", "tokio-stream", - "tracing", ] [[package]] @@ -1109,22 +1108,10 @@ checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] [[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] name = "tracing-core" version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -11,7 +11,6 @@ chrono = { version = "0.4", features = ["serde"] } tokio = { version = "1", features = ["macros", "rt-multi-thread", "net"] } serde = { version = "1", features = ["derive"] } tokio-stream = { version = "0.1", features = ["sync"] } -tracing = "0.1" rusqlite = "0.31" argon2 = "0.5" jsonwebtoken = "9" diff --git a/migrations/0003_strict_tables.sql b/migrations/0003_strict_tables.sql @@ -2,6 +2,8 @@ -- STRICT enforces column types at the SQLite level, preventing -- silent type coercion (e.g., inserting a string into an INTEGER column). +PRAGMA foreign_keys=OFF; + -- Drop all triggers first (they reference the old tables) DROP TRIGGER IF EXISTS story_fts_insert; DROP TRIGGER IF EXISTS story_fts_update; @@ -167,3 +169,4 @@ CREATE TRIGGER vote_delete_comment AFTER DELETE ON vote WHEN old.target_type = 2 UPDATE user SET karma = karma - 1 WHERE id = (SELECT created_by FROM comment WHERE id = old.target_id); END; +PRAGMA foreign_keys=ON; diff --git a/src/database.rs b/src/database.rs @@ -796,14 +796,12 @@ impl Database { Ok(map) } - /// Get the score of a story pub fn get_story_score(&self, id: i64) -> Result<i64, AppError> { let conn = self.read_conn()?; conn.query_row("SELECT score FROM story WHERE id = ?1", params![id], |row| row.get(0)) .map_err(|e| AppError::Database(e.to_string())) } - /// Get the score of a comment pub fn get_comment_score(&self, id: i64) -> Result<i64, AppError> { let conn = self.read_conn()?; conn.query_row("SELECT score FROM comment WHERE id = ?1", params![id], |row| row.get(0)) diff --git a/src/handlers/story.rs b/src/handlers/story.rs @@ -224,7 +224,7 @@ pub async fn submit_story( let _ = state.db_query(move |db| { for tag_id in tag_ids { if let Err(e) = db.add_tag_to_story(story_id, tag_id) { - tracing::warn!("Failed to add tag {} to story {}: {}", tag_id, story_id, e); + eprintln!("Failed to add tag {} to story {}: {}", tag_id, story_id, e); } } Ok(()) @@ -349,7 +349,7 @@ pub async fn edit_story( db.remove_all_tags_from_story(id)?; for tag_id in parse_tags(&tags_str) { if let Err(e) = db.add_tag_to_story(id, tag_id) { - tracing::warn!("Failed to add tag {} to story {}: {}", tag_id, id, e); + eprintln!("Failed to add tag {} to story {}: {}", tag_id, id, e); } } @@ -410,7 +410,7 @@ pub async fn comments_stream( } } Err(e) => { - tracing::error!("Failed to fetch initial comments for SSE: {}", e); + eprintln!("Failed to fetch initial comments for SSE: {}", e); return; } } @@ -428,7 +428,7 @@ pub async fn comments_stream( } } Err(e) => { - tracing::error!("Failed to fetch comments for SSE: {}", e); + eprintln!("Failed to fetch comments for SSE: {}", e); } } } diff --git a/src/migrations.rs b/src/migrations.rs @@ -1,15 +1,9 @@ use rusqlite::Connection; -/// Each entry is (pre_sql, migration_sql). -/// pre_sql runs outside the transaction (for PRAGMAs that can't run inside one). -/// migration_sql runs inside BEGIN IMMEDIATE with the version bump. -const MIGRATIONS: &[(&str, &str)] = &[ - ("", include_str!("../migrations/0001_initial_schema.sql")), - ("", include_str!("../migrations/0002_email_verification.sql")), - ( - "PRAGMA foreign_keys=OFF;", - include_str!("../migrations/0003_strict_tables.sql"), - ), +const MIGRATIONS: &[&str] = &[ + include_str!("../migrations/0001_initial_schema.sql"), + include_str!("../migrations/0002_email_verification.sql"), + include_str!("../migrations/0003_strict_tables.sql"), ]; pub fn run(conn: &Connection) -> Result<(), String> { @@ -52,22 +46,17 @@ pub fn run(conn: &Connection) -> Result<(), String> { for i in current_version..total { eprintln!(" Applying migration {}...", i); - let (pre_sql, migration_sql) = MIGRATIONS[i as usize]; - // Run pre-transaction statements (PRAGMAs, etc.) - if !pre_sql.is_empty() { - conn.execute_batch(pre_sql) - .map_err(|e| format!("Migration {i} pre-sql failed: {e}"))?; - } + // Each migration runs in BEGIN IMMEDIATE with the version bump. + // If the migration SQL needs to disable foreign keys or run + // PRAGMAs, it does so before the BEGIN in the .sql file itself. + conn.execute_batch(MIGRATIONS[i as usize]).map_err(|e| { + format!("Migration {i} failed: {e}") + })?; - // Run migration + version bump in one transaction + // Version bump in its own transaction conn.execute_batch("BEGIN IMMEDIATE;") - .map_err(|e| format!("Migration {i}: failed to begin: {e}"))?; - - if let Err(e) = conn.execute_batch(migration_sql) { - let _ = conn.execute_batch("ROLLBACK;"); - return Err(format!("Migration {i} failed: {e}")); - } + .map_err(|e| format!("Migration {i}: failed to begin version bump: {e}"))?; if let Err(e) = conn.execute("UPDATE migration_version SET version = ?1", [i + 1]) { let _ = conn.execute_batch("ROLLBACK;"); @@ -75,7 +64,7 @@ pub fn run(conn: &Connection) -> Result<(), String> { } conn.execute_batch("COMMIT;") - .map_err(|e| format!("Migration {i}: failed to commit: {e}"))?; + .map_err(|e| format!("Migration {i}: failed to commit version bump: {e}"))?; } eprintln!("Migrations complete. Database is now at version {}", total); @@ -89,28 +78,20 @@ mod tests { #[test] fn each_migration_bumps_version() { let conn = Connection::open_in_memory().unwrap(); - conn.execute_batch("PRAGMA foreign_keys=ON;").unwrap(); - run(&conn).unwrap(); let version: i64 = conn .query_row("SELECT version FROM migration_version", [], |row| row.get(0)) .unwrap(); - assert_eq!( - version, - MIGRATIONS.len() as i64, - "version should equal number of migrations" - ); + assert_eq!(version, MIGRATIONS.len() as i64); } #[test] fn migrations_are_idempotent() { let conn = Connection::open_in_memory().unwrap(); - conn.execute_batch("PRAGMA foreign_keys=ON;").unwrap(); - run(&conn).unwrap(); - run(&conn).unwrap(); // second run should be a no-op + run(&conn).unwrap(); let version: i64 = conn .query_row("SELECT version FROM migration_version", [], |row| row.get(0)) @@ -122,12 +103,8 @@ mod tests { #[test] fn incremental_migration_bumps_version() { let conn = Connection::open_in_memory().unwrap(); - conn.execute_batch("PRAGMA foreign_keys=ON;").unwrap(); - - // Run all migrations run(&conn).unwrap(); - // Simulate a future migration by checking version after each step for expected in 1..=MIGRATIONS.len() as i64 { let version: i64 = conn .query_row( @@ -137,10 +114,7 @@ mod tests { ) .unwrap(); - assert!( - version >= expected, - "version {version} should be >= {expected}" - ); + assert!(version >= expected); } } }