-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(coin): add get balance endpoint
- Loading branch information
Showing
27 changed files
with
578 additions
and
4 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
use academy_models::coin::Balance; | ||
use schemars::JsonSchema; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] | ||
pub struct ApiBalance { | ||
/// Number of Morphcoins the user owns | ||
pub coins: u64, | ||
/// Number of Morphcoins withheld until the user completes their invoice | ||
/// info | ||
pub withheld_coins: u64, | ||
} | ||
|
||
impl From<Balance> for ApiBalance { | ||
fn from(value: Balance) -> Self { | ||
Self { | ||
coins: value.coins, | ||
withheld_coins: value.withheld_coins, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
use std::sync::Arc; | ||
|
||
use academy_core_coin_contracts::{CoinFeatureService, CoinGetBalanceError}; | ||
use aide::{ | ||
axum::{routing, ApiRouter}, | ||
transform::TransformOperation, | ||
}; | ||
use axum::{ | ||
extract::{Path, State}, | ||
http::StatusCode, | ||
response::{IntoResponse, Response}, | ||
Json, | ||
}; | ||
|
||
use super::user::UserNotFoundError; | ||
use crate::{ | ||
docs::TransformOperationExt, | ||
errors::{auth_error, auth_error_docs, internal_server_error, internal_server_error_docs}, | ||
extractors::auth::ApiToken, | ||
models::{coin::ApiBalance, user::PathUserIdOrSelf}, | ||
}; | ||
|
||
pub const TAG: &str = "Coins"; | ||
|
||
pub fn router(service: Arc<impl CoinFeatureService>) -> ApiRouter<()> { | ||
ApiRouter::new() | ||
.api_route( | ||
"/shop/coins/:user_id", | ||
routing::get_with(get_balance, get_balance_docs), | ||
) | ||
.with_state(service) | ||
.with_path_items(|op| op.tag(TAG)) | ||
} | ||
|
||
async fn get_balance( | ||
service: State<Arc<impl CoinFeatureService>>, | ||
token: ApiToken, | ||
Path(PathUserIdOrSelf { user_id }): Path<PathUserIdOrSelf>, | ||
) -> Response { | ||
match service.get_balance(&token.0, user_id.into()).await { | ||
Ok(balance) => Json(ApiBalance::from(balance)).into_response(), | ||
Err(CoinGetBalanceError::NotFound) => UserNotFoundError.into_response(), | ||
Err(CoinGetBalanceError::Auth(err)) => auth_error(err), | ||
Err(CoinGetBalanceError::Other(err)) => internal_server_error(err), | ||
} | ||
} | ||
|
||
fn get_balance_docs(op: TransformOperation) -> TransformOperation { | ||
op.summary("Return the Morphcoin balance of the given user.") | ||
.add_response::<ApiBalance>(StatusCode::OK, None) | ||
.add_error::<UserNotFoundError>() | ||
.with(auth_error_docs) | ||
.with(internal_server_error_docs) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
pub mod coin; | ||
pub mod config; | ||
pub mod contact; | ||
pub mod health; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "academy_core_coin_contracts" | ||
version.workspace = true | ||
edition.workspace = true | ||
publish.workspace = true | ||
homepage.workspace = true | ||
repository.workspace = true | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[dependencies] | ||
academy_models.workspace = true | ||
anyhow.workspace = true | ||
thiserror.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use std::future::Future; | ||
|
||
use academy_models::{ | ||
auth::{AccessToken, AuthError}, | ||
coin::Balance, | ||
user::UserIdOrSelf, | ||
}; | ||
use thiserror::Error; | ||
|
||
pub trait CoinFeatureService: Send + Sync + 'static { | ||
/// Return the Morphcoin balance of the given user. | ||
/// | ||
/// Requires admin privileges if not used on the authenticated user. | ||
fn get_balance( | ||
&self, | ||
token: &AccessToken, | ||
user_id: UserIdOrSelf, | ||
) -> impl Future<Output = Result<Balance, CoinGetBalanceError>> + Send; | ||
} | ||
|
||
#[derive(Debug, Error)] | ||
pub enum CoinGetBalanceError { | ||
#[error(transparent)] | ||
Auth(#[from] AuthError), | ||
#[error("The user does not exist.")] | ||
NotFound, | ||
#[error(transparent)] | ||
Other(#[from] anyhow::Error), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
[package] | ||
name = "academy_core_coin_impl" | ||
version.workspace = true | ||
edition.workspace = true | ||
publish.workspace = true | ||
homepage.workspace = true | ||
repository.workspace = true | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[dependencies] | ||
academy_auth_contracts.workspace = true | ||
academy_core_coin_contracts.workspace = true | ||
academy_di.workspace = true | ||
academy_models.workspace = true | ||
academy_persistence_contracts.workspace = true | ||
academy_utils.workspace = true | ||
tracing.workspace = true | ||
|
||
[dev-dependencies] | ||
academy_auth_contracts = { workspace = true, features = ["mock"] } | ||
academy_demo.workspace = true | ||
academy_persistence_contracts = { workspace = true, features = ["mock"] } | ||
tokio.workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
use academy_auth_contracts::{AuthResultExt, AuthService}; | ||
use academy_core_coin_contracts::{CoinFeatureService, CoinGetBalanceError}; | ||
use academy_di::Build; | ||
use academy_models::{auth::AccessToken, coin::Balance, user::UserIdOrSelf}; | ||
use academy_persistence_contracts::{coin::CoinRepository, user::UserRepository, Database}; | ||
use academy_utils::trace_instrument; | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
|
||
#[derive(Debug, Clone, Default, Build)] | ||
pub struct CoinFeatureServiceImpl<Db, Auth, UserRepo, CoinRepo> { | ||
db: Db, | ||
auth: Auth, | ||
user_repo: UserRepo, | ||
coin_repo: CoinRepo, | ||
} | ||
|
||
impl<Db, Auth, UserRepo, CoinRepo> CoinFeatureService | ||
for CoinFeatureServiceImpl<Db, Auth, UserRepo, CoinRepo> | ||
where | ||
Db: Database, | ||
Auth: AuthService<Db::Transaction>, | ||
UserRepo: UserRepository<Db::Transaction>, | ||
CoinRepo: CoinRepository<Db::Transaction>, | ||
{ | ||
#[trace_instrument(skip(self))] | ||
async fn get_balance( | ||
&self, | ||
token: &AccessToken, | ||
user_id: UserIdOrSelf, | ||
) -> Result<Balance, CoinGetBalanceError> { | ||
let auth = self.auth.authenticate(token).await.map_auth_err()?; | ||
let user_id = user_id.unwrap_or(auth.user_id); | ||
auth.ensure_self_or_admin(user_id).map_auth_err()?; | ||
|
||
let mut txn = self.db.begin_transaction().await?; | ||
|
||
if !self.user_repo.exists(&mut txn, user_id).await? { | ||
return Err(CoinGetBalanceError::NotFound); | ||
} | ||
|
||
let balance = self | ||
.coin_repo | ||
.get_balance(&mut txn, user_id) | ||
.await? | ||
.unwrap_or_default(); | ||
|
||
Ok(balance) | ||
} | ||
} |
Oops, something went wrong.