feat: gacha roll, user auth

This commit is contained in:
Maulana Sodiqin
2025-03-18 14:13:53 +07:00
parent 0c28d34d57
commit 12043f027c
17 changed files with 174 additions and 86 deletions
+1
View File
@@ -1,3 +1,4 @@
hard_tabs = true hard_tabs = true
edition = "2021" edition = "2021"
max_width = 85 max_width = 85
tab_spaces = 2
+8 -1
View File
@@ -29,8 +29,15 @@ pub struct AuthRegisterRequestDto {
} }
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] #[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 email: String,
pub fullname: String, pub fullname: String,
pub password: String, pub password: String,
pub is_active: bool,
} }
+13 -13
View File
@@ -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::{ use axum::{
extract::Request, http::StatusCode, middleware::Next, response::Response, extract::Request, http::StatusCode, middleware::Next, response::Response,
Extension, Extension,
}; };
use std::convert::Infallible; use std::convert::Infallible;
use super::{AuthQueryByEmailResponse, AuthRepository};
pub async fn auth_middleware( pub async fn auth_middleware(
Extension(state): Extension<AppState>, Extension(state): Extension<AppState>,
mut req: Request, mut req: Request,
@@ -26,16 +27,15 @@ pub async fn auth_middleware(
let repository = AuthRepository::new(&state); let repository = AuthRepository::new(&state);
let user: Option<AuthQueryByEmailResponse> = let user: Option<UsersSchema> = match repository.query_user_by_email(email).await {
match repository.query_user_by_email(email).await { Ok(user) => Some(user),
Ok(user) => Some(user), Err(err) => {
Err(err) => { return Ok(common_response(
return Ok(common_response( StatusCode::INTERNAL_SERVER_ERROR,
StatusCode::INTERNAL_SERVER_ERROR, &err.to_string(),
&format!("DB error: {}", err), ))
)) }
} };
};
if user.is_none() { if user.is_none() {
return Ok(common_response( return Ok(common_response(
+22 -14
View File
@@ -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 anyhow::{bail, Result};
use redis::Commands; use redis::Commands;
use super::{AuthQueryByEmailResponse, AuthRegisterRequestDto};
pub struct AuthRepository<'a> { pub struct AuthRepository<'a> {
state: &'a AppState, state: &'a AppState,
} }
@@ -33,26 +35,19 @@ impl<'a> AuthRepository<'a> {
pub fn query_get_stored_user(&self, email: String) -> Result<UsersItemDto> { pub fn query_get_stored_user(&self, email: String) -> Result<UsersItemDto> {
let redis_key = format!("{}:{}", RedisKeyEnum::User, email); let redis_key = format!("{}:{}", RedisKeyEnum::User, email);
let mut conn = self.state.redisdb.get_connection()?; let mut conn = self.state.redisdb.get_connection()?;
let data: Option<String> = conn.get(&redis_key)?; let data: Option<String> = conn.get(&redis_key)?;
match data { match data {
Some(user_json) => { Some(user_json) => {
let user: UsersItemDto = serde_json::from_str(&user_json)?; let user: UsersItemDto = serde_json::from_str(&user_json)?;
Ok(user) 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( pub async fn query_user_by_email(&self, email: String) -> Result<UsersSchema> {
&self,
email: String,
) -> Result<AuthQueryByEmailResponse> {
let db = &self.state.surrealdb; let db = &self.state.surrealdb;
let result = db.select((ResourceEnum::Users.to_string(), email)).await?; let result = db.select((ResourceEnum::Users.to_string(), email)).await?;
match result { match result {
Some(response) => Ok(response), Some(response) => Ok(response),
None => bail!("User not found"), None => bail!("User not found"),
@@ -64,15 +59,28 @@ impl<'a> AuthRepository<'a> {
data: AuthRegisterRequestDto, data: AuthRegisterRequestDto,
) -> Result<String> { ) -> Result<String> {
let db = &self.state.surrealdb; let db = &self.state.surrealdb;
let record: Option<UsersItemDto> = db let record: Option<UsersItemDto> = db
.create((ResourceEnum::Users.to_string(), &data.email)) .create((ResourceEnum::Users.to_string(), &data.email))
.content(data) .content(data)
.await?; .await?;
match record { match record {
Some(_) => Ok("Success create user".into()), Some(_) => Ok("Success create user".into()),
None => bail!("Failed to create user"), None => bail!("Failed to create user"),
} }
} }
pub async fn query_active_inactive_user(
&self,
data: AuthActiveInactiveRequestDto,
) -> Result<String> {
let db = &self.state.surrealdb;
let record: Option<UsersItemDto> = 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"),
}
}
} }
+5 -12
View File
@@ -6,8 +6,7 @@ use super::{
}; };
use crate::{ use crate::{
common_response, encode_access_token, encode_refresh_token, hash_password, common_response, encode_access_token, encode_refresh_token, hash_password,
success_response, v1::UsersItemDto, verify_password, AppState, success_response, v1::UsersItemDto, verify_password, AppState, ResponseSuccessDto,
ResponseSuccessDto,
}; };
pub struct AuthService; pub struct AuthService;
@@ -21,14 +20,10 @@ impl AuthService {
match repository.query_user_by_email(payload.email.clone()).await { match repository.query_user_by_email(payload.email.clone()).await {
Ok(user) => { Ok(user) => {
let is_password_correct = let is_password_correct =
verify_password(&payload.password, &user.password) verify_password(&payload.password, &user.password).unwrap_or(false);
.unwrap_or(false);
if is_password_correct { if is_password_correct {
common_response( common_response(StatusCode::BAD_REQUEST, "Email or password not correct");
StatusCode::BAD_REQUEST,
"Email or password not correct",
);
} }
let access_token = encode_access_token(payload.email.clone()); let access_token = encode_access_token(payload.email.clone());
@@ -39,6 +34,7 @@ impl AuthService {
user: UsersItemDto { user: UsersItemDto {
fullname: user.fullname.clone(), fullname: user.fullname.clone(),
email: user.email.clone(), email: user.email.clone(),
is_active: user.is_active.clone(),
}, },
token: TokenDto { token: TokenDto {
access_token: access_token.unwrap(), access_token: access_token.unwrap(),
@@ -55,10 +51,7 @@ impl AuthService {
}) })
.is_ok() .is_ok()
{ {
return common_response( return common_response(StatusCode::BAD_REQUEST, "Failed to store data");
StatusCode::BAD_REQUEST,
"Failed to store data",
);
} }
success_response(response) success_response(response)
+12 -3
View File
@@ -1,5 +1,9 @@
use crate::{ use crate::{
v1::{auth, AuthLoginRequestDto, AuthLoginResponsetDto}, v1::{
auth, gacha, AuthLoginRequestDto, AuthLoginResponsetDto,
GachaCreateClaimRequestDto, GachaCreateItemRequestDto,
GachaCreateRollRequestDto,
},
MessageResponseDto, MetaRequestDto, MetaResponseDto, ResponseSuccessDto, MessageResponseDto, MetaRequestDto, MetaResponseDto, ResponseSuccessDto,
}; };
@@ -12,17 +16,22 @@ use utoipa::{
#[openapi( #[openapi(
paths( paths(
auth::auth_controller::post_login, 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( components(
schemas( schemas(
MetaRequestDto, MetaRequestDto,
MetaResponseDto, MetaResponseDto,
MessageResponseDto, MessageResponseDto,
AuthLoginRequestDto, AuthLoginRequestDto,
AuthLoginResponsetDto, AuthLoginResponsetDto,
ResponseSuccessDto<AuthLoginResponsetDto>, ResponseSuccessDto<AuthLoginResponsetDto>,
GachaCreateClaimRequestDto,
GachaCreateItemRequestDto,
GachaCreateRollRequestDto
) )
), ),
info( info(
+22 -5
View File
@@ -1,23 +1,23 @@
use super::{GachaClaimRequestDto, GachaService}; use super::{GachaCreateClaimRequestDto, GachaCreateRollRequestDto, GachaService};
use crate::{v1::GachaCreateItemRequestDto, AppState, MessageResponseDto}; use crate::{v1::GachaCreateItemRequestDto, AppState, MessageResponseDto};
use axum::{http::HeaderMap, response::IntoResponse, Extension, Json}; use axum::{http::HeaderMap, response::IntoResponse, Extension, Json};
#[utoipa::path( #[utoipa::path(
post, post,
path = "/v1/gacha/create/claims", path = "/v1/gacha/create/claims",
request_body = GachaClaimRequestDto, request_body = GachaCreateClaimRequestDto,
responses( responses(
(status = 200, description = "Create gacha claims successful", body = MessageResponseDto), (status = 200, description = "Create gacha claims successful", body = MessageResponseDto),
(status = 401, description = "Create gacha claims failed", body = MessageResponseDto) (status = 401, description = "Create gacha claims failed", body = MessageResponseDto)
), ),
tag = "Gacha" tag = "Gacha"
)] )]
pub async fn post_create_gacha_claims( pub async fn post_create_gacha_claim(
header: HeaderMap, header: HeaderMap,
Extension(state): Extension<AppState>, Extension(state): Extension<AppState>,
Json(payload): Json<GachaClaimRequestDto>, Json(payload): Json<GachaCreateClaimRequestDto>,
) -> impl IntoResponse { ) -> impl IntoResponse {
GachaService::mutation_create_gacha_claims(payload, &state, header).await GachaService::mutation_create_gacha_claim(payload, &state, header).await
} }
#[utoipa::path( #[utoipa::path(
@@ -36,3 +36,20 @@ pub async fn post_create_gacha_item(
) -> impl IntoResponse { ) -> impl IntoResponse {
GachaService::mutation_create_gacha_item(payload, &state).await 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<AppState>,
Json(payload): Json<GachaCreateRollRequestDto>,
) -> impl IntoResponse {
GachaService::mutation_create_gacha_roll(payload, &state).await
}
+7 -2
View File
@@ -4,7 +4,7 @@ use utoipa::ToSchema;
use crate::v1::UsersItemDto; use crate::v1::UsersItemDto;
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaClaimRequestDto { pub struct GachaCreateClaimRequestDto {
pub transaction_number: String, pub transaction_number: String,
} }
@@ -16,7 +16,7 @@ pub struct GachaCreateItemRequestDto {
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaCreateRollRequestDto { pub struct GachaCreateRollRequestDto {
pub item_id: String, pub item_name: String,
pub weight: String, pub weight: String,
} }
@@ -31,3 +31,8 @@ pub struct GachaClaimResponseDto {
pub transaction_number: String, pub transaction_number: String,
pub user: UsersItemDto, pub user: UsersItemDto,
} }
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaRollResponseDto {
pub item: GachaItemResponseDto,
}
+40 -19
View File
@@ -1,6 +1,7 @@
use super::{ use super::{
GachaClaimRequestDto, GachaClaimResponseDto, GachaCreateItemRequestDto, GachaClaimResponseDto, GachaClaimSchema, GachaCreateClaimRequestDto,
GachaItemSchema, GachaSchema, GachaCreateItemRequestDto, GachaCreateRollRequestDto, GachaItemResponseDto,
GachaItemSchema, GachaRollSchema,
}; };
use crate::{v1::AuthRepository, AppState, ResourceEnum}; use crate::{v1::AuthRepository, AppState, ResourceEnum};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
@@ -15,25 +16,41 @@ impl<'a> GachaRepository<'a> {
Self { state } Self { state }
} }
pub async fn query_gacha_by_transaction_number( pub async fn query_gacha_claim_by_transaction_number(
&self, &self,
transaction_number: String, transaction_number: String,
) -> Result<GachaClaimResponseDto> { ) -> Result<GachaClaimResponseDto> {
let db = &self.state.surrealdb; let db = &self.state.surrealdb;
let result = db let result = db
.select((ResourceEnum::Gacha.to_string(), transaction_number)) .select((ResourceEnum::GachaClaims.to_string(), transaction_number))
.await?; .await?;
match result { match result {
Some(response) => Ok(response), 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, &self,
data: GachaClaimRequestDto, name: String,
) -> Result<GachaItemResponseDto> {
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, email: String,
) -> Result<String> { ) -> Result<String> {
let auth_repository = AuthRepository::new(self.state); let auth_repository = AuthRepository::new(self.state);
@@ -44,12 +61,12 @@ impl<'a> GachaRepository<'a> {
let user_thing = let user_thing =
Thing::from((ResourceEnum::Users.to_string(), Id::String(user.email))); Thing::from((ResourceEnum::Users.to_string(), Id::String(user.email)));
let record: Option<GachaSchema> = db let record: Option<GachaClaimSchema> = db
.create(( .create((
ResourceEnum::GachaClaims.to_string(), ResourceEnum::GachaClaims.to_string(),
&data.transaction_number, &data.transaction_number,
)) ))
.content(GachaSchema { .content(GachaClaimSchema {
transaction_number: data.transaction_number.clone(), transaction_number: data.transaction_number.clone(),
user: user_thing, user: user_thing,
}) })
@@ -68,7 +85,7 @@ impl<'a> GachaRepository<'a> {
let db = &self.state.surrealdb; let db = &self.state.surrealdb;
let record: Option<GachaItemSchema> = db let record: Option<GachaItemSchema> = db
.create((ResourceEnum::Gacha.to_string(), data.item_name.clone())) .create((ResourceEnum::GachaItems.to_string(), data.item_name.clone()))
.content(GachaItemSchema { .content(GachaItemSchema {
item_name: data.item_name.clone(), item_name: data.item_name.clone(),
item_image: data.item_image.clone(), item_image: data.item_image.clone(),
@@ -77,27 +94,31 @@ impl<'a> GachaRepository<'a> {
match record { match record {
Some(_) => Ok("Gacha item successfully created".to_string()), 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( pub async fn query_create_gacha_roll(
&self, &self,
data: GachaCreateItemRequestDto, data: GachaCreateRollRequestDto,
) -> Result<String> { ) -> Result<String> {
let db = &self.state.surrealdb; let db = &self.state.surrealdb;
let item_thing = Thing::from((
ResourceEnum::GachaItems.to_string(),
Id::String(data.item_name.clone()),
));
let record: Option<GachaItemSchema> = db let record: Option<GachaRollSchema> = db
.create((ResourceEnum::Gacha.to_string(), data.item_name.clone())) .create((ResourceEnum::GachaRolls.to_string(), data.item_name.clone()))
.content(GachaItemSchema { .content(GachaRollSchema {
item_name: data.item_name.clone(), weight: data.weight.clone(),
item_image: data.item_image.clone(), item: item_thing,
}) })
.await?; .await?;
match record { match record {
Some(_) => Ok("Gacha item successfully created".to_string()), Some(_) => Ok("Gacha roll successfully created".to_string()),
None => bail!("Failed to create gacha item record"), None => bail!("Failed to create gacha roll"),
} }
} }
} }
+7 -1
View File
@@ -2,11 +2,17 @@ use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing; use surrealdb::sql::Thing;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaSchema { pub struct GachaClaimSchema {
pub transaction_number: String, pub transaction_number: String,
pub user: Thing, pub user: Thing,
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaRollSchema {
pub weight: String,
pub item: Thing,
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GachaItemSchema { pub struct GachaItemSchema {
pub item_image: String, pub item_image: String,
+19 -4
View File
@@ -1,4 +1,7 @@
use super::{GachaClaimRequestDto, GachaCreateItemRequestDto, GachaRepository}; use super::{
GachaCreateClaimRequestDto, GachaCreateItemRequestDto, GachaCreateRollRequestDto,
GachaRepository,
};
use crate::{common_response, extract_email, AppState}; use crate::{common_response, extract_email, AppState};
use axum::{ use axum::{
http::{HeaderMap, StatusCode}, http::{HeaderMap, StatusCode},
@@ -8,8 +11,8 @@ use axum::{
pub struct GachaService; pub struct GachaService;
impl GachaService { impl GachaService {
pub async fn mutation_create_gacha_claims( pub async fn mutation_create_gacha_claim(
payload: GachaClaimRequestDto, payload: GachaCreateClaimRequestDto,
state: &AppState, state: &AppState,
header: HeaderMap, header: HeaderMap,
) -> Response { ) -> 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), Ok(msg) => common_response(StatusCode::CREATED, &msg),
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()), 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()), 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()),
}
}
} }
+6 -2
View File
@@ -14,11 +14,15 @@ pub use gacha_service::*;
pub fn gacha_router() -> Router { pub fn gacha_router() -> Router {
Router::new() Router::new()
.route( .route(
"/create/claims", "/create/claim",
post(gacha_controller::post_create_gacha_claims), post(gacha_controller::post_create_gacha_claim),
) )
.route( .route(
"/create/item", "/create/item",
post(gacha_controller::post_create_gacha_item), post(gacha_controller::post_create_gacha_item),
) )
.route(
"/create/roll",
post(gacha_controller::post_create_gacha_roll),
)
} }
+1
View File
@@ -5,4 +5,5 @@ use utoipa::ToSchema;
pub struct UsersItemDto { pub struct UsersItemDto {
pub email: String, pub email: String,
pub fullname: String, pub fullname: String,
pub is_active: bool,
} }
+1
View File
@@ -6,4 +6,5 @@ pub struct UsersSchema {
pub email: String, pub email: String,
pub fullname: String, pub fullname: String,
pub password: String, pub password: String,
pub is_active: bool,
} }
+1 -2
View File
@@ -13,8 +13,7 @@ pub mod error {
impl IntoResponse for Error { impl IntoResponse for Error {
fn into_response(self) -> Response { fn into_response(self) -> Response {
(StatusCode::INTERNAL_SERVER_ERROR, Json(self.to_string())) (StatusCode::INTERNAL_SERVER_ERROR, Json(self.to_string())).into_response()
.into_response()
} }
} }
+3 -6
View File
@@ -47,16 +47,13 @@ impl Env {
.unwrap_or_else(|_| "no-reply@example.com".to_string()), .unwrap_or_else(|_| "no-reply@example.com".to_string()),
smtp_password: env::var("SMTP_PASSWORD") smtp_password: env::var("SMTP_PASSWORD")
.unwrap_or_else(|_| "default_smtp_password".to_string()), .unwrap_or_else(|_| "default_smtp_password".to_string()),
smtp_name: env::var("SMTP_NAME") smtp_name: env::var("SMTP_NAME").unwrap_or_else(|_| "MyApp SMTP".to_string()),
.unwrap_or_else(|_| "MyApp SMTP".to_string()),
smtp_host: env::var("SMTP_HOST") smtp_host: env::var("SMTP_HOST")
.unwrap_or_else(|_| "smtp.gmail.com".to_string()), .unwrap_or_else(|_| "smtp.gmail.com".to_string()),
redisdb_url: env::var("REDISDB_URL") redisdb_url: env::var("REDISDB_URL")
.unwrap_or_else(|_| "localhost".to_string()), .unwrap_or_else(|_| "localhost".to_string()),
fe_url: env::var("FE_URL") fe_url: env::var("FE_URL").unwrap_or_else(|_| "http://localhost".to_string()),
.unwrap_or_else(|_| "http://localhost".to_string()), rust_env: env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()),
rust_env: env::var("RUST_ENV")
.unwrap_or_else(|_| "development".to_string()),
minio_endpoint: env::var("MINIO_ENDPOINT") minio_endpoint: env::var("MINIO_ENDPOINT")
.unwrap_or_else(|_| "http://localhost:9000".to_string()), .unwrap_or_else(|_| "http://localhost:9000".to_string()),
minio_bucket_name: env::var("MINIO_BUCKET_NAME") minio_bucket_name: env::var("MINIO_BUCKET_NAME")
+6 -2
View File
@@ -2,11 +2,13 @@ use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResourceEnum { pub enum ResourceEnum {
Gacha, GachaItems,
GachaClaims, GachaClaims,
GachaRolls,
Users, Users,
Roles, Roles,
Permissions, Permissions,
RolesPermissions,
} }
impl fmt::Display for ResourceEnum { impl fmt::Display for ResourceEnum {
@@ -15,8 +17,10 @@ impl fmt::Display for ResourceEnum {
ResourceEnum::Users => "app_users", ResourceEnum::Users => "app_users",
ResourceEnum::Roles => "app_roles", ResourceEnum::Roles => "app_roles",
ResourceEnum::Permissions => "app_permissions", ResourceEnum::Permissions => "app_permissions",
ResourceEnum::Gacha => "app_gacha", ResourceEnum::RolesPermissions => "app_roles_permissions",
ResourceEnum::GachaItems => "app_gacha_items",
ResourceEnum::GachaClaims => "app_gacha_claims", ResourceEnum::GachaClaims => "app_gacha_claims",
ResourceEnum::GachaRolls => "app_gacha_rolls",
}; };
write!(f, "{}", str) write!(f, "{}", str)
} }