feat: add gacha credits
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{get, post},
|
||||
};
|
||||
|
||||
pub mod gacha_claim_controller;
|
||||
pub mod gacha_claim_dto;
|
||||
pub mod gacha_claim_repository;
|
||||
pub mod gacha_claim_schema;
|
||||
pub mod gacha_claim_service;
|
||||
|
||||
pub use gacha_claim_controller::*;
|
||||
pub use gacha_claim_dto::*;
|
||||
pub use gacha_claim_repository::*;
|
||||
pub use gacha_claim_schema::*;
|
||||
pub use gacha_claim_service::*;
|
||||
|
||||
pub fn gacha_claim_router() -> Router {
|
||||
Router::new()
|
||||
.route("/create", post(post_create_gacha_claim))
|
||||
.route("/detail/{id}", get(get_detail_gacha_claim))
|
||||
}
|
||||
+1
-3
@@ -46,9 +46,7 @@ impl GachaClaimSchema {
|
||||
),
|
||||
user: make_thing(&ResourceEnum::Users.to_string(), &dto.user_id),
|
||||
item: make_thing(&ResourceEnum::GachaItems.to_string(), &dto.item_id),
|
||||
is_deleted: false,
|
||||
created_at: Some(get_iso_date()),
|
||||
updated_at: Some(get_iso_date()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{get, post},
|
||||
};
|
||||
|
||||
pub mod gacha_claims_controller;
|
||||
pub mod gacha_claims_dto;
|
||||
pub mod gacha_claims_repository;
|
||||
pub mod gacha_claims_schema;
|
||||
pub mod gacha_claims_service;
|
||||
|
||||
pub use gacha_claims_controller::*;
|
||||
pub use gacha_claims_dto::*;
|
||||
pub use gacha_claims_repository::*;
|
||||
pub use gacha_claims_schema::*;
|
||||
pub use gacha_claims_service::*;
|
||||
|
||||
pub fn gacha_claim_router() -> Router {
|
||||
Router::new()
|
||||
.route("/create", post(post_create_gacha_claim))
|
||||
.route("/detail/{id}", get(get_detail_gacha_claim))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GachaCreditRequestDto {
|
||||
pub user_id: String,
|
||||
pub amount: i32,
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use super::{GachaCreditRequestDto, GachaCreditSchema};
|
||||
use crate::{AppState, ResourceEnum};
|
||||
use anyhow::{Result, bail};
|
||||
use imphnen_iam::make_thing;
|
||||
use surrealdb::Uuid;
|
||||
|
||||
pub struct GachaCreditRepository<'a> {
|
||||
state: &'a AppState,
|
||||
}
|
||||
|
||||
impl<'a> GachaCreditRepository<'a> {
|
||||
pub fn new(state: &'a AppState) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
pub async fn query_by_user_id(
|
||||
&self,
|
||||
user_id: String,
|
||||
) -> Result<Option<GachaCreditSchema>> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let sql = format!(
|
||||
"SELECT * FROM {} WHERE user = {}:⟨$user_id⟩ AND is_deleted = false LIMIT 1",
|
||||
ResourceEnum::GachaCredits.to_string(),
|
||||
ResourceEnum::Users.to_string()
|
||||
);
|
||||
let result: Vec<GachaCreditSchema> =
|
||||
db.query(sql).bind(("user_id", user_id)).await?.take(0)?;
|
||||
Ok(result.into_iter().next())
|
||||
}
|
||||
|
||||
pub async fn query_consume_credit(&self, user_id: String) -> Result<()> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let credit_opt = self.query_by_user_id(user_id).await?;
|
||||
let Some(mut credit) = credit_opt else {
|
||||
return Ok(());
|
||||
};
|
||||
if credit.available_rolls <= 0 {
|
||||
bail!("No extra roll credits remaining");
|
||||
}
|
||||
credit.available_rolls -= 1;
|
||||
let _: Option<GachaCreditSchema> = db
|
||||
.update((
|
||||
&ResourceEnum::GachaCredits.to_string(),
|
||||
credit.id.id.to_raw(),
|
||||
))
|
||||
.merge(credit)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn query_add_credit(
|
||||
&self,
|
||||
payload: GachaCreditRequestDto,
|
||||
) -> Result<()> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
if let Some(mut credit) = self.query_by_user_id(payload.user_id.clone()).await? {
|
||||
credit.available_rolls += payload.amount;
|
||||
let _: Option<GachaCreditSchema> = db
|
||||
.update((
|
||||
&ResourceEnum::GachaCredits.to_string(),
|
||||
credit.id.id.to_raw(),
|
||||
))
|
||||
.merge(credit)
|
||||
.await?;
|
||||
} else {
|
||||
let data = GachaCreditSchema::from(&GachaCreditSchema {
|
||||
id: make_thing(
|
||||
&ResourceEnum::GachaCredits.to_string(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
),
|
||||
user: make_thing(&ResourceEnum::Users.to_string(), &payload.user_id),
|
||||
available_rolls: payload.amount,
|
||||
..Default::default()
|
||||
});
|
||||
let _: Option<GachaCreditSchema> = db
|
||||
.create(&ResourceEnum::GachaCredits.to_string())
|
||||
.content(data)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
use imphnen_iam::get_iso_date;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::sql::Thing;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GachaCreditSchema {
|
||||
pub id: Thing,
|
||||
pub user: Thing,
|
||||
pub available_rolls: i32,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: Option<String>,
|
||||
pub updated_at: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for GachaCreditSchema {
|
||||
fn default() -> Self {
|
||||
GachaCreditSchema {
|
||||
id: Thing::from(("app_gacha_credits", "uuid")),
|
||||
user: Thing::from(("app_users", "uuid")),
|
||||
available_rolls: 0,
|
||||
is_deleted: false,
|
||||
created_at: Some(get_iso_date()),
|
||||
updated_at: Some(get_iso_date()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GachaCreditSchema {
|
||||
pub fn from(&self) -> Self {
|
||||
Self {
|
||||
id: self.id.clone(),
|
||||
user: self.user.clone(),
|
||||
available_rolls: self.available_rolls,
|
||||
is_deleted: false,
|
||||
created_at: Some(get_iso_date()),
|
||||
updated_at: Some(get_iso_date()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod gacha_credits_dto;
|
||||
pub mod gacha_credits_repository;
|
||||
pub mod gacha_credits_schema;
|
||||
|
||||
pub use gacha_credits_dto::*;
|
||||
pub use gacha_credits_repository::*;
|
||||
pub use gacha_credits_schema::*;
|
||||
+2
-4
@@ -1,8 +1,6 @@
|
||||
use crate::{
|
||||
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||
ResponseSuccessDto,
|
||||
gacha_item_dto::{GachaItemDto, GachaItemRequestDto},
|
||||
gacha_item_service::GachaItemService,
|
||||
AppState, GachaItemDto, GachaItemRequestDto, GachaItemService, MessageResponseDto,
|
||||
MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto,
|
||||
};
|
||||
use axum::{
|
||||
Extension, Json,
|
||||
+1
-3
@@ -40,9 +40,7 @@ impl GachaItemSchema {
|
||||
),
|
||||
name: dto.name,
|
||||
image_url: dto.image_url,
|
||||
is_deleted: false,
|
||||
created_at: Some(get_iso_date()),
|
||||
updated_at: Some(get_iso_date()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,17 @@ use axum::{
|
||||
routing::{delete, get, post, put},
|
||||
};
|
||||
|
||||
pub mod gacha_item_controller;
|
||||
pub mod gacha_item_dto;
|
||||
pub mod gacha_item_repository;
|
||||
pub mod gacha_item_schema;
|
||||
pub mod gacha_item_service;
|
||||
pub mod gacha_items_controller;
|
||||
pub mod gacha_items_dto;
|
||||
pub mod gacha_items_repository;
|
||||
pub mod gacha_items_schema;
|
||||
pub mod gacha_items_service;
|
||||
|
||||
pub use gacha_item_controller::*;
|
||||
pub use gacha_item_dto::*;
|
||||
pub use gacha_item_repository::*;
|
||||
pub use gacha_item_schema::*;
|
||||
pub use gacha_item_service::*;
|
||||
pub use gacha_items_controller::*;
|
||||
pub use gacha_items_dto::*;
|
||||
pub use gacha_items_repository::*;
|
||||
pub use gacha_items_schema::*;
|
||||
pub use gacha_items_service::*;
|
||||
|
||||
pub fn gacha_item_router() -> Router {
|
||||
Router::new()
|
||||
@@ -1,22 +0,0 @@
|
||||
pub mod gacha_roll_controller;
|
||||
pub mod gacha_roll_dto;
|
||||
pub mod gacha_roll_repository;
|
||||
pub mod gacha_roll_schema;
|
||||
pub mod gacha_roll_service;
|
||||
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{get, post},
|
||||
};
|
||||
pub use gacha_roll_controller::*;
|
||||
pub use gacha_roll_dto::*;
|
||||
pub use gacha_roll_repository::*;
|
||||
pub use gacha_roll_schema::*;
|
||||
pub use gacha_roll_service::*;
|
||||
|
||||
pub fn gacha_roll_router() -> Router {
|
||||
Router::new()
|
||||
.route("/create", post(post_create_gacha_roll))
|
||||
.route("/execute", post(post_execute_gacha_roll))
|
||||
.route("/detail/{id}", get(get_detail_gacha_roll))
|
||||
}
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
use crate::{
|
||||
AppState, MessageResponseDto, ResponseSuccessDto,
|
||||
gacha_roll_dto::{GachaRollItemDto, GachaRollRequestDto},
|
||||
gacha_roll_service::GachaRollService,
|
||||
AppState, GachaRollItemDto, GachaRollRequestDto, GachaRollService,
|
||||
MessageResponseDto, ResponseSuccessDto,
|
||||
};
|
||||
use axum::{
|
||||
Extension, Json, extract::Path, http::HeaderMap, response::IntoResponse,
|
||||
+1
-3
@@ -46,9 +46,7 @@ impl GachaRollSchema {
|
||||
item: make_thing(&ResourceEnum::GachaItems.to_string(), &dto.item_id),
|
||||
weight: dto.weight,
|
||||
quantity: dto.quantity,
|
||||
is_deleted: false,
|
||||
created_at: Some(get_iso_date()),
|
||||
updated_at: Some(get_iso_date()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
pub mod gacha_rolls_controller;
|
||||
pub mod gacha_rolls_dto;
|
||||
pub mod gacha_rolls_repository;
|
||||
pub mod gacha_rolls_schema;
|
||||
pub mod gacha_rolls_service;
|
||||
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{get, post},
|
||||
};
|
||||
pub use gacha_rolls_controller::*;
|
||||
pub use gacha_rolls_dto::*;
|
||||
pub use gacha_rolls_repository::*;
|
||||
pub use gacha_rolls_schema::*;
|
||||
pub use gacha_rolls_service::*;
|
||||
|
||||
pub fn gacha_roll_router() -> Router {
|
||||
Router::new()
|
||||
.route("/create", post(post_create_gacha_roll))
|
||||
.route("/execute", post(post_execute_gacha_roll))
|
||||
.route("/detail/{id}", get(get_detail_gacha_roll))
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
use axum::Router;
|
||||
|
||||
pub mod gacha_claim;
|
||||
pub mod gacha_item;
|
||||
pub mod gacha_roll;
|
||||
pub mod gacha_claims;
|
||||
pub mod gacha_credits;
|
||||
pub mod gacha_items;
|
||||
pub mod gacha_rolls;
|
||||
|
||||
pub use gacha_claim::*;
|
||||
pub use gacha_item::*;
|
||||
pub use gacha_roll::*;
|
||||
pub use gacha_claims::*;
|
||||
pub use gacha_credits::*;
|
||||
pub use gacha_items::*;
|
||||
pub use gacha_rolls::*;
|
||||
|
||||
pub fn gacha_router() -> Router {
|
||||
Router::new()
|
||||
|
||||
+11
-11
@@ -5,7 +5,7 @@ use utoipa::{
|
||||
openapi::security::{Http, HttpAuthScheme, SecurityScheme},
|
||||
Modify, OpenApi,
|
||||
};
|
||||
use imphnen_gacha::{gacha_claim, gacha_item, gacha_roll, GachaClaimItemDto, GachaClaimRequestDto, GachaItemDto, GachaItemRequestDto, GachaRollItemDto, GachaRollRequestDto};
|
||||
use imphnen_gacha::{gacha_claims, gacha_items, gacha_rolls, GachaClaimItemDto, GachaClaimRequestDto, GachaItemDto, GachaItemRequestDto, GachaRollItemDto, GachaRollRequestDto};
|
||||
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
@@ -35,16 +35,16 @@ use imphnen_gacha::{gacha_claim, gacha_item, gacha_roll, GachaClaimItemDto, Gach
|
||||
permissions::permissions_controller::post_create_permission,
|
||||
permissions::permissions_controller::put_update_permission,
|
||||
permissions::permissions_controller::delete_permission,
|
||||
gacha_claim::get_detail_gacha_claim,
|
||||
gacha_claim::post_create_gacha_claim,
|
||||
gacha_item::get_gacha_item_list,
|
||||
gacha_item::get_gacha_item_by_id,
|
||||
gacha_item::post_create_gacha_item,
|
||||
gacha_item::put_update_gacha_item,
|
||||
gacha_item::delete_gacha_item,
|
||||
gacha_roll::get_detail_gacha_roll,
|
||||
gacha_roll::post_create_gacha_roll,
|
||||
gacha_roll::post_execute_gacha_roll,
|
||||
gacha_claims::get_detail_gacha_claim,
|
||||
gacha_claims::post_create_gacha_claim,
|
||||
gacha_items::get_gacha_item_list,
|
||||
gacha_items::get_gacha_item_by_id,
|
||||
gacha_items::post_create_gacha_item,
|
||||
gacha_items::put_update_gacha_item,
|
||||
gacha_items::delete_gacha_item,
|
||||
gacha_rolls::get_detail_gacha_roll,
|
||||
gacha_rolls::post_create_gacha_roll,
|
||||
gacha_rolls::post_execute_gacha_roll,
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
|
||||
@@ -7,6 +7,7 @@ pub enum ResourceEnum {
|
||||
GachaItems,
|
||||
GachaClaims,
|
||||
GachaRolls,
|
||||
GachaCredits,
|
||||
Users,
|
||||
Roles,
|
||||
Permissions,
|
||||
@@ -25,6 +26,7 @@ impl fmt::Display for ResourceEnum {
|
||||
ResourceEnum::GachaItems => "app_gacha_items",
|
||||
ResourceEnum::GachaClaims => "app_gacha_claims",
|
||||
ResourceEnum::GachaRolls => "app_gacha_rolls",
|
||||
ResourceEnum::GachaCredits => "app_gacha_credits",
|
||||
};
|
||||
write!(f, "{}", str)
|
||||
}
|
||||
|
||||
@@ -50,16 +50,13 @@ where
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||
let mut inner = self.inner.clone();
|
||||
let app_state = self.app_state.clone();
|
||||
let permissions = self.permissions.clone();
|
||||
|
||||
Box::pin(async move {
|
||||
let headers = req.headers();
|
||||
let email = match extract_email(headers) {
|
||||
@@ -71,7 +68,6 @@ where
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let auth_repo = AuthRepository::new(&app_state);
|
||||
let user = match auth_repo.query_get_stored_user(email).await {
|
||||
Ok(user) => user,
|
||||
@@ -82,20 +78,17 @@ where
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let user_permissions: Vec<String> =
|
||||
user.role.permissions.into_iter().map(|p| p.name).collect();
|
||||
let allowed = permissions
|
||||
.iter()
|
||||
.all(|p| user_permissions.contains(&p.to_string()));
|
||||
|
||||
if !allowed {
|
||||
return Ok(common_response(
|
||||
StatusCode::FORBIDDEN,
|
||||
"You don't have the required permissions",
|
||||
));
|
||||
}
|
||||
|
||||
inner.call(req).await
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
mod auth_repository_test {
|
||||
use crate::{
|
||||
AuthOtpSchema, AuthRepository, ResourceEnum, UsersRepository, UsersSchema,
|
||||
create_mock_app_state, get_iso_date, make_thing,
|
||||
create_mock_app_state, generate_unique_email, get_iso_date, get_role_id,
|
||||
make_thing,
|
||||
};
|
||||
use chrono::{Duration, Utc};
|
||||
use imphnen_iam::{RolesDetailQueryDto, UsersDetailQueryDto};
|
||||
use imphnen_iam::{AppState, RolesDetailQueryDto, UsersDetailQueryDto};
|
||||
use surrealdb::Uuid;
|
||||
|
||||
fn create_mock_user(email: &str) -> UsersSchema {
|
||||
async fn create_mock_user(state: &AppState, email: &str) -> UsersSchema {
|
||||
UsersSchema {
|
||||
id: make_thing("app_users", &Uuid::new_v4().to_string()),
|
||||
email: email.to_string(),
|
||||
@@ -20,7 +21,7 @@ mod auth_repository_test {
|
||||
is_active: true,
|
||||
gender: None,
|
||||
birthdate: None,
|
||||
role: make_thing("roles", "user"),
|
||||
role: make_thing("app_roles", &get_role_id(state).await),
|
||||
created_at: get_iso_date(),
|
||||
updated_at: get_iso_date(),
|
||||
}
|
||||
@@ -30,10 +31,13 @@ mod auth_repository_test {
|
||||
async fn test_store_and_get_user() {
|
||||
let app_state = create_mock_app_state().await;
|
||||
let repo = AuthRepository::new(&app_state);
|
||||
let user = create_mock_user("forgot@example.com");
|
||||
let email = generate_unique_email("forgot");
|
||||
let user = create_mock_user(&app_state, &email).await;
|
||||
let user_repo = UsersRepository::new(&app_state);
|
||||
let create_user = user_repo.query_create_user(user.clone()).await;
|
||||
assert!(create_user.is_ok());
|
||||
let user_data = user_repo
|
||||
.query_user_by_email("forgot@example.com".to_string())
|
||||
.query_user_by_email(email.to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let store = repo.query_store_user(user_data.clone()).await;
|
||||
|
||||
@@ -1,30 +1,5 @@
|
||||
use imphnen_iam::RolesRequestCreateDto;
|
||||
use surrealdb::Uuid;
|
||||
|
||||
use crate::{MetaRequestDto, UsersRepository};
|
||||
use crate::{RolesRepository, create_mock_app_state, create_test_user};
|
||||
|
||||
async fn get_role_id(state: &crate::AppState) -> String {
|
||||
let repo = RolesRepository::new(state);
|
||||
if let Ok(existing) = repo.query_role_by_name("User".into()).await {
|
||||
return existing.id;
|
||||
}
|
||||
let _ = repo
|
||||
.query_create_role(RolesRequestCreateDto {
|
||||
name: "User".into(),
|
||||
permissions: vec![],
|
||||
})
|
||||
.await;
|
||||
repo
|
||||
.query_role_by_name("User".into())
|
||||
.await
|
||||
.expect("Role not found after creation")
|
||||
.id
|
||||
}
|
||||
|
||||
fn generate_unique_email(prefix: &str) -> String {
|
||||
format!("{}_{}@example.com", prefix, Uuid::new_v4())
|
||||
}
|
||||
use crate::{MetaRequestDto, UsersRepository, generate_unique_email, get_role_id};
|
||||
use crate::{create_mock_app_state, create_test_user};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_and_get_user() {
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
use ::surrealdb::Uuid;
|
||||
pub use imphnen_entities::*;
|
||||
pub use imphnen_iam::*;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod iam;
|
||||
|
||||
pub fn generate_unique_email(prefix: &str) -> String {
|
||||
format!("{}_{}@example.com", prefix, Uuid::new_v4())
|
||||
}
|
||||
|
||||
pub async fn get_role_id(state: &crate::AppState) -> String {
|
||||
let repo = RolesRepository::new(state);
|
||||
if let Ok(existing) = repo.query_role_by_name("User".into()).await {
|
||||
return existing.id;
|
||||
}
|
||||
let _ = repo
|
||||
.query_create_role(RolesRequestCreateDto {
|
||||
name: "User".into(),
|
||||
permissions: vec![],
|
||||
})
|
||||
.await;
|
||||
repo
|
||||
.query_role_by_name("User".into())
|
||||
.await
|
||||
.expect("Role not found after creation")
|
||||
.id
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user