Expose utilities for setting up the database (#4657)

* Expose utilities for setting up the database

* Expose migrator directly

* Make some test utils publicly accessible

* expose migrator

* more test fixture utils

* more test fixture utils

* more test fixture utils

* fix

* fix lint
This commit is contained in:
aecsocket
2025-10-30 03:10:25 -07:00
committed by GitHub
parent 632b27dc21
commit bcc36362be
7 changed files with 118 additions and 32 deletions

View File

@@ -5,5 +5,6 @@ pub use models::DBImage;
pub use models::DBProject;
pub use models::DBVersion;
pub use postgres_database::{
ReadOnlyPgPool, check_for_migrations, connect_all, register_and_set_metrics,
MIGRATOR, ReadOnlyPgPool, check_for_migrations, connect_all,
register_and_set_metrics,
};

View File

@@ -1,5 +1,6 @@
use eyre::Context;
use prometheus::{IntGauge, Registry};
use sqlx::migrate::MigrateDatabase;
use sqlx::migrate::{MigrateDatabase, Migrator};
use sqlx::postgres::{PgPool, PgPoolOptions};
use sqlx::{Connection, PgConnection, Postgres};
use std::ops::{Deref, DerefMut};
@@ -75,25 +76,36 @@ pub async fn connect_all() -> Result<(PgPool, ReadOnlyPgPool), sqlx::Error> {
Ok((pool, ro))
}
}
pub async fn check_for_migrations() -> Result<(), sqlx::Error> {
let uri = dotenvy::var("DATABASE_URL").expect("`DATABASE_URL` not in .env");
pub async fn check_for_migrations() -> eyre::Result<()> {
let uri =
dotenvy::var("DATABASE_URL").wrap_err("`DATABASE_URL` not in .env")?;
let uri = uri.as_str();
if !Postgres::database_exists(uri).await? {
if !Postgres::database_exists(uri)
.await
.wrap_err("failed to check if database exists")?
{
info!("Creating database...");
Postgres::create_database(uri).await?;
Postgres::create_database(uri)
.await
.wrap_err("failed to create database")?;
}
info!("Applying migrations...");
let mut conn: PgConnection = PgConnection::connect(uri).await?;
sqlx::migrate!()
let mut conn: PgConnection = PgConnection::connect(uri)
.await
.wrap_err("failed to connect to database")?;
MIGRATOR
.run(&mut conn)
.await
.expect("Error while running database migrations!");
.wrap_err("failed to run database migrations")?;
Ok(())
}
pub static MIGRATOR: Migrator = sqlx::migrate!();
pub async fn register_and_set_metrics(
pool: &PgPool,
registry: &Registry,

View File

@@ -37,6 +37,7 @@ pub mod routes;
pub mod scheduler;
pub mod search;
pub mod sync;
pub mod test;
pub mod util;
pub mod validate;

View File

@@ -0,0 +1,81 @@
use eyre::{Context, Result};
use sqlx::{Executor, PgPool};
/// Static personal access token for use in [`AppendPat`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pat(pub &'static str);
/// Dummy [`Pat`]s.
#[allow(missing_docs, reason = "self-explanatory")]
pub mod pat {
use super::Pat;
pub const ADMIN: Pat = Pat("mrp_patadmin");
pub const MODERATOR: Pat = Pat("mrp_patmoderator");
pub const USER: Pat = Pat("mrp_patuser");
pub const FRIEND: Pat = Pat("mrp_patfriend");
pub const ENEMY: Pat = Pat("mrp_patenemy");
}
/// See [`AppendPat::append_pat`].
pub trait AppendPat {
/// Appends a [`Pat`] authorization token to an
/// [`actix_web::test::TestRequest`].
#[must_use]
fn append_pat(self, pat: Pat) -> Self;
}
impl AppendPat for actix_web::test::TestRequest {
fn append_pat(self, pat: Pat) -> Self {
self.append_header(("Authorization", pat.0))
}
}
/// Dummy [`DBUserId`]s.
///
/// [`DBUserId`]: crate::database::models::DBUserId
pub mod user_id {
use crate::database::models::DBUserId;
pub const ADMIN: DBUserId = DBUserId(1);
pub const MODERATOR: DBUserId = DBUserId(2);
pub const USER: DBUserId = DBUserId(3);
pub const FRIEND: DBUserId = DBUserId(4);
pub const ENEMY: DBUserId = DBUserId(5);
}
/// Initialize a database with dummy fixture data.
///
/// # Errors
///
/// Errors if the fixture could not be applied.
pub async fn add_dummy_data(db: &PgPool) -> Result<()> {
db.execute(
include_str!("../../fixtures/dummy_data.sql")
//
.replace("{{user_id::ADMIN}}", &user_id::ADMIN.0.to_string())
.replace(
"{{user_id::MODERATOR}}",
&user_id::MODERATOR.0.to_string(),
)
.replace("{{user_id::USER}}", &user_id::USER.0.to_string())
.replace("{{user_id::FRIEND}}", &user_id::FRIEND.0.to_string())
.replace("{{user_id::ENEMY}}", &user_id::ENEMY.0.to_string())
//
.replace("{{pat::ADMIN}}", pat::ADMIN.0)
.replace("{{pat::MODERATOR}}", pat::MODERATOR.0)
.replace("{{pat::USER}}", pat::USER.0)
.replace("{{pat::FRIEND}}", pat::FRIEND.0)
.replace("{{pat::ENEMY}}", pat::ENEMY.0)
//
.replace(
"{{all_scopes}}",
&crate::models::pats::Scopes::all().bits().to_string(),
)
.as_str(),
)
.await
.wrap_err("failed to add dummy data")?;
Ok(())
}

View File

@@ -0,0 +1 @@
pub mod db;