Refactor the dao module into a directory full of models

This will make it a lot easier for myself and new folks to navigate the tree. No
sense shoving everything into a single module.
This commit is contained in:
R Tyler Croy 2023-02-05 16:25:30 -08:00
parent dbfd92614a
commit 1339c8c2e8
No known key found for this signature in database
GPG Key ID: E5C92681BEF6CEA2
10 changed files with 293 additions and 274 deletions

View File

@ -42,9 +42,9 @@ pub struct Project {
*/
#[derive(Clone, Debug, Serialize)]
pub struct Agent {
name: String,
pub name: String,
pub url: Url,
capabilities: Vec<janky::Capability>,
pub capabilities: Vec<janky::Capability>,
}
impl Agent {

View File

@ -1,268 +0,0 @@
/*
* The DAO module contains all the necessary structs for interacting with the database
*/
use chrono::{DateTime, NaiveDateTime, Utc};
use serde::Serialize;
use sqlx::sqlite::SqliteQueryResult;
use sqlx::SqlitePool;
use uuid::Uuid;
#[derive(Clone, Debug, Serialize)]
pub struct Project {
uuid: String,
name: String,
created_at: NaiveDateTime,
}
impl Default for Project {
fn default() -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
name: "Default Project".into(),
created_at: Utc::now().naive_utc(),
}
}
}
impl Project {
pub fn new(name: &str) -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
name: name.into(),
created_at: Utc::now().naive_utc(),
}
}
pub async fn by_name(name: &str, pool: &SqlitePool) -> Result<Project, sqlx::Error> {
sqlx::query_as!(Project, "SELECT * FROM projects WHERE name = ?", name)
.fetch_one(pool)
.await
}
pub async fn list(pool: &SqlitePool) -> Result<Vec<Project>, sqlx::Error> {
sqlx::query_as!(Project, "SELECT * FROM projects")
.fetch_all(pool)
.await
}
pub async fn create(
project: &Project,
tx: &SqlitePool,
) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!(
r#"INSERT INTO projects (uuid, name, created_at) VALUES (?, ?, ?)"#,
project.uuid,
project.name,
project.created_at,
)
.execute(tx)
.await
}
}
#[derive(Clone, Debug)]
struct Run {
run: RunRow,
project: Project,
scm_info: ScmInfo,
definition: RunDefinition,
}
/* The basic implementation for Run has all the database access operations
*/
impl Run {
/*
* Create the Run in the database given the appropriate struct
*/
async fn create(run: &Run, pool: &SqlitePool) -> Result<(), sqlx::Error> {
let mut tx = pool.begin().await?;
sqlx::query!(
r#"INSERT INTO scm_info (uuid, git_url, ref, created_at) VALUES (?, ?, ?, ?)"#,
run.scm_info.uuid,
run.scm_info.git_url,
run.scm_info.r#ref,
run.scm_info.created_at
)
.execute(&mut tx)
.await?;
sqlx::query!(
r#"INSERT INTO run_definition (uuid, definition, created_at) VALUES (?, ?, ?)"#,
run.definition.uuid,
run.definition.definition,
run.definition.created_at,
)
.execute(&mut tx)
.await?;
sqlx::query!(
"INSERT INTO runs (uuid, num, status, log_url, definition, scm_info, project) VALUES (?, ?, ?, ?, ?, ?, ?)",
run.run.uuid,
run.run.num,
run.run.status,
run.run.log_url,
run.definition.uuid,
run.scm_info.uuid,
run.project.uuid,
)
.execute(&mut tx)
.await?;
tx.commit().await
}
/*
* Allow finding a Run by the given Uuid
*/
async fn find_by(uuid: &str, pool: &SqlitePool) -> Result<Run, sqlx::Error> {
let row = sqlx::query_as!(RunRow, "SELECT * FROM runs WHERE uuid = ?", uuid)
.fetch_one(pool)
.await?;
let scm_info = sqlx::query_as!(
ScmInfo,
"SELECT * FROM scm_info WHERE uuid = ?",
row.scm_info
)
.fetch_one(pool)
.await?;
let project = sqlx::query_as!(
Project,
"SELECT * FROM projects WHERE uuid = ?",
row.project
)
.fetch_one(pool)
.await?;
let definition = sqlx::query_as!(
RunDefinition,
"SELECT * FROM run_definition WHERE uuid = ?",
row.definition
)
.fetch_one(pool)
.await?;
Ok(Run {
run: row,
scm_info,
project,
definition,
})
}
}
impl Default for Run {
fn default() -> Self {
Self {
run: RunRow::default(),
project: Project::default(),
scm_info: ScmInfo::default(),
definition: RunDefinition::default(),
}
}
}
/*
* The RunRow is the struct for the deserialization/serialization of the runs table
* unfortunately this is a little bit of misdirection due to the inability to make
* nested structs with sqlx work well
*/
#[derive(Clone, Debug)]
struct RunRow {
// Unique identifier for the Run
uuid: String,
// User-identifiable number for the Run, monotonically increasing
num: i64,
// Unix status return code from the run, zero is success
status: i64,
// Globally resolvable URL for fetching raw logs
log_url: String,
// Foreign key to projects
project: String,
// Foreign key to run_definition
definition: String,
// Foreign key to scm_info
scm_info: String,
created_at: NaiveDateTime,
}
impl Default for RunRow {
fn default() -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
num: 42,
status: 0,
log_url: "https://example.com/console.log".into(),
definition: Uuid::new_v4().hyphenated().to_string(),
project: Uuid::new_v4().hyphenated().to_string(),
scm_info: Uuid::new_v4().hyphenated().to_string(),
created_at: Utc::now().naive_utc(),
}
}
}
#[derive(Clone, Debug)]
struct ScmInfo {
uuid: String,
git_url: String,
r#ref: String,
created_at: NaiveDateTime,
}
impl Default for ScmInfo {
fn default() -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
git_url: "https://example.com/some/repo.git".into(),
r#ref: "main".into(),
created_at: Utc::now().naive_utc(),
}
}
}
#[derive(Clone, Debug)]
struct RunDefinition {
uuid: String,
definition: String,
created_at: NaiveDateTime,
}
impl Default for RunDefinition {
fn default() -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
definition: String::new(),
created_at: Utc::now().naive_utc(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sqlx::SqlitePool;
async fn setup_database() -> SqlitePool {
let pool = SqlitePool::connect(":memory:")
.await
.expect("Failed to setup_database()");
sqlx::migrate!()
.run(&pool)
.await
.expect("Failed to run migrations in a test");
pool
}
#[async_std::test]
async fn test_create_a_run() {
pretty_env_logger::try_init();
let pool = setup_database().await;
let project = Project::new("test");
Project::create(&project, &pool).await.unwrap();
let mut run = Run::default();
run.project = project;
let result = Run::create(&run, &pool).await.unwrap();
let fetched_run = Run::find_by(&run.run.uuid, &pool).await.unwrap();
assert_eq!(run.run.uuid, fetched_run.run.uuid);
}
}

View File

@ -16,10 +16,11 @@ use sqlx::SqlitePool;
use url::Url;
mod config;
mod dao;
mod models;
mod routes;
use crate::config::*;
use crate::models::Project;
#[derive(Clone, Debug)]
pub struct AppState<'a> {
@ -103,11 +104,11 @@ async fn main() -> Result<(), tide::Error> {
*/
for name in config.projects.keys() {
match dao::Project::by_name(&name, &pool).await {
match Project::by_name(&name, &pool).await {
Ok(_) => {}
Err(sqlx::Error::RowNotFound) => {
debug!("Project not found in database, creating: {}", name);
dao::Project::create(&dao::Project::new(&name), &pool).await?;
Project::create(&Project::new(&name), &pool).await?;
}
Err(e) => {
return Err(e.into());

11
src/server/models/mod.rs Normal file
View File

@ -0,0 +1,11 @@
mod project;
mod run;
mod rundefinition;
mod runrow;
mod scminfo;
pub use self::project::Project;
pub use self::run::Run;
pub use self::rundefinition::RunDefinition;
pub use self::runrow::RunRow;
pub use self::scminfo::ScmInfo;

View File

@ -0,0 +1,58 @@
use chrono::{NaiveDateTime, Utc};
use serde::Serialize;
use sqlx::sqlite::SqliteQueryResult;
use sqlx::SqlitePool;
use uuid::Uuid;
#[derive(Clone, Debug, Serialize)]
pub struct Project {
pub uuid: String,
pub name: String,
pub created_at: NaiveDateTime,
}
impl Default for Project {
fn default() -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
name: "Default Project".into(),
created_at: Utc::now().naive_utc(),
}
}
}
impl Project {
pub fn new(name: &str) -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
name: name.into(),
created_at: Utc::now().naive_utc(),
}
}
pub async fn by_name(name: &str, pool: &SqlitePool) -> Result<Project, sqlx::Error> {
sqlx::query_as!(Project, "SELECT * FROM projects WHERE name = ?", name)
.fetch_one(pool)
.await
}
pub async fn list(pool: &SqlitePool) -> Result<Vec<Project>, sqlx::Error> {
sqlx::query_as!(Project, "SELECT * FROM projects")
.fetch_all(pool)
.await
}
pub async fn create(
project: &Project,
tx: &SqlitePool,
) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!(
r#"INSERT INTO projects (uuid, name, created_at) VALUES (?, ?, ?)"#,
project.uuid,
project.name,
project.created_at,
)
.execute(tx)
.await
}
}

134
src/server/models/run.rs Normal file
View File

@ -0,0 +1,134 @@
use sqlx::SqlitePool;
use crate::models::*;
#[derive(Clone, Debug)]
pub struct Run {
run: RunRow,
project: Project,
scm_info: ScmInfo,
definition: RunDefinition,
}
/* The basic implementation for Run has all the database access operations
*/
impl Run {
/*
* Create the Run in the database given the appropriate struct
*/
async fn create(run: &Run, pool: &SqlitePool) -> Result<(), sqlx::Error> {
let mut tx = pool.begin().await?;
sqlx::query!(
r#"INSERT INTO scm_info (uuid, git_url, ref, created_at) VALUES (?, ?, ?, ?)"#,
run.scm_info.uuid,
run.scm_info.git_url,
run.scm_info.r#ref,
run.scm_info.created_at
)
.execute(&mut tx)
.await?;
sqlx::query!(
r#"INSERT INTO run_definition (uuid, definition, created_at) VALUES (?, ?, ?)"#,
run.definition.uuid,
run.definition.definition,
run.definition.created_at,
)
.execute(&mut tx)
.await?;
sqlx::query!(
"INSERT INTO runs (uuid, num, status, log_url, definition, scm_info, project) VALUES (?, ?, ?, ?, ?, ?, ?)",
run.run.uuid,
run.run.num,
run.run.status,
run.run.log_url,
run.definition.uuid,
run.scm_info.uuid,
run.project.uuid,
)
.execute(&mut tx)
.await?;
tx.commit().await
}
/*
* Allow finding a Run by the given Uuid
*/
async fn find_by(uuid: &str, pool: &SqlitePool) -> Result<Run, sqlx::Error> {
let row = sqlx::query_as!(RunRow, "SELECT * FROM runs WHERE uuid = ?", uuid)
.fetch_one(pool)
.await?;
let scm_info = sqlx::query_as!(
ScmInfo,
"SELECT * FROM scm_info WHERE uuid = ?",
row.scm_info
)
.fetch_one(pool)
.await?;
let project = sqlx::query_as!(
Project,
"SELECT * FROM projects WHERE uuid = ?",
row.project
)
.fetch_one(pool)
.await?;
let definition = sqlx::query_as!(
RunDefinition,
"SELECT * FROM run_definition WHERE uuid = ?",
row.definition
)
.fetch_one(pool)
.await?;
Ok(Run {
run: row,
scm_info,
project,
definition,
})
}
}
impl Default for Run {
fn default() -> Self {
Self {
run: RunRow::default(),
project: Project::default(),
scm_info: ScmInfo::default(),
definition: RunDefinition::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use sqlx::SqlitePool;
async fn setup_database() -> SqlitePool {
let pool = SqlitePool::connect(":memory:")
.await
.expect("Failed to setup_database()");
sqlx::migrate!()
.run(&pool)
.await
.expect("Failed to run migrations in a test");
pool
}
#[async_std::test]
async fn test_create_a_run() {
let _ = pretty_env_logger::try_init();
let pool = setup_database().await;
let project = crate::models::Project::new("test");
Project::create(&project, &pool).await.unwrap();
let mut run = Run::default();
run.project = project;
let _result = Run::create(&run, &pool).await.unwrap();
let fetched_run = Run::find_by(&run.run.uuid, &pool).await.unwrap();
assert_eq!(run.run.uuid, fetched_run.run.uuid);
}
}

View File

@ -0,0 +1,19 @@
use chrono::{NaiveDateTime, Utc};
use uuid::Uuid;
#[derive(Clone, Debug)]
pub struct RunDefinition {
pub uuid: String,
pub definition: String,
pub created_at: NaiveDateTime,
}
impl Default for RunDefinition {
fn default() -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
definition: String::new(),
created_at: Utc::now().naive_utc(),
}
}
}

View File

@ -0,0 +1,41 @@
use chrono::{NaiveDateTime, Utc};
use uuid::Uuid;
/*
* The RunRow is the struct for the deserialization/serialization of the runs table
* unfortunately this is a little bit of misdirection due to the inability to make
* nested structs with sqlx work well
*/
#[derive(Clone, Debug)]
pub struct RunRow {
// Unique identifier for the Run
pub uuid: String,
// User-identifiable number for the Run, monotonically increasing
pub num: i64,
// Unix status return code from the run, zero is success
pub status: i64,
// Globally resolvable URL for fetching raw logs
pub log_url: String,
// Foreign key to projects
pub project: String,
// Foreign key to run_definition
pub definition: String,
// Foreign key to scm_info
pub scm_info: String,
pub created_at: NaiveDateTime,
}
impl Default for RunRow {
fn default() -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
num: 42,
status: 0,
log_url: "https://example.com/console.log".into(),
definition: Uuid::new_v4().hyphenated().to_string(),
project: Uuid::new_v4().hyphenated().to_string(),
scm_info: Uuid::new_v4().hyphenated().to_string(),
created_at: Utc::now().naive_utc(),
}
}
}

View File

@ -0,0 +1,21 @@
use chrono::{NaiveDateTime, Utc};
use uuid::Uuid;
#[derive(Clone, Debug)]
pub struct ScmInfo {
pub uuid: String,
pub git_url: String,
pub r#ref: String,
pub created_at: NaiveDateTime,
}
impl Default for ScmInfo {
fn default() -> Self {
Self {
uuid: Uuid::new_v4().hyphenated().to_string(),
git_url: "https://example.com/some/repo.git".into(),
r#ref: "main".into(),
created_at: Utc::now().naive_utc(),
}
}
}

View File

@ -7,6 +7,8 @@
use crate::AppState;
use tide::{Body, Request};
use crate::models::Project;
/**
* GET /
*/
@ -21,7 +23,7 @@ pub async fn index(req: Request<AppState<'_>>) -> Result<Body, tide::Error> {
"page": "home",
"agents" : agents,
"config" : req.state().config,
"projects" : crate::dao::Project::list(&req.state().db).await?,
"projects" : Project::list(&req.state().db).await?,
});
let mut body = req.state().render("index", &params).await?;