Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,764 changes: 1,229 additions & 1,535 deletions Cargo.lock

Large diffs are not rendered by default.

61 changes: 41 additions & 20 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,73 @@ edition = "2021"
publish = false

# docs
authors = ["Weiliang Li <to.be.impressive@gmail.com>"]
description = "Axum scaffold with clean architecture"
authors = ["Sid Iyer <mail@yeahsid.com>"]
description = "scaffold for backend services"
license = "MIT"
readme = "README.md"

[workspace]
members = ["api", "app", "doc", "models", "migration", "utils"]

[workspace.dependencies]
axum = { version = "0.7.5", default-features = false }
tower = { version = "0.5.0", default-features = false }
sea-orm = { version = "1.0.0", default-features = false }
axum = { version = "0.8.1", default-features = false }
tower = { version = "0.5.2", default-features = false }
sea-orm = { version = "1.1.7", default-features = false }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", default-features = false }
tracing = "0.1.40"
utoipa = { version = "5.0.0-alpha.0", default-features = false }
validator = { version = "0.18", default-features = false }

[dependencies]
tracing = "0.1.41"
utoipa = { version = "5.3.1", default-features = false }
validator = { version = "0.20", default-features = false }
anyhow = "1.0.97"
api = { path = "api" }
utils = { path = "utils" }
app = { path = "app" }
async-std = "1"
doc = { path = "doc" }
dotenvy = "0.15.7"
http-body-util = "0.1.3"
migration = { path = "migration" }
models = { path = "models" }
prefork = { version = "0.5.1", default-features = false }
sea-orm-migration = "1.1.7"
serde_with = "3.12.0"
shuttle-axum = "0.53.0"
shuttle-runtime = "0.53.0"
shuttle-shared-db = "0.53.0"
tokio = "1.44.1"
tower-cookies = "0.11.0"
tower-http = "0.6.2"
tracing-subscriber = "0.3.19"
utils = { path = "utils" }
utoipa-scalar = { version = "0.3.0", default-features = false }

[dependencies]
api = { workspace = true }
utils = { workspace = true }
doc = { workspace = true }

sea-orm = { workspace = true }

# logging
tracing = { workspace = true }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
tracing-subscriber = { workspace = true, features = ["env-filter"] }

# runtime
axum = { workspace = true, features = ["tokio", "http1", "http2"] }
tokio = { version = "1.38.0", features = ["full"] }
prefork = { version = "0.5.1", default-features = false, optional = true }
tokio = { workspace = true, features = ["full"] }
prefork = { workspace = true, optional = true }

