feat: add gacha

This commit is contained in:
Maulana Sodiqin
2025-04-01 20:00:00 +07:00
parent 4b8da68df4
commit 8d2f720de1
15 changed files with 377 additions and 120 deletions
+1 -1
View File
@@ -120,7 +120,7 @@ impl AuthService {
let auth_repo = AuthRepository::new(state);
let role_repo = RolesRepository::new(state);
let role = match role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
{
Ok(role) => role,
+91
View File
@@ -0,0 +1,91 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;
use utoipa::ToSchema;
use validator::Validate;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaItemRequestDto {
#[validate(length(min = 1, message = "Item name must not be empty"))]
pub name: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaClaimRequestDto {
#[validate(length(min = 1, message = "User ID must not be empty"))]
pub user_id: String,
#[validate(length(min = 1, message = "Item ID must not be empty"))]
pub item_id: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
pub struct GachaRollRequestDto {
#[validate(length(min = 1, message = "Item ID must not be empty"))]
pub item_id: String,
#[validate(range(min = 1, message = "Weight must be greater than zero"))]
pub weight: f32,
#[validate(range(min = 1, message = "Quantity must be at least 1"))]
pub quantity: i32,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaItemDto {
pub id: String,
pub name: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaItemDtoRaw {
pub id: Thing,
pub name: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaClaimDto {
pub id: String,
pub user: String,
pub item: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaClaimDtoRaw {
pub id: Thing,
pub user: Thing,
pub item: Thing,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaRollDto {
pub id: String,
pub item: String,
pub weight: String,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaRollDtoRaw {
pub id: Thing,
pub item: Thing,
pub weight: String,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
+155
View File
@@ -0,0 +1,155 @@
use super::{GachaClaimSchema, GachaItemSchema, GachaRollSchema};
use crate::{
get_id, make_thing, query_list_with_meta, AppState, MetaRequestDto, ResourceEnum,
ResponseListSuccessDto,
};
use anyhow::{bail, Result};
pub struct GachaRepository<'a> {
state: &'a AppState,
}
impl<'a> GachaRepository<'a> {
pub fn new(state: &'a AppState) -> Self {
Self { state }
}
pub async fn query_gacha_item_list(
&self,
meta: MetaRequestDto,
) -> Result<ResponseListSuccessDto<Vec<GachaItemSchema>>> {
let mut conditions = vec!["is_deleted = false".into()];
if meta.search.is_some() {
conditions.push("string::contains(name, $search)".into());
}
query_list_with_meta(
&self.state.surrealdb_ws,
&ResourceEnum::GachaItems.to_string(),
&meta,
conditions,
None,
)
.await
}
pub async fn query_gacha_item_by_id(&self, id: String) -> Result<GachaItemSchema> {
let db = &self.state.surrealdb_ws;
let result: Option<GachaItemSchema> = db
.select((ResourceEnum::GachaItems.to_string(), id.clone()))
.await?;
match result {
Some(item) if !item.is_deleted => Ok(item),
_ => bail!("Gacha Item not found"),
}
}
pub async fn query_create_gacha_item(
&self,
data: GachaItemSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<GachaItemSchema> = db
.create(ResourceEnum::GachaItems.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create Gacha Item".into()),
None => bail!("Failed to create Gacha Item"),
}
}
pub async fn query_update_gacha_item(
&self,
data: GachaItemSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record_key = get_id(&data.id)?;
let existing = self.query_gacha_item_by_id(data.id.id.to_raw()).await?;
if existing.is_deleted {
bail!("Gacha Item already deleted");
}
let merged = GachaItemSchema {
created_at: existing.created_at,
..data.clone()
};
let record: Option<GachaItemSchema> =
db.update(record_key).merge(merged).await?;
match record {
Some(_) => Ok("Success update Gacha Item".into()),
None => bail!("Failed to update Gacha Item"),
}
}
pub async fn query_delete_gacha_item(&self, id: String) -> Result<String> {
let db = &self.state.surrealdb_ws;
let item_id = make_thing(&ResourceEnum::GachaItems.to_string(), &id);
let item = self.query_gacha_item_by_id(item_id.id.to_raw()).await?;
if item.is_deleted {
bail!("Gacha Item already deleted");
}
let record_key = get_id(&item.id)?;
let record: Option<GachaItemSchema> = db
.update(record_key)
.merge(serde_json::json!({ "is_deleted": true }))
.await?;
match record {
Some(_) => Ok("Success delete Gacha Item".into()),
None => bail!("Failed to delete Gacha Item"),
}
}
pub async fn query_gacha_claim_by_id(
&self,
id: String,
) -> Result<GachaClaimSchema> {
let db = &self.state.surrealdb_ws;
let result: Option<GachaClaimSchema> = db
.select((ResourceEnum::GachaClaims.to_string(), id.clone()))
.await?;
match result {
Some(claim) if !claim.is_deleted => Ok(claim),
_ => bail!("Gacha Claim not found"),
}
}
pub async fn query_create_gacha_claim(
&self,
data: GachaClaimSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<GachaClaimSchema> = db
.create(ResourceEnum::GachaClaims.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create Gacha Claim".into()),
None => bail!("Failed to create Gacha Claim"),
}
}
pub async fn query_gacha_roll_by_id(&self, id: String) -> Result<GachaRollSchema> {
let db = &self.state.surrealdb_ws;
let result: Option<GachaRollSchema> = db
.select((ResourceEnum::GachaRolls.to_string(), id.clone()))
.await?;
match result {
Some(roll) if !roll.is_deleted => Ok(roll),
_ => bail!("Gacha Roll not found"),
}
}
pub async fn query_create_gacha_roll(
&self,
data: GachaRollSchema,
) -> Result<String> {
let db = &self.state.surrealdb_ws;
let record: Option<GachaRollSchema> = db
.create(ResourceEnum::GachaRolls.to_string())
.content(data)
.await?;
match record {
Some(_) => Ok("Success create Gacha Roll".into()),
None => bail!("Failed to create Gacha Roll"),
}
}
}
+90
View File
@@ -0,0 +1,90 @@
use crate::{make_thing, ResourceEnum};
use serde::{Deserialize, Serialize};
use surrealdb::{sql::Thing, Uuid};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaItemSchema {
pub id: Thing,
pub name: String,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaItemSchema {
fn default() -> Self {
GachaItemSchema {
id: make_thing(
&ResourceEnum::GachaItems.to_string(),
&Uuid::new_v4().to_string(),
),
name: String::new(),
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaClaimSchema {
pub id: Thing,
pub user: Thing,
pub item: Thing,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaClaimSchema {
fn default() -> Self {
GachaClaimSchema {
id: make_thing(
&ResourceEnum::GachaClaims.to_string(),
&Uuid::new_v4().to_string(),
),
user: make_thing(
&ResourceEnum::Users.to_string(),
&Uuid::new_v4().to_string(),
),
item: make_thing(
&ResourceEnum::GachaItems.to_string(),
&Uuid::new_v4().to_string(),
),
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaRollSchema {
pub id: Thing,
pub item: Thing,
pub weight: f32,
pub quantity: i32,
pub is_deleted: bool,
pub created_at: Option<String>,
pub updated_at: Option<String>,
}
impl Default for GachaRollSchema {
fn default() -> Self {
GachaRollSchema {
id: make_thing(
&ResourceEnum::GachaClaims.to_string(),
&Uuid::new_v4().to_string(),
),
item: make_thing(
&ResourceEnum::GachaRolls.to_string(),
&Uuid::new_v4().to_string(),
),
weight: 0.2,
quantity: 2,
is_deleted: false,
created_at: None,
updated_at: None,
}
}
}
+6
View File
@@ -0,0 +1,6 @@
pub mod gacha_dto;
pub mod gacha_repository;
pub mod gacha_schema;
pub use gacha_repository::*;
pub use gacha_schema::*;
+1
View File
@@ -1,6 +1,7 @@
use axum::{middleware::from_fn, Router};
pub mod auth;
pub mod docs;
pub mod gacha;
pub mod permissions;
pub mod roles;
pub mod users;
@@ -17,16 +17,6 @@ pub enum PermissionsEnum {
CreatePermissions,
DeletePermissions,
UpdatePermissions,
ReadListSessions,
ReadDetailSessions,
CreateSessions,
UpdateSessions,
DeleteSessions,
ReadListTests,
ReadDetailTests,
CreateTests,
UpdateTests,
DeleteTests,
}
impl fmt::Display for PermissionsEnum {
@@ -47,16 +37,6 @@ impl fmt::Display for PermissionsEnum {
PermissionsEnum::CreatePermissions => "Create Permissions",
PermissionsEnum::DeletePermissions => "Delete Permissions",
PermissionsEnum::UpdatePermissions => "Update Permissions",
PermissionsEnum::ReadListSessions => "Read List Sessions",
PermissionsEnum::ReadDetailSessions => "Read Detail Sessions",
PermissionsEnum::CreateSessions => "Create Sessions",
PermissionsEnum::UpdateSessions => "Update Sessions",
PermissionsEnum::DeleteSessions => "Delete Sessions",
PermissionsEnum::ReadListTests => "Read List Tests",
PermissionsEnum::ReadDetailTests => "Read Detail Tests",
PermissionsEnum::CreateTests => "Create Tests",
PermissionsEnum::UpdateTests => "Update Tests",
PermissionsEnum::DeleteTests => "Delete Tests",
};
write!(f, "{}", permission_str)
}
@@ -1,9 +1,6 @@
use crate::ResourceEnum;
use crate::{make_thing, ResourceEnum};
use serde::{Deserialize, Serialize};
use surrealdb::{
sql::{Id, Thing},
Uuid,
};
use surrealdb::{sql::Thing, Uuid};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PermissionsSchema {
@@ -17,10 +14,10 @@ pub struct PermissionsSchema {
impl Default for PermissionsSchema {
fn default() -> Self {
PermissionsSchema {
id: Thing::from((
ResourceEnum::Permissions.to_string(),
Id::String(Uuid::new_v4().to_string()),
)),
id: make_thing(
&ResourceEnum::Permissions.to_string(),
&Uuid::new_v4().to_string(),
),
name: String::new(),
is_deleted: false,
created_at: None,
+2 -2
View File
@@ -3,7 +3,7 @@ use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RolesEnum {
Admin,
Student,
User,
Staf,
}
@@ -11,7 +11,7 @@ impl fmt::Display for RolesEnum {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let roles_str = match self {
RolesEnum::Admin => "Admin",
RolesEnum::Student => "Student",
RolesEnum::User => "User",
RolesEnum::Staf => "Staf",
};
write!(f, "{}", roles_str)
+10 -13
View File
@@ -1,10 +1,7 @@
use serde::{Deserialize, Serialize};
use surrealdb::{
sql::{Id, Thing},
Uuid,
};
use surrealdb::{sql::Thing, Uuid};
use crate::ResourceEnum;
use crate::{make_thing, ResourceEnum};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RolesSchema {
@@ -19,14 +16,14 @@ pub struct RolesSchema {
impl Default for RolesSchema {
fn default() -> Self {
RolesSchema {
id: Thing::from((
ResourceEnum::Roles.to_string(),
Id::String(Uuid::new_v4().to_string()),
)),
permissions: vec![Thing::from((
ResourceEnum::Permissions.to_string(),
Id::String(Uuid::new_v4().to_string()),
))],
id: make_thing(
&ResourceEnum::Roles.to_string(),
&Uuid::new_v4().to_string(),
),
permissions: vec![make_thing(
&ResourceEnum::Permissions.to_string(),
&Uuid::new_v4().to_string(),
)],
name: String::new(),
is_deleted: false,
created_at: None,
+11 -11
View File
@@ -106,7 +106,7 @@ async fn test_create_user_should_return_201() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id;
@@ -131,7 +131,7 @@ async fn test_get_user_detail_should_return_200() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id;
@@ -160,7 +160,7 @@ async fn test_delete_user_should_return_200() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.expect("Failed to get role");
let repo = UsersRepository::new(&state);
@@ -193,7 +193,7 @@ async fn test_activate_user_should_return_200() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id;
@@ -228,7 +228,7 @@ async fn test_update_user_should_return_200() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id.clone();
@@ -274,7 +274,7 @@ async fn test_create_user_should_fail_if_email_taken() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id.clone();
@@ -317,7 +317,7 @@ async fn test_delete_user_should_fail_if_already_deleted() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id.clone();
@@ -350,7 +350,7 @@ async fn test_update_user_should_fail_if_user_not_found() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id.clone();
@@ -481,7 +481,7 @@ async fn test_user_detail_should_fail_if_user_is_soft_deleted() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id.clone();
@@ -514,7 +514,7 @@ async fn test_update_user_should_fail_if_user_is_deleted() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id.clone();
@@ -559,7 +559,7 @@ async fn test_update_user_should_fail_if_payload_invalid() {
let state = create_mock_app_state().await;
let role_repo = RolesRepository::new(&state);
let role = role_repo
.query_role_by_name(RolesEnum::Student.to_string())
.query_role_by_name(RolesEnum::User.to_string())
.await
.unwrap();
let role_id = role.id.clone();