From 12043f027c1c949a2913897ef0f274476851aab0 Mon Sep 17 00:00:00 2001 From: Maulana Sodiqin Date: Tue, 18 Mar 2025 14:13:53 +0700 Subject: [PATCH] feat: gacha roll, user auth --- rustfmt.toml | 1 + src/apps/v1/auth/auth_dto.rs | 9 +++- src/apps/v1/auth/auth_middleware.rs | 26 ++++++------ src/apps/v1/auth/auth_repository.rs | 36 +++++++++------- src/apps/v1/auth/auth_service.rs | 17 +++----- src/apps/v1/docs/docs_controller.rs | 15 +++++-- src/apps/v1/gacha/gacha_controller.rs | 27 +++++++++--- src/apps/v1/gacha/gacha_dto.rs | 9 +++- src/apps/v1/gacha/gacha_repository.rs | 59 ++++++++++++++++++--------- src/apps/v1/gacha/gacha_schema.rs | 8 +++- src/apps/v1/gacha/gacha_service.rs | 23 +++++++++-- src/apps/v1/gacha/mod.rs | 8 +++- src/apps/v1/users/users_dto.rs | 1 + src/apps/v1/users/users_schema.rs | 1 + src/entities/error_dto.rs | 3 +- src/libs/enviroment/mod.rs | 9 ++-- src/libs/surrealdb/resource.rs | 8 +++- 17 files changed, 174 insertions(+), 86 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 7ce9162..a77bb46 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,4 @@ hard_tabs = true edition = "2021" max_width = 85 +tab_spaces = 2 diff --git a/src/apps/v1/auth/auth_dto.rs b/src/apps/v1/auth/auth_dto.rs index cdba5ff..69c4216 100644 --- a/src/apps/v1/auth/auth_dto.rs +++ b/src/apps/v1/auth/auth_dto.rs @@ -29,8 +29,15 @@ pub struct AuthRegisterRequestDto { } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] -pub struct AuthQueryByEmailResponse { +pub struct AuthActiveInactiveRequestDto { + pub is_active: bool, + pub email: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] +pub struct AuthQueryByEmailResponseDto { pub email: String, pub fullname: String, pub password: String, + pub is_active: bool, } diff --git a/src/apps/v1/auth/auth_middleware.rs b/src/apps/v1/auth/auth_middleware.rs index 8b57341..848a4fc 100644 --- a/src/apps/v1/auth/auth_middleware.rs +++ b/src/apps/v1/auth/auth_middleware.rs @@ -1,12 +1,13 @@ -use crate::{common_response, extract_email, AppState}; +use super::AuthRepository; +use crate::{ + common_response, extract_email, v1::users_schema::UsersSchema, AppState, +}; use axum::{ extract::Request, http::StatusCode, middleware::Next, response::Response, Extension, }; use std::convert::Infallible; -use super::{AuthQueryByEmailResponse, AuthRepository}; - pub async fn auth_middleware( Extension(state): Extension, mut req: Request, @@ -26,16 +27,15 @@ pub async fn auth_middleware( let repository = AuthRepository::new(&state); - let user: Option = - match repository.query_user_by_email(email).await { - Ok(user) => Some(user), - Err(err) => { - return Ok(common_response( - StatusCode::INTERNAL_SERVER_ERROR, - &format!("DB error: {}", err), - )) - } - }; + let user: Option = match repository.query_user_by_email(email).await { + Ok(user) => Some(user), + Err(err) => { + return Ok(common_response( + StatusCode::INTERNAL_SERVER_ERROR, + &err.to_string(), + )) + } + }; if user.is_none() { return Ok(common_response( diff --git a/src/apps/v1/auth/auth_repository.rs b/src/apps/v1/auth/auth_repository.rs index bbd8a39..b40733c 100644 --- a/src/apps/v1/auth/auth_repository.rs +++ b/src/apps/v1/auth/auth_repository.rs @@ -1,9 +1,11 @@ -use crate::{v1::UsersItemDto, AppState, RedisKeyEnum, ResourceEnum}; +use super::{AuthActiveInactiveRequestDto, AuthRegisterRequestDto}; +use crate::{ + v1::{users_schema::UsersSchema, UsersItemDto}, + AppState, RedisKeyEnum, ResourceEnum, +}; use anyhow::{bail, Result}; use redis::Commands; -use super::{AuthQueryByEmailResponse, AuthRegisterRequestDto}; - pub struct AuthRepository<'a> { state: &'a AppState, } @@ -33,26 +35,19 @@ impl<'a> AuthRepository<'a> { pub fn query_get_stored_user(&self, email: String) -> Result { let redis_key = format!("{}:{}", RedisKeyEnum::User, email); let mut conn = self.state.redisdb.get_connection()?; - let data: Option = conn.get(&redis_key)?; - match data { Some(user_json) => { let user: UsersItemDto = serde_json::from_str(&user_json)?; Ok(user) } - None => bail!("No stored user data found for email"), + None => bail!("No stored user data found"), } } - pub async fn query_user_by_email( - &self, - email: String, - ) -> Result { + pub async fn query_user_by_email(&self, email: String) -> Result { let db = &self.state.surrealdb; - let result = db.select((ResourceEnum::Users.to_string(), email)).await?; - match result { Some(response) => Ok(response), None => bail!("User not found"), @@ -64,15 +59,28 @@ impl<'a> AuthRepository<'a> { data: AuthRegisterRequestDto, ) -> Result { let db = &self.state.surrealdb; - let record: Option = db .create((ResourceEnum::Users.to_string(), &data.email)) .content(data) .await?; - match record { Some(_) => Ok("Success create user".into()), None => bail!("Failed to create user"), } } + + pub async fn query_active_inactive_user( + &self, + data: AuthActiveInactiveRequestDto, + ) -> Result { + let db = &self.state.surrealdb; + let record: Option = db + .update((ResourceEnum::Users.to_string(), &data.email)) + .content(data) + .await?; + match record { + Some(_) => Ok("Success update user".into()), + None => bail!("Failed to update user"), + } + } } diff --git a/src/apps/v1/auth/auth_service.rs b/src/apps/v1/auth/auth_service.rs index 099e2eb..b15913a 100644 --- a/src/apps/v1/auth/auth_service.rs +++ b/src/apps/v1/auth/auth_service.rs @@ -6,8 +6,7 @@ use super::{ }; use crate::{ common_response, encode_access_token, encode_refresh_token, hash_password, - success_response, v1::UsersItemDto, verify_password, AppState, - ResponseSuccessDto, + success_response, v1::UsersItemDto, verify_password, AppState, ResponseSuccessDto, }; pub struct AuthService; @@ -21,14 +20,10 @@ impl AuthService { match repository.query_user_by_email(payload.email.clone()).await { Ok(user) => { let is_password_correct = - verify_password(&payload.password, &user.password) - .unwrap_or(false); + verify_password(&payload.password, &user.password).unwrap_or(false); if is_password_correct { - common_response( - StatusCode::BAD_REQUEST, - "Email or password not correct", - ); + common_response(StatusCode::BAD_REQUEST, "Email or password not correct"); } let access_token = encode_access_token(payload.email.clone()); @@ -39,6 +34,7 @@ impl AuthService { user: UsersItemDto { fullname: user.fullname.clone(), email: user.email.clone(), + is_active: user.is_active.clone(), }, token: TokenDto { access_token: access_token.unwrap(), @@ -55,10 +51,7 @@ impl AuthService { }) .is_ok() { - return common_response( - StatusCode::BAD_REQUEST, - "Failed to store data", - ); + return common_response(StatusCode::BAD_REQUEST, "Failed to store data"); } success_response(response) diff --git a/src/apps/v1/docs/docs_controller.rs b/src/apps/v1/docs/docs_controller.rs index 2427410..7926e5c 100644 --- a/src/apps/v1/docs/docs_controller.rs +++ b/src/apps/v1/docs/docs_controller.rs @@ -1,5 +1,9 @@ use crate::{ - v1::{auth, AuthLoginRequestDto, AuthLoginResponsetDto}, + v1::{ + auth, gacha, AuthLoginRequestDto, AuthLoginResponsetDto, + GachaCreateClaimRequestDto, GachaCreateItemRequestDto, + GachaCreateRollRequestDto, + }, MessageResponseDto, MetaRequestDto, MetaResponseDto, ResponseSuccessDto, }; @@ -12,17 +16,22 @@ use utoipa::{ #[openapi( paths( auth::auth_controller::post_login, - auth::auth_controller::post_register + auth::auth_controller::post_register, + gacha::gacha_controller::post_create_gacha_claim, + gacha::gacha_controller::post_create_gacha_item, + gacha::gacha_controller::post_create_gacha_roll, ), components( schemas( MetaRequestDto, MetaResponseDto, MessageResponseDto, - AuthLoginRequestDto, AuthLoginResponsetDto, ResponseSuccessDto, + GachaCreateClaimRequestDto, + GachaCreateItemRequestDto, + GachaCreateRollRequestDto ) ), info( diff --git a/src/apps/v1/gacha/gacha_controller.rs b/src/apps/v1/gacha/gacha_controller.rs index d4d0aab..40197c6 100644 --- a/src/apps/v1/gacha/gacha_controller.rs +++ b/src/apps/v1/gacha/gacha_controller.rs @@ -1,23 +1,23 @@ -use super::{GachaClaimRequestDto, GachaService}; +use super::{GachaCreateClaimRequestDto, GachaCreateRollRequestDto, GachaService}; use crate::{v1::GachaCreateItemRequestDto, AppState, MessageResponseDto}; use axum::{http::HeaderMap, response::IntoResponse, Extension, Json}; #[utoipa::path( post, path = "/v1/gacha/create/claims", - request_body = GachaClaimRequestDto, + request_body = GachaCreateClaimRequestDto, responses( (status = 200, description = "Create gacha claims successful", body = MessageResponseDto), (status = 401, description = "Create gacha claims failed", body = MessageResponseDto) ), tag = "Gacha" )] -pub async fn post_create_gacha_claims( +pub async fn post_create_gacha_claim( header: HeaderMap, Extension(state): Extension, - Json(payload): Json, + Json(payload): Json, ) -> impl IntoResponse { - GachaService::mutation_create_gacha_claims(payload, &state, header).await + GachaService::mutation_create_gacha_claim(payload, &state, header).await } #[utoipa::path( @@ -36,3 +36,20 @@ pub async fn post_create_gacha_item( ) -> impl IntoResponse { GachaService::mutation_create_gacha_item(payload, &state).await } + +#[utoipa::path( + post, + path = "/v1/gacha/create/roll", + request_body = GachaCreateRollRequestDto, + responses( + (status = 200, description = "Create gacha roll successful", body = MessageResponseDto), + (status = 401, description = "Create gacha roll failed", body = MessageResponseDto) + ), + tag = "Gacha" +)] +pub async fn post_create_gacha_roll( + Extension(state): Extension, + Json(payload): Json, +) -> impl IntoResponse { + GachaService::mutation_create_gacha_roll(payload, &state).await +} diff --git a/src/apps/v1/gacha/gacha_dto.rs b/src/apps/v1/gacha/gacha_dto.rs index 324fc5c..418dac2 100644 --- a/src/apps/v1/gacha/gacha_dto.rs +++ b/src/apps/v1/gacha/gacha_dto.rs @@ -4,7 +4,7 @@ use utoipa::ToSchema; use crate::v1::UsersItemDto; #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] -pub struct GachaClaimRequestDto { +pub struct GachaCreateClaimRequestDto { pub transaction_number: String, } @@ -16,7 +16,7 @@ pub struct GachaCreateItemRequestDto { #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct GachaCreateRollRequestDto { - pub item_id: String, + pub item_name: String, pub weight: String, } @@ -31,3 +31,8 @@ pub struct GachaClaimResponseDto { pub transaction_number: String, pub user: UsersItemDto, } + +#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] +pub struct GachaRollResponseDto { + pub item: GachaItemResponseDto, +} diff --git a/src/apps/v1/gacha/gacha_repository.rs b/src/apps/v1/gacha/gacha_repository.rs index 148e16e..1efef81 100644 --- a/src/apps/v1/gacha/gacha_repository.rs +++ b/src/apps/v1/gacha/gacha_repository.rs @@ -1,6 +1,7 @@ use super::{ - GachaClaimRequestDto, GachaClaimResponseDto, GachaCreateItemRequestDto, - GachaItemSchema, GachaSchema, + GachaClaimResponseDto, GachaClaimSchema, GachaCreateClaimRequestDto, + GachaCreateItemRequestDto, GachaCreateRollRequestDto, GachaItemResponseDto, + GachaItemSchema, GachaRollSchema, }; use crate::{v1::AuthRepository, AppState, ResourceEnum}; use anyhow::{bail, Result}; @@ -15,25 +16,41 @@ impl<'a> GachaRepository<'a> { Self { state } } - pub async fn query_gacha_by_transaction_number( + pub async fn query_gacha_claim_by_transaction_number( &self, transaction_number: String, ) -> Result { let db = &self.state.surrealdb; let result = db - .select((ResourceEnum::Gacha.to_string(), transaction_number)) + .select((ResourceEnum::GachaClaims.to_string(), transaction_number)) .await?; match result { Some(response) => Ok(response), - None => bail!("Gacha not found"), + None => bail!("Gacha claim not found"), } } - pub async fn query_create_gacha_claims( + pub async fn query_gacha_item_by_name( &self, - data: GachaClaimRequestDto, + name: String, + ) -> Result { + let db = &self.state.surrealdb; + + let result = db + .select((ResourceEnum::GachaItems.to_string(), name)) + .await?; + + match result { + Some(response) => Ok(response), + None => bail!("Gacha item not found"), + } + } + + pub async fn query_create_gacha_claim( + &self, + data: GachaCreateClaimRequestDto, email: String, ) -> Result { let auth_repository = AuthRepository::new(self.state); @@ -44,12 +61,12 @@ impl<'a> GachaRepository<'a> { let user_thing = Thing::from((ResourceEnum::Users.to_string(), Id::String(user.email))); - let record: Option = db + let record: Option = db .create(( ResourceEnum::GachaClaims.to_string(), &data.transaction_number, )) - .content(GachaSchema { + .content(GachaClaimSchema { transaction_number: data.transaction_number.clone(), user: user_thing, }) @@ -68,7 +85,7 @@ impl<'a> GachaRepository<'a> { let db = &self.state.surrealdb; let record: Option = db - .create((ResourceEnum::Gacha.to_string(), data.item_name.clone())) + .create((ResourceEnum::GachaItems.to_string(), data.item_name.clone())) .content(GachaItemSchema { item_name: data.item_name.clone(), item_image: data.item_image.clone(), @@ -77,27 +94,31 @@ impl<'a> GachaRepository<'a> { match record { Some(_) => Ok("Gacha item successfully created".to_string()), - None => bail!("Failed to create gacha item record"), + None => bail!("Failed to create gacha item"), } } pub async fn query_create_gacha_roll( &self, - data: GachaCreateItemRequestDto, + data: GachaCreateRollRequestDto, ) -> Result { let db = &self.state.surrealdb; + let item_thing = Thing::from(( + ResourceEnum::GachaItems.to_string(), + Id::String(data.item_name.clone()), + )); - let record: Option = db - .create((ResourceEnum::Gacha.to_string(), data.item_name.clone())) - .content(GachaItemSchema { - item_name: data.item_name.clone(), - item_image: data.item_image.clone(), + let record: Option = db + .create((ResourceEnum::GachaRolls.to_string(), data.item_name.clone())) + .content(GachaRollSchema { + weight: data.weight.clone(), + item: item_thing, }) .await?; match record { - Some(_) => Ok("Gacha item successfully created".to_string()), - None => bail!("Failed to create gacha item record"), + Some(_) => Ok("Gacha roll successfully created".to_string()), + None => bail!("Failed to create gacha roll"), } } } diff --git a/src/apps/v1/gacha/gacha_schema.rs b/src/apps/v1/gacha/gacha_schema.rs index e8b24f5..4b1d0f6 100644 --- a/src/apps/v1/gacha/gacha_schema.rs +++ b/src/apps/v1/gacha/gacha_schema.rs @@ -2,11 +2,17 @@ use serde::{Deserialize, Serialize}; use surrealdb::sql::Thing; #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct GachaSchema { +pub struct GachaClaimSchema { pub transaction_number: String, pub user: Thing, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GachaRollSchema { + pub weight: String, + pub item: Thing, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GachaItemSchema { pub item_image: String, diff --git a/src/apps/v1/gacha/gacha_service.rs b/src/apps/v1/gacha/gacha_service.rs index 3edfbdf..f993d43 100644 --- a/src/apps/v1/gacha/gacha_service.rs +++ b/src/apps/v1/gacha/gacha_service.rs @@ -1,4 +1,7 @@ -use super::{GachaClaimRequestDto, GachaCreateItemRequestDto, GachaRepository}; +use super::{ + GachaCreateClaimRequestDto, GachaCreateItemRequestDto, GachaCreateRollRequestDto, + GachaRepository, +}; use crate::{common_response, extract_email, AppState}; use axum::{ http::{HeaderMap, StatusCode}, @@ -8,8 +11,8 @@ use axum::{ pub struct GachaService; impl GachaService { - pub async fn mutation_create_gacha_claims( - payload: GachaClaimRequestDto, + pub async fn mutation_create_gacha_claim( + payload: GachaCreateClaimRequestDto, state: &AppState, header: HeaderMap, ) -> Response { @@ -25,7 +28,7 @@ impl GachaService { } }; - match repository.query_create_gacha_claims(payload, email).await { + match repository.query_create_gacha_claim(payload, email).await { Ok(msg) => common_response(StatusCode::CREATED, &msg), Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()), } @@ -42,4 +45,16 @@ impl GachaService { Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()), } } + + pub async fn mutation_create_gacha_roll( + payload: GachaCreateRollRequestDto, + state: &AppState, + ) -> Response { + let repository = GachaRepository::new(state); + + match repository.query_create_gacha_roll(payload).await { + Ok(msg) => common_response(StatusCode::CREATED, &msg), + Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()), + } + } } diff --git a/src/apps/v1/gacha/mod.rs b/src/apps/v1/gacha/mod.rs index 7b08ef7..58eb213 100644 --- a/src/apps/v1/gacha/mod.rs +++ b/src/apps/v1/gacha/mod.rs @@ -14,11 +14,15 @@ pub use gacha_service::*; pub fn gacha_router() -> Router { Router::new() .route( - "/create/claims", - post(gacha_controller::post_create_gacha_claims), + "/create/claim", + post(gacha_controller::post_create_gacha_claim), ) .route( "/create/item", post(gacha_controller::post_create_gacha_item), ) + .route( + "/create/roll", + post(gacha_controller::post_create_gacha_roll), + ) } diff --git a/src/apps/v1/users/users_dto.rs b/src/apps/v1/users/users_dto.rs index 3828896..9badfb9 100644 --- a/src/apps/v1/users/users_dto.rs +++ b/src/apps/v1/users/users_dto.rs @@ -5,4 +5,5 @@ use utoipa::ToSchema; pub struct UsersItemDto { pub email: String, pub fullname: String, + pub is_active: bool, } diff --git a/src/apps/v1/users/users_schema.rs b/src/apps/v1/users/users_schema.rs index 6d0907e..35f34e5 100644 --- a/src/apps/v1/users/users_schema.rs +++ b/src/apps/v1/users/users_schema.rs @@ -6,4 +6,5 @@ pub struct UsersSchema { pub email: String, pub fullname: String, pub password: String, + pub is_active: bool, } diff --git a/src/entities/error_dto.rs b/src/entities/error_dto.rs index a50bcf5..aedc52a 100644 --- a/src/entities/error_dto.rs +++ b/src/entities/error_dto.rs @@ -13,8 +13,7 @@ pub mod error { impl IntoResponse for Error { fn into_response(self) -> Response { - (StatusCode::INTERNAL_SERVER_ERROR, Json(self.to_string())) - .into_response() + (StatusCode::INTERNAL_SERVER_ERROR, Json(self.to_string())).into_response() } } diff --git a/src/libs/enviroment/mod.rs b/src/libs/enviroment/mod.rs index 9b60fc7..5d80d46 100644 --- a/src/libs/enviroment/mod.rs +++ b/src/libs/enviroment/mod.rs @@ -47,16 +47,13 @@ impl Env { .unwrap_or_else(|_| "no-reply@example.com".to_string()), smtp_password: env::var("SMTP_PASSWORD") .unwrap_or_else(|_| "default_smtp_password".to_string()), - smtp_name: env::var("SMTP_NAME") - .unwrap_or_else(|_| "MyApp SMTP".to_string()), + smtp_name: env::var("SMTP_NAME").unwrap_or_else(|_| "MyApp SMTP".to_string()), smtp_host: env::var("SMTP_HOST") .unwrap_or_else(|_| "smtp.gmail.com".to_string()), redisdb_url: env::var("REDISDB_URL") .unwrap_or_else(|_| "localhost".to_string()), - fe_url: env::var("FE_URL") - .unwrap_or_else(|_| "http://localhost".to_string()), - rust_env: env::var("RUST_ENV") - .unwrap_or_else(|_| "development".to_string()), + fe_url: env::var("FE_URL").unwrap_or_else(|_| "http://localhost".to_string()), + rust_env: env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()), minio_endpoint: env::var("MINIO_ENDPOINT") .unwrap_or_else(|_| "http://localhost:9000".to_string()), minio_bucket_name: env::var("MINIO_BUCKET_NAME") diff --git a/src/libs/surrealdb/resource.rs b/src/libs/surrealdb/resource.rs index 3067369..7666b28 100644 --- a/src/libs/surrealdb/resource.rs +++ b/src/libs/surrealdb/resource.rs @@ -2,11 +2,13 @@ use std::fmt; #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResourceEnum { - Gacha, + GachaItems, GachaClaims, + GachaRolls, Users, Roles, Permissions, + RolesPermissions, } impl fmt::Display for ResourceEnum { @@ -15,8 +17,10 @@ impl fmt::Display for ResourceEnum { ResourceEnum::Users => "app_users", ResourceEnum::Roles => "app_roles", ResourceEnum::Permissions => "app_permissions", - ResourceEnum::Gacha => "app_gacha", + ResourceEnum::RolesPermissions => "app_roles_permissions", + ResourceEnum::GachaItems => "app_gacha_items", ResourceEnum::GachaClaims => "app_gacha_claims", + ResourceEnum::GachaRolls => "app_gacha_rolls", }; write!(f, "{}", str) }