# shuttle runtime
shuttle-axum = { version = "0.47.0", optional = true }
shuttle-runtime = { version = "0.47.0", optional = true }
shuttle-shared-db = { version = "0.47.0", features = [
shuttle-axum = { workspace = true, optional = true }
shuttle-runtime = { workspace = true, optional = true }
shuttle-shared-db = { workspace = true, features = [
"postgres",
], optional = true }

[dev-dependencies]
app = { path = "app" }
models = { path = "models" }
app = { workspace = true }
models = { workspace = true }

http-body-util = "0.1.2"
http-body-util = { workspace = true }
serde_json = { workspace = true }

[features]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,4 @@ cargo shuttle deploy
# edit .env to use Postgres
cargo run --release
wrk --latency -t20 -c50 -d10s http://localhost:3000/users\?username\=
```
```
16 changes: 8 additions & 8 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@ edition = "2021"
publish = false

[dependencies]
axum = { workspace = true, features = ["macros", "query"] }
axum = { workspace = true, features = ["macros", "query", "json"] }
serde = { workspace = true }
tower = { workspace = true }
tracing = { workspace = true }
validator = { workspace = true, features = ["derive"] }

tower-http = { version = "0.5.2", features = ["fs"] }
tower-cookies = "0.10.0"
anyhow = "1.0.86"
dotenvy = "0.15.7"
tower-http = { workspace = true, features = ["fs"] }
tower-cookies = { workspace = true }
anyhow = { workspace = true }
dotenvy = { workspace = true }

# db
sea-orm = { workspace = true }

# doc
utoipa = { workspace = true }
utoipa = { workspace = true, features = ["macros"] }

# local dependencies
app = { path = "../app" }
models = { path = "../models" }
app = { workspace = true }
models = { workspace = true }

[dev-dependencies]
6 changes: 1 addition & 5 deletions api/src/extractor/valid.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
use axum::{
async_trait,
extract::{FromRequest, Request},
};
use axum::extract::{FromRequest, Request};
use validator::Validate;

use crate::validation::ValidRejection;

#[derive(Debug, Clone, Copy, Default)]
pub struct Valid<T>(pub T);

#[async_trait]
impl<State, Extractor> FromRequest<State> for Valid<Extractor>
where
State: Send + Sync,
Expand Down
19 changes: 16 additions & 3 deletions api/src/models/response.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::collections::HashMap;

use serde::Serialize;
use std::collections::HashMap;
use utoipa::ToSchema;

#[derive(Serialize, ToSchema)]
Expand All @@ -9,12 +8,17 @@ pub struct ApiErrorResponse {
}

#[derive(Serialize, ToSchema)]
#[aliases(ParamsErrorResponse = ValidationErrorResponse<HashMap<String, Vec<HashMap<String, String>>>>)]
pub struct ValidationErrorResponse<T> {
pub message: String,
pub details: T,
}

#[derive(Serialize, ToSchema)]
pub struct ParamsErrorResponse {
pub message: String,
pub details: HashMap<String, Vec<HashMap<String, String>>>,
}

impl<T> From<T> for ValidationErrorResponse<T> {
fn from(t: T) -> Self {
Self {
Expand All @@ -23,3 +27,12 @@ impl<T> From<T> for ValidationErrorResponse<T> {
}
}
}

impl From<HashMap<String, Vec<HashMap<String, String>>>> for ParamsErrorResponse {
fn from(t: HashMap<String, Vec<HashMap<String, String>>>) -> Self {
Self {
message: "Validation error".to_string(),
details: t,
}
}
}
13 changes: 7 additions & 6 deletions api/src/routers/blog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ use models::schemas::blog::{BlogListSchema, BlogSchema};
use crate::error::ApiError;
use crate::extractor::{Json, Valid};

use crate::models::ApiErrorResponse;
use crate::models::ParamsErrorResponse;

#[utoipa::path(
post,
path = "",
Expand All @@ -28,7 +31,7 @@ use crate::extractor::{Json, Valid};
)
)]
async fn blogs_post(
state: State<AppState>,
State(state): State<AppState>,
Valid(Json(params)): Valid<Json<CreateBlogParams>>,
) -> Result<impl IntoResponse, ApiError> {
let blog = create_blog(&state.conn, params)
Expand All @@ -51,17 +54,15 @@ async fn blogs_post(
)
)]
async fn blogs_get(
state: State<AppState>,
query: Option<Query<BlogQuery>>,
State(state): State<AppState>,
Query(query): Query<BlogQuery>,
) -> Result<impl IntoResponse, ApiError> {
let Query(query) = query.unwrap_or_default();

let blogs = search_blogs(&state.conn, query)
.await
.map_err(ApiError::from)?;
Ok(Json(BlogListSchema::from(blogs)))
}

pub fn create_blog_router() -> Router<AppState> {
Router::new().route("/", get(blogs_get).post(blogs_post))
Router::new().route("/blogs", get(blogs_get).post(blogs_post))
}
6 changes: 3 additions & 3 deletions api/src/routers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use user::create_user_router;

pub fn create_router(state: AppState) -> Router {
Router::new()
.nest("/users", create_user_router())
.nest("/blogs", create_blog_router())
.nest("/", create_root_router())
.merge(create_user_router())
.merge(create_blog_router())
.merge(create_root_router())
.with_state(state)
}
17 changes: 9 additions & 8 deletions api/src/routers/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ use models::params::user::CreateUserParams;
use models::queries::user::UserQuery;
use models::schemas::user::{UserListSchema, UserSchema};

use crate::models::ApiErrorResponse;
use crate::models::ParamsErrorResponse;

use crate::error::ApiError;
use crate::extractor::{Json, Valid};

