diff --git a/Cargo.lock b/Cargo.lock index 29dfea7..7ee6b24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,23 +514,26 @@ dependencies = [ [[package]] name = "diesel" -version = "1.4.8" +version = "2.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" +checksum = "876a12f2d98c35d1dfaf74083664be5bb71606b72f4756f3792cdb1ddb192b46" dependencies = [ "bitflags", "byteorder", "diesel_derives", + "itoa", "pq-sys", "r2d2", + "uuid", ] [[package]] name = "diesel_derives" -version = "1.4.1" +version = "2.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3" +checksum = "2497d9cefebedc40492310f3c94493e1d0bd5b11b5e1bfc0a7f4b106012203ea" dependencies = [ + "proc-macro-error", "proc-macro2", "quote", "syn", @@ -538,10 +541,11 @@ dependencies = [ [[package]] name = "diesel_migrations" -version = "1.4.0" +version = "2.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c" +checksum = "af3e284b00439e362c3f55849a103f38b4c5a9918ae3cf34fca2268b6eb52fef" dependencies = [ + "diesel", "migrations_internals", "migrations_macros", ] @@ -632,6 +636,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.21" @@ -639,6 +658,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -647,6 +667,17 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.21" @@ -679,6 +710,12 @@ dependencies = [ "syn", ] +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + [[package]] name = "futures-task" version = "0.3.21" @@ -691,9 +728,13 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite 0.2.9", "pin-utils", "slab", @@ -863,12 +904,16 @@ name = "inventory-system" version = "0.1.0" dependencies = [ "async-std", + "async-trait", "diesel", "diesel_migrations", + "futures", "log", "rust-embed", + "serde", "thiserror", "tide", + "uuid", ] [[package]] @@ -942,23 +987,23 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "migrations_internals" -version = "1.4.1" +version = "2.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860" +checksum = "e03913f1a1c044aa53b37c8f34011599bbf98914092a0444081555e9ca35022c" dependencies = [ - "diesel", + "serde", + "toml", ] [[package]] name = "migrations_macros" -version = "1.4.2" +version = "2.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c" +checksum = "9ccfa634200e9de9fe6497f568d54951e933e9e0ddf61058e52e24bda896de48" dependencies = [ "migrations_internals", "proc-macro2", "quote", - "syn", ] [[package]] @@ -1114,6 +1159,30 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -1696,6 +1765,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + [[package]] name = "typenum" version = "1.15.0" @@ -1746,6 +1824,15 @@ dependencies = [ "serde", ] +[[package]] +name = "uuid" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +dependencies = [ + "serde", +] + [[package]] name = "value-bag" version = "1.0.0-alpha.9" diff --git a/Cargo.toml b/Cargo.toml index f56b93e..3c05e9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,10 +9,15 @@ edition = "2021" log = "0.4" thiserror = "1" +futures = "0.3.21" async-std = "1" +async-trait = "0.1.56" +serde = { version = "1.0", features = ["derive"] } tide = "0.17.0-beta.1" -diesel = { version = "1.4", features = ["postgres", "r2d2"] } -diesel_migrations = "1.4" +diesel = { version = "2.0.0-rc.1", features = ["postgres", "r2d2", "uuid"] } +diesel_migrations = "2.0.0-rc.1" + +uuid = { version = "1.1.2", features = ["serde"] } rust-embed = { version = "6.2.0", features = ["interpolate-folder-path"]} diff --git a/migrations/2022-07-23-163034_create_containers/down.sql b/migrations/2022-07-23-163034_create_containers/down.sql new file mode 100644 index 0000000..182f6dc --- /dev/null +++ b/migrations/2022-07-23-163034_create_containers/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +drop table if exists containers; diff --git a/migrations/2022-07-23-163034_create_containers/up.sql b/migrations/2022-07-23-163034_create_containers/up.sql new file mode 100644 index 0000000..02d4b9f --- /dev/null +++ b/migrations/2022-07-23-163034_create_containers/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here + +create table containers +( + id uuid default gen_random_uuid() not null + primary key, + parent uuid + constraint foreign_key_name + references containers, + name varchar not null +); diff --git a/src/api/containers.rs b/src/api/containers.rs new file mode 100644 index 0000000..0f85031 --- /dev/null +++ b/src/api/containers.rs @@ -0,0 +1,30 @@ +use super::{ApiEndpoint, ApiResult, ApiSuccess, State}; +use crate::db; + +use std::sync::Arc; + +use diesel::Queryable; +use serde::Serialize; +use uuid::Uuid; + +pub fn setup_routes(mut route: tide::Route>) { + route.at("/").get(ApiEndpoint(index)); +} + +#[derive(Queryable, Serialize)] +pub struct Container { + pub id: Uuid, + pub parent: Option, + pub name: String, +} + +async fn index(req: tide::Request>) -> ApiResult> { + use db::schema::containers::dsl::*; + use diesel::{QueryDsl, RunQueryDsl}; + + let mut connection = req.state().db.get().unwrap(); + + let items: Vec = containers.limit(5).load::(&mut connection)?; + + Ok(ApiSuccess::Ok(items)) +} diff --git a/src/api/error.rs b/src/api/error.rs new file mode 100644 index 0000000..d02c025 --- /dev/null +++ b/src/api/error.rs @@ -0,0 +1,118 @@ +use log::error; + +use async_trait::async_trait; +use diesel::result::Error as DieselError; +use futures::future::Future; +use serde::Serialize; +use thiserror::Error; +use tide::{Body, Response, StatusCode}; + +pub type ApiResult = Result, ApiError>; + +#[derive(Serialize, Error, Debug)] +pub enum ApiSuccess { + Ok(T), +} + +#[derive(Error, Debug)] +pub enum ApiError { + #[error("not found")] + NotFound, + + #[error("database error: {0}")] + Database(DieselError), +} + +impl From for ApiError { + fn from(err: DieselError) -> Self { + match err { + DieselError::NotFound => Self::NotFound, + e => Self::Database(e), + } + } +} + +pub(super) struct ApiEndpoint(pub(super) E); + +#[async_trait] +impl tide::Endpoint for ApiEndpoint +where + E: Fn(tide::Request) -> F + Send + Sync + 'static, + S: Clone + Send + Sync + 'static, + F: Future> + Send + Sync + 'static, + T: Serialize, +{ + async fn call(&self, req: tide::Request) -> tide::Result { + Ok(ApiResultProxy::::from(self.0(req).await).into()) + } +} + +struct ApiResultProxy(pub Result, ApiError>); + +impl From> for ApiResultProxy { + fn from(res: ApiResult) -> Self { + Self(res) + } +} + +#[derive(Serialize)] +enum ApiResponse<'a, T> { + #[serde(rename = "content")] + Content(&'a T), + + #[serde(rename = "err")] + Err(&'a str), +} + +impl From> for Response { + fn from(res: ApiResultProxy) -> Self { + match res.0 { + Ok(resp) => { + let (status_code, body) = resp.response(); + + Response::builder(status_code) + .body( + Body::from_json(&ApiResponse::Content(body)) + .expect("json serialisation should succeed"), + ) + .build() + } + + Err(err) => { + let (status_code, body) = err.response(); + + Response::builder(status_code) + .body( + Body::from_json(&ApiResponse::<()>::Err(&body)) + .expect("json serialisation should succeed"), + ) + .build() + } + } + } +} + +impl ApiSuccess { + fn response(&self) -> (StatusCode, &T) { + match self { + Self::Ok(b) => (StatusCode::Ok, b), + } + } +} + +impl ApiError { + fn response(&self) -> (StatusCode, String) { + match self { + Self::NotFound => (StatusCode::NotFound, "requested object not found".into()), + + e => { + error!("internal server error handling request: {}", e); + + ( + StatusCode::InternalServerError, + "internal server error".into(), + ) + } + } + } +} diff --git a/src/api.rs b/src/api/mod.rs similarity index 54% rename from src/api.rs rename to src/api/mod.rs index 747fb56..2c18ffa 100644 --- a/src/api.rs +++ b/src/api/mod.rs @@ -1,17 +1,23 @@ +mod containers; +mod error; + +use error::{ApiEndpoint, ApiResult, ApiSuccess}; + use std::sync::Arc; use diesel::pg::PgConnection; use diesel::r2d2::{ConnectionManager, Pool}; pub struct State { - _db: Pool>, + db: Pool>, } pub fn serve(db: Pool>) -> tide::Server> { - let state = Arc::new(State { _db: db }); + let state = Arc::new(State { db }); let mut app = tide::with_state(state); - app.at("/hello").get(|_| async { Ok("Hello, Jake!") }); + + containers::setup_routes(app.at("/containers/")); app } diff --git a/src/db/error.rs b/src/db/error.rs index 4ed0b54..1dfad03 100644 --- a/src/db/error.rs +++ b/src/db/error.rs @@ -6,5 +6,5 @@ pub enum Error { Pool(#[from] diesel::r2d2::PoolError), #[error("database migration failed: {0}")] - Migration(#[from] diesel::migration::RunMigrationsError), + Migration(Box), } diff --git a/src/db/mod.rs b/src/db/mod.rs index f5e6b1e..882c819 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -4,7 +4,6 @@ mod error; #[rustfmt::skip] pub mod schema; -use crate::embedded_migrations; pub use error::Error; use std::env; @@ -12,13 +11,18 @@ use std::env; use diesel::pg::PgConnection; use diesel::r2d2::{ConnectionManager, Pool}; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + pub(super) fn new() -> Result>, Error> { let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - let pool = Pool::new(ConnectionManager::new(database_url))?; + let pool = Pool::new(ConnectionManager::::new(database_url))?; info!("starting db migrations..."); - embedded_migrations::run(&pool.get()?)?; + pool.get()? + .run_pending_migrations(MIGRATIONS) + .map_err(|e| Error::Migration(e))?; info!("db migrations complete"); Ok(pool) diff --git a/src/db/schema.rs b/src/db/schema.rs index ba98e14..c83c099 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -1,6 +1,19 @@ +table! { + containers (id) { + id -> Uuid, + parent -> Nullable, + name -> Varchar, + } +} + table! { users (id) { id -> Uuid, nickname -> Varchar, } } + +allow_tables_to_appear_in_same_query!( + containers, + users, +); diff --git a/src/main.rs b/src/main.rs index ed24115..2e4f2ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,12 +13,6 @@ use std::io; #[macro_use] extern crate diesel; -// embed diesel migrations -#[macro_use] -extern crate diesel_migrations; - -embed_migrations!(); - fn main() -> Result<(), io::Error> { log::with_level( std::env::var("LOG")