diff --git a/src/server/config.rs b/src/server/config.rs index 295fbc7..ee0ef9f 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -42,9 +42,9 @@ pub struct Project { */ #[derive(Clone, Debug, Serialize)] pub struct Agent { - name: String, + pub name: String, pub url: Url, - capabilities: Vec, + pub capabilities: Vec, } impl Agent { diff --git a/src/server/dao.rs b/src/server/dao.rs deleted file mode 100644 index 7029b25..0000000 --- a/src/server/dao.rs +++ /dev/null @@ -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 { - sqlx::query_as!(Project, "SELECT * FROM projects WHERE name = ?", name) - .fetch_one(pool) - .await - } - - pub async fn list(pool: &SqlitePool) -> Result, sqlx::Error> { - sqlx::query_as!(Project, "SELECT * FROM projects") - .fetch_all(pool) - .await - } - - pub async fn create( - project: &Project, - tx: &SqlitePool, - ) -> Result { - 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 { - 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); - } -} diff --git a/src/server/main.rs b/src/server/main.rs index 8b51aac..c5b57d8 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -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()); diff --git a/src/server/models/mod.rs b/src/server/models/mod.rs new file mode 100644 index 0000000..2bfd5ac --- /dev/null +++ b/src/server/models/mod.rs @@ -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; diff --git a/src/server/models/project.rs b/src/server/models/project.rs new file mode 100644 index 0000000..6b95c17 --- /dev/null +++ b/src/server/models/project.rs @@ -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 { + sqlx::query_as!(Project, "SELECT * FROM projects WHERE name = ?", name) + .fetch_one(pool) + .await + } + + pub async fn list(pool: &SqlitePool) -> Result, sqlx::Error> { + sqlx::query_as!(Project, "SELECT * FROM projects") + .fetch_all(pool) + .await + } + + pub async fn create( + project: &Project, + tx: &SqlitePool, + ) -> Result { + sqlx::query!( + r#"INSERT INTO projects (uuid, name, created_at) VALUES (?, ?, ?)"#, + project.uuid, + project.name, + project.created_at, + ) + .execute(tx) + .await + } +} diff --git a/src/server/models/run.rs b/src/server/models/run.rs new file mode 100644 index 0000000..ba3d1c8 --- /dev/null +++ b/src/server/models/run.rs @@ -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 { + 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); + } +} diff --git a/src/server/models/rundefinition.rs b/src/server/models/rundefinition.rs new file mode 100644 index 0000000..e1196d8 --- /dev/null +++ b/src/server/models/rundefinition.rs @@ -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(), + } + } +} diff --git a/src/server/models/runrow.rs b/src/server/models/runrow.rs new file mode 100644 index 0000000..dc7c4e8 --- /dev/null +++ b/src/server/models/runrow.rs @@ -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(), + } + } +} diff --git a/src/server/models/scminfo.rs b/src/server/models/scminfo.rs new file mode 100644 index 0000000..af6b31d --- /dev/null +++ b/src/server/models/scminfo.rs @@ -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(), + } + } +} diff --git a/src/server/routes.rs b/src/server/routes.rs index 9f3c721..a2c43bc 100644 --- a/src/server/routes.rs +++ b/src/server/routes.rs @@ -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>) -> Result { "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", ¶ms).await?;