feedsignal/crates/db/src/lib.rs
Austin Schaefer e70909af47
All checks were successful
CI / check (pull_request) Successful in 2m2s
CI / test (pull_request) Successful in 3m15s
CI / audit (pull_request) Successful in 10s
Add and apply a models.rs convention for data-model structs
Document in the DoD that data-model structs (domain models, query row
types, DTOs/views) belong in a crate's models.rs rather than the file
that produces/consumes them, once used outside that function -
matching the existing crates/core/src/models.rs pattern. Component-
local structs (Props, Styles, context) are exempt.

Apply it to db: move RankedArticleRow out of articles.rs into a new
crates/db/src/models.rs.
2026-09-03 21:23:34 +02:00

53 lines
2 KiB
Rust

mod affinities;
mod articles;
mod feeds;
mod models;
mod reading_events;
pub use models::RankedArticleRow;
pub(crate) mod schema;
use anyhow::Result;
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use diesel_async::pooled_connection::bb8::Pool;
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
use diesel_async::sync_connection_wrapper::SyncConnectionWrapper;
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");
/// SQLite has no native async driver, so `diesel-async` wraps a blocking
/// `SqliteConnection` and runs it on a blocking thread under the hood.
type AsyncSqliteConnection = SyncConnectionWrapper<SqliteConnection>;
#[derive(Clone)]
pub struct Db {
pool: Pool<AsyncSqliteConnection>,
}
impl Db {
/// `database_url` is a plain SQLite file path (e.g. "feedsignal.db"),
/// created if it doesn't exist. Everything lives in one file, no
/// separate database server to run.
pub async fn connect(database_url: &str) -> Result<Self> {
// `MigrationHarness` only works on a synchronous `Connection`, and
// this only runs once at boot, so open a throwaway sync connection
// just for it rather than pulling migrations through the async pool.
// Each pending migration runs in its own transaction (SQLite
// supports transactional DDL), and `down.sql` gives each one a
// matching revert.
let url = database_url.to_string();
tokio::task::spawn_blocking(move || -> Result<()> {
let mut conn = SqliteConnection::establish(&url)?;
conn.run_pending_migrations(MIGRATIONS)
.map_err(|e| anyhow::anyhow!(e))?;
Ok(())
})
.await??;
let manager = AsyncDieselConnectionManager::<AsyncSqliteConnection>::new(database_url);
let pool = Pool::builder().max_size(5).build(manager).await?;
Ok(Self { pool })
}
}