Expand All @@ -29,7 +32,7 @@ use crate::extractor::{Json, Valid};
)
)]
async fn users_post(
state: State<AppState>,
State(state): State<AppState>,
Valid(Json(params)): Valid<Json<CreateUserParams>>,
) -> Result<impl IntoResponse, ApiError> {
let user = create_user(&state.conn, params)
Expand All @@ -52,11 +55,9 @@ async fn users_post(
)
)]
async fn users_get(
state: State<AppState>,
query: Option<Query<UserQuery>>,
State(state): State<AppState>,
Query(query): Query<UserQuery>,
) -> Result<impl IntoResponse, ApiError> {
let Query(query) = query.unwrap_or_default();

let users = search_users(&state.conn, query)
.await
.map_err(ApiError::from)?;
Expand All @@ -76,7 +77,7 @@ async fn users_get(
)
)]
async fn users_id_get(
state: State<AppState>,
State(state): State<AppState>,
Path(id): Path<i32>,
) -> Result<impl IntoResponse, ApiError> {
let user = get_user(&state.conn, id).await.map_err(ApiError::from)?;
Expand All @@ -87,6 +88,6 @@ async fn users_id_get(

pub fn create_user_router() -> Router<AppState> {
Router::new()
.route("/", post(users_post).get(users_get))
.route("/:id", get(users_id_get))
.route("/users", post(users_post).get(users_get))
.route("/users/{:id}", get(users_id_get))
}
2 changes: 1 addition & 1 deletion app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ publish = false


[dependencies]
models = { path = "../models" }
models = { workspace = true }

sea-orm = { workspace = true }
2 changes: 1 addition & 1 deletion app/src/persistence/blog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use models::{domains::blog, params::blog::CreateBlogParams, queries::blog::BlogQ

pub async fn search_blogs(db: &DbConn, query: BlogQuery) -> Result<Vec<blog::Model>, DbErr> {
blog::Entity::find()
.filter(blog::Column::Title.contains(query.title))
.filter(blog::Column::Title.contains(query.title.unwrap_or_default()))
.all(db)
.await
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/persistence/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub async fn create_user(

pub async fn search_users(db: &DbConn, query: UserQuery) -> Result<Vec<user::Model>, DbErr> {
user::Entity::find()
.filter(user::Column::Username.contains(query.username))
.filter(user::Column::Username.contains(query.username.unwrap_or_default()))
.all(db)
.await
}
Expand Down
12 changes: 3 additions & 9 deletions doc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@ publish = false
[dependencies]
axum = { workspace = true }
utoipa = { workspace = true, features = ["axum_extras"] }
utoipa-swagger-ui = { version = "7.1.1-alpha.0", features = [
"axum",
"vendored",
], default-features = false }
utoipa-scalar = { version = "0.2.0-alpha.0", features = [
"axum",
], default-features = false }
utoipa-scalar = { workspace = true, features = ["axum"] }

api = { path = "../api" }
models = { path = "../models" }
api = { workspace = true }
models = { workspace = true }
4 changes: 1 addition & 3 deletions doc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use axum::Router;
use utoipa::OpenApi;
use utoipa_scalar::{Scalar, Servable as ScalarServable};
use utoipa_swagger_ui::SwaggerUi;

mod blog;
mod root;
Expand Down Expand Up @@ -29,7 +28,6 @@ pub trait ApiDoc {

impl ApiDoc for Router {
fn attach_doc(self) -> Self {
self.merge(SwaggerUi::new("/docs").url("/openapi.json", _ApiDoc::openapi()))
.merge(Scalar::with_url("/scalar", _ApiDoc::openapi()))
self.merge(Scalar::with_url("/scalar", _ApiDoc::openapi()))
}
}
6 changes: 3 additions & 3 deletions migration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ name = "migration"
path = "src/lib.rs"

[dependencies]
models = { path = "../models" }
models = { workspace = true }

async-std = { version = "1", features = ["attributes", "tokio1"] }
sea-orm-migration = { version = "1.0.0" }
async-std = { workspace = true, features = ["attributes", "tokio1"] }
sea-orm-migration = { workspace = true }
3 changes: 2 additions & 1 deletion models/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ sea-orm = { workspace = true, features = [
"macros",
] }
validator = { workspace = true, features = ["derive"] }
utoipa = { workspace = true }
utoipa = { workspace = true, features = ["macros"]}
serde_with = { workspace = true }
5 changes: 4 additions & 1 deletion models/src/queries/blog.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use serde::Deserialize;
use serde_with::serde_as;
use utoipa::IntoParams;

#[derive(Deserialize, Default, IntoParams)]
#[into_params(style = Form, parameter_in = Query)]
#[serde_as]
pub struct BlogQuery {
#[serde_as(as = "NoneAsEmptyString")]
#[param(nullable = true)]
pub title: String,
pub title: Option<String>,
}
Loading
Loading