added database crate, "settings" with migrations and queries, running migrations on web startup

This commit is contained in:
Philippe Loctaux 2023-02-27 16:07:18 +01:00
parent f60eb616d3
commit 9c2b43ec3c
16 changed files with 357 additions and 1 deletions

View file

@ -0,0 +1,45 @@
// error
#[derive(thiserror::Error)]
// the rest
#[derive(Debug)]
pub enum Error {
#[error("Unique constraint on primary key")]
UniqueConstraintPrimaryKey,
#[error("Unique constraint on key {0}")]
UniqueConstraint(String),
#[error("{0}")]
Database(#[from] sqlx::Error),
}
fn parse_unique_constraint(msg: &str) -> Option<String> {
// Format will be "table.column"
let mut constraint = msg.strip_prefix("UNIQUE constraint failed: ")?.split('.');
// Extract "column" (which is the second value)
let _table = constraint.next()?;
let column = constraint.next()?;
Some(column.to_string())
}
pub fn handle_error(e: sqlx::Error) -> Error {
match e.as_database_error() {
Some(database_error) => match database_error.code() {
// List of all codes: https://www.sqlite.org/rescode.html
Some(code) => match code.as_ref() {
"1555" => Error::UniqueConstraintPrimaryKey,
// Attempt to extract unique constraint column
"2067" => match parse_unique_constraint(database_error.message()) {
Some(column) => Error::UniqueConstraint(column),
None => Error::Database(e),
},
_ => Error::Database(e),
},
None => Error::Database(e),
},
None => Error::Database(e),
}
}

View file

@ -0,0 +1,11 @@
pub(crate) mod error;
mod tables;
/// Re-export sqlx
pub use sqlx;
/// Error
pub use self::error::Error;
/// Export tables
pub use self::tables::*;

View file

@ -0,0 +1,3 @@
mod settings;
pub use settings::Settings;

View file

@ -0,0 +1,57 @@
use crate::error::{handle_error, Error};
use sqlx::sqlite::SqliteQueryResult;
use sqlx::types::chrono::{DateTime, Utc};
use sqlx::{FromRow, SqliteExecutor};
#[derive(FromRow)]
pub struct Settings {
pub id: i64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub business_name: Option<String>,
pub business_logo: Option<Vec<u8>>,
}
impl Settings {
pub async fn init(conn: impl SqliteExecutor<'_>) -> Result<Option<()>, Error> {
let query: SqliteQueryResult = sqlx::query_file!("queries/settings/init.sql")
.execute(conn)
.await
.map_err(handle_error)?;
Ok((query.rows_affected() == 1).then_some(()))
}
pub async fn get(conn: impl SqliteExecutor<'_>) -> Result<Self, Error> {
sqlx::query_file_as!(Self, "queries/settings/get.sql")
.fetch_one(conn)
.await
.map_err(handle_error)
}
pub async fn set_business_name(
conn: impl SqliteExecutor<'_>,
value: Option<&str>,
) -> Result<Option<()>, Error> {
let query: SqliteQueryResult =
sqlx::query_file!("queries/settings/set_business_name.sql", value)
.execute(conn)
.await
.map_err(handle_error)?;
Ok((query.rows_affected() == 1).then_some(()))
}
pub async fn set_business_logo(
conn: impl SqliteExecutor<'_>,
value: Option<&[u8]>,
) -> Result<Option<()>, Error> {
let query: SqliteQueryResult =
sqlx::query_file!("queries/settings/set_business_logo.sql", value)
.execute(conn)
.await
.map_err(handle_error)?;
Ok((query.rows_affected() == 1).then_some(()))
}
}