Skip to content

Commit

Permalink
item api (#56)
Browse files Browse the repository at this point in the history
- **feat: Add item http api framework**
- **feat: Adding create-item to core**
- **feat: wire api create-item to entity**
- **feat: Add item adapter**
- **feat: Add new item to database**
  • Loading branch information
szinn authored Aug 18, 2024
1 parent 3f28276 commit 56ad48b
Show file tree
Hide file tree
Showing 25 changed files with 264 additions and 30 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ members = [
]

[workspace.package]
version = "0.2.0" # managed by release.sh
version = "0.2.0" # managed by release.sh
edition = "2021"
authors = ["Scotte Zinn <scotte@zinn.ca>"]
license = "MIT"
Expand Down Expand Up @@ -47,6 +47,10 @@ features = ["full"]
version = "0.1.40"
features = ["log"]

[workspace.dependencies.uuid]
version = "1.10.0"
features = ["serde", "v4"]

[profile.release]
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
lto = true # Enables link to optimizations
Expand Down
4 changes: 4 additions & 0 deletions crates/arch-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ publish = false

[dependencies]
arch-domain-api.workspace = true
arch-domain-models.workspace = true
arch-utils.workspace = true

serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
tokio.workspace = true
tokio-graceful-shutdown.workspace = true
tracing.workspace = true
uuid.workspace = true

axum = "0.7.5"

Expand Down
1 change: 1 addition & 0 deletions crates/arch-api/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use tokio_graceful_shutdown::{SubsystemBuilder, SubsystemHandle};
use crate::ApiError;

pub(crate) mod handlers;
pub(crate) mod health;
pub(crate) mod v1;

pub async fn start_server(port: u16, arch_api: Arc<ArchApi>, subsys: SubsystemHandle) -> Result<(), ApiError> {
Expand Down
12 changes: 9 additions & 3 deletions crates/arch-api/src/http/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ use tokio_graceful_shutdown::SubsystemHandle;
use tower::Service;
use tower_http::timeout::TimeoutLayer;

use super::v1;
use super::{health, v1};

pub fn get_routes(arch_api: Arc<ArchApi>) -> Router<()> {
let v1_routes = v1::get_routes(arch_api.clone());

let api_routes = Router::new().nest("/v1", v1_routes);

let health_route: Router = Router::new().route("/", get(health::health)).with_state(arch_api.health_api.clone());

axum::Router::new()
.route("/api/v1/health", get(v1::health::health))
.with_state(arch_api.health_api.clone())
.nest("/health", health_route)
.nest("/api", api_routes)
.layer(TimeoutLayer::new(Duration::from_secs(2)))
}

Expand Down
File renamed without changes.
15 changes: 14 additions & 1 deletion crates/arch-api/src/http/v1.rs
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
pub(crate) mod health;
use std::{sync::Arc, time::Duration};

use arch_domain_api::ArchApi;
use axum::Router;
use tower_http::timeout::TimeoutLayer;

pub(crate) mod items;

pub(crate) fn get_routes(arch_api: Arc<ArchApi>) -> Router<()> {
let items_routes = items::get_routes(arch_api.clone());
axum::Router::new()
.nest("/items", items_routes)
.layer(TimeoutLayer::new(Duration::from_secs(2)))
}
61 changes: 61 additions & 0 deletions crates/arch-api/src/http/v1/items.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::{sync::Arc, time::Duration};

use arch_domain_api::{ArchApi, ItemApi};
use arch_domain_models::item::NewItem;
use arch_utils::arcbox::ArcBox;
use axum::{
extract::{Path, State},
routing::{get, post},
Json, Router,
};
use hyper::StatusCode;
use serde::{Deserialize, Serialize};
use tower_http::timeout::TimeoutLayer;
use uuid::Uuid;

#[derive(Debug, Serialize)]
struct Item {
pub id: i64,
pub version: i32,
pub uuid: Uuid,
pub text: String,
}

#[derive(Deserialize)]
struct ItemParams {
text: String,
}

pub(crate) fn get_routes(arch_api: Arc<ArchApi>) -> Router<()> {
Router::new()
.route("/:id", get(get_item))
.route("/", post(create_item))
.with_state(arch_api.item_api.clone())
.layer(TimeoutLayer::new(Duration::from_secs(2)))
}

#[tracing::instrument(level = "trace", skip(_id))]
async fn get_item(Path(_id): Path<Uuid>) -> Result<Json<Item>, StatusCode> {
Err(StatusCode::BAD_REQUEST)
}

#[tracing::instrument(level = "trace", skip(item_api, params))]
async fn create_item(State(item_api): State<ArcBox<dyn ItemApi>>, Json(params): Json<ItemParams>) -> Result<Json<Item>, StatusCode> {
let new_item = NewItem { text: params.text };

match item_api.create_item(&new_item).await {
Err(_) => Err(StatusCode::BAD_REQUEST),
Ok(item) => Ok(Json(item.into())),
}
}

impl From<arch_domain_models::item::Item> for Item {
fn from(value: arch_domain_models::item::Item) -> Self {
Item {
id: value.id,
version: value.version,
uuid: value.uuid,
text: value.text,
}
}
}
3 changes: 3 additions & 0 deletions crates/arch-db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ name = "arch_db"
path = "src/lib.rs"

[dependencies]
arch-domain-models.workspace = true
arch-utils.workspace = true

serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
Expand Down
11 changes: 11 additions & 0 deletions crates/arch-db/src/adapters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use arch_domain_models::item::{Item, NewItem};
use sea_orm_migration::async_trait::async_trait;

use crate::Error;

pub(crate) mod item;

#[async_trait]
pub trait ItemAdapter: Send + Sync {
async fn create_item(&self, new_item: &NewItem) -> Result<Item, Error>;
}
47 changes: 47 additions & 0 deletions crates/arch-db/src/adapters/item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::sync::Arc;

use arch_domain_models::item::{Item, NewItem};
use sea_orm::{prelude::Uuid, ActiveModelTrait, Set, TransactionTrait};
use sea_orm_migration::async_trait::async_trait;

use crate::{entities::items, Error, Repository};

use super::ItemAdapter;

pub(crate) struct ItemAdapterImpl {
repository: Arc<Repository>,
}

impl ItemAdapterImpl {
pub(crate) fn new(repository: Arc<Repository>) -> Self {
Self { repository }
}

fn from_model(model: items::Model) -> Item {
Item {
id: model.id,
version: model.version,
uuid: model.uuid,
text: model.text,
}
}
}

#[async_trait]
impl ItemAdapter for ItemAdapterImpl {
#[tracing::instrument(level = "trace", skip(self, new_item))]
async fn create_item(&self, new_item: &NewItem) -> Result<Item, Error> {
let new_item = items::ActiveModel {
version: Set(0),
uuid: Set(Uuid::new_v4()),
text: Set(new_item.text.clone()),
..Default::default()
};

let tx = self.repository.database.begin().await?;
let item = new_item.insert(&tx).await?;
tx.commit().await?;

Ok(ItemAdapterImpl::from_model(item))
}
}
2 changes: 1 addition & 1 deletion crates/arch-db/src/entities/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
#[sea_orm(table_name = "items")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub id: i64,
pub version: i32,
#[sea_orm(unique)]
pub uuid: Uuid,
Expand Down
3 changes: 3 additions & 0 deletions crates/arch-db/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use sea_orm::DbErr;

#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("{0}")]
Message(String),

#[error(transparent)]
SeaOrm(#[from] DbErr),

Expand Down
19 changes: 17 additions & 2 deletions crates/arch-db/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::sync::Arc;

use adapters::{item::ItemAdapterImpl, ItemAdapter};
use arch_utils::{arcbox, arcbox::ArcBox};
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
pub use sea_orm_migration::prelude::*;

pub mod adapters;
pub mod entities;
pub mod error;
pub use error::*;
Expand All @@ -23,7 +26,12 @@ pub struct Repository {
pub database: DatabaseConnection,
}

pub async fn connect_database(url: &str) -> Result<Arc<Repository>, Error> {
pub struct RepositoryAdapters {
pub repository: Arc<Repository>,
pub item_adapter: ArcBox<dyn ItemAdapter>,
}

pub async fn connect_database(url: &str) -> Result<Arc<RepositoryAdapters>, Error> {
tracing::debug!("Connecting to database...");
let mut opt = ConnectOptions::new(url);
opt.max_connections(100)
Expand All @@ -35,7 +43,14 @@ pub async fn connect_database(url: &str) -> Result<Arc<Repository>, Error> {
Migrator::up(&database, None).await?;
tracing::debug!("...connected to database");

Ok(Arc::new(Repository { database }))
let repository = Arc::new(Repository { database });

let item_adapter = ItemAdapterImpl::new(repository.clone());
let item_adapter: ArcBox<dyn ItemAdapter> = arcbox!(item_adapter);

let adapters = Arc::new(RepositoryAdapters { repository, item_adapter });

Ok(adapters)
}

pub async fn run_migration_cli() {
Expand Down
2 changes: 1 addition & 1 deletion crates/arch-db/src/m20240815_124028_create_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ impl MigrationTrait for Migration {
Table::create()
.table(Items::Table)
.if_not_exists()
.col(ColumnDef::new(Items::Id).integer().not_null().auto_increment().primary_key())
.col(ColumnDef::new(Items::Id).big_integer().not_null().auto_increment().primary_key())
.col(ColumnDef::new(Items::Version).integer().default(0).not_null())
.col(ColumnDef::new(Items::Uuid).uuid().not_null().unique_key())
.col(ColumnDef::new(Items::Text).string().string_len(200).not_null())
Expand Down
1 change: 1 addition & 0 deletions crates/arch-domain-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ publish = false

[dependencies]
arch-db.workspace = true
arch-domain-models.workspace = true
arch-utils.workspace = true

async-trait.workspace = true
Expand Down
8 changes: 7 additions & 1 deletion crates/arch-domain-api/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
#[derive(Debug, thiserror::Error)]
pub enum Error {}
pub enum Error {
#[error("{0}")]
Message(String),

#[error(transparent)]
DatabaseError(#[from] arch_db::Error),
}
9 changes: 9 additions & 0 deletions crates/arch-domain-api/src/item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use arch_domain_models::item::{Item, NewItem};
use async_trait::async_trait;

use crate::Error;

#[async_trait]
pub trait ItemApi: Send + Sync {
async fn create_item(&self, new_item: &NewItem) -> Result<Item, Error>;
}
3 changes: 3 additions & 0 deletions crates/arch-domain-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
mod error;
mod health;
pub mod item;

use arch_utils::arcbox::ArcBox;

pub use error::Error;
pub use health::HealthApi;
pub use item::ItemApi;

pub struct ArchApi {
pub health_api: ArcBox<dyn HealthApi>,
pub item_api: ArcBox<dyn ItemApi>,
}
1 change: 1 addition & 0 deletions crates/arch-domain-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ publish = false
[dependencies]
arch-db.workspace = true
arch-domain-api.workspace = true
arch-domain-models.workspace = true
arch-utils.workspace = true

async-trait.workspace = true
Expand Down
Loading

0 comments on commit 56ad48b

Please sign in to comment.