diff --git a/.env.example b/.env.example index 9ae7360..b90ccc5 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,9 @@ PORT= -DATABASE_URL= +SURREALDB_URL= +SURREALDB_USERNAME= +SURREALDB_PASSWORD= +SURREALDB_NAMESPACE= +SURREALDB_DBNAME= +REDISDB_URL= ACCESS_TOKEN_SECRET= REFRESH_TOKEN_SECRET= diff --git a/apply-env.ps1 b/apply-env.ps1 new file mode 100644 index 0000000..2312b4b --- /dev/null +++ b/apply-env.ps1 @@ -0,0 +1,30 @@ +function Set-TempEnvFromDotEnv { + param ( + [string]$envFilePath + ) + + if (-Not (Test-Path $envFilePath)) { + Write-Error "The .env file at path '$envFilePath' does not exist." + return + } + + $envContent = Get-Content $envFilePath + + foreach ($line in $envContent) { + $trimmedLine = $line.Trim() + + if (-Not [string]::IsNullOrWhiteSpace($trimmedLine) -and -Not $trimmedLine.StartsWith("#")) { + $keyValue = $trimmedLine -split "=", 2 + if ($keyValue.Length -eq 2) { + $key = $keyValue[0].Trim() + $value = $keyValue[1].Trim() + [System.Environment]::SetEnvironmentVariable($key, $value, [System.EnvironmentVariableTarget]::Process) + Write-Host "Set temporary environment variable: $key=$value" + } + } + } + + Write-Host "All environment variables from '$envFilePath' have been set temporarily." +} + +Set-TempEnvFromDotEnv -envFilePath ".env" diff --git a/src/apps/v1/auth/auth_middleware.rs b/src/apps/v1/auth/auth_middleware.rs index 41bb24a..8b57341 100644 --- a/src/apps/v1/auth/auth_middleware.rs +++ b/src/apps/v1/auth/auth_middleware.rs @@ -1,9 +1,6 @@ -use crate::{common_response, decode_access_token, AppState}; +use crate::{common_response, extract_email, AppState}; use axum::{ - extract::Request, - http::{header::AUTHORIZATION, StatusCode}, - middleware::Next, - response::Response, + extract::Request, http::StatusCode, middleware::Next, response::Response, Extension, }; use std::convert::Infallible; @@ -15,42 +12,30 @@ pub async fn auth_middleware( mut req: Request, next: Next, ) -> Result { - let auth_header = match req.headers().get(AUTHORIZATION) { - Some(h) => h.to_str().unwrap_or_default(), + let headers = req.headers(); + + let email = match extract_email(headers) { + Some(email) => email, None => { return Ok(common_response( StatusCode::UNAUTHORIZED, - "You are not authorized", - )); - } - }; - - let token = auth_header.strip_prefix("Bearer ").unwrap_or(""); - - let token_data = match decode_access_token(token) { - Ok(data) => data, - Err(_) => { - return Ok(common_response( - StatusCode::UNAUTHORIZED, - &format!("Invalid or expired token"), + "Invalid or expired token", )); } }; let repository = AuthRepository::new(&state); - let user: Option = match repository - .query_user_by_email(token_data.claims.sub.clone()) - .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, + &format!("DB error: {}", err), + )) + } + }; if user.is_none() { return Ok(common_response( diff --git a/src/apps/v1/gacha/gacha_controller.rs b/src/apps/v1/gacha/gacha_controller.rs index 5bd1058..d4d0aab 100644 --- a/src/apps/v1/gacha/gacha_controller.rs +++ b/src/apps/v1/gacha/gacha_controller.rs @@ -1,22 +1,23 @@ -use super::{GachaRequestDto, GachaService}; +use super::{GachaClaimRequestDto, GachaService}; use crate::{v1::GachaCreateItemRequestDto, AppState, MessageResponseDto}; -use axum::{response::IntoResponse, Extension, Json}; +use axum::{http::HeaderMap, response::IntoResponse, Extension, Json}; #[utoipa::path( post, - path = "/v1/gacha/create", - request_body = GachaRequestDto, + path = "/v1/gacha/create/claims", + request_body = GachaClaimRequestDto, responses( - (status = 200, description = "Create gacha successful", body = MessageResponseDto), - (status = 401, description = "Create gacha failed", body = MessageResponseDto) + (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( +pub async fn post_create_gacha_claims( + header: HeaderMap, Extension(state): Extension, - Json(payload): Json, + Json(payload): Json, ) -> impl IntoResponse { - GachaService::mutation_create_gacha(payload, &state).await + GachaService::mutation_create_gacha_claims(payload, &state, header).await } #[utoipa::path( diff --git a/src/apps/v1/gacha/gacha_dto.rs b/src/apps/v1/gacha/gacha_dto.rs index d758e0a..324fc5c 100644 --- a/src/apps/v1/gacha/gacha_dto.rs +++ b/src/apps/v1/gacha/gacha_dto.rs @@ -4,9 +4,7 @@ use utoipa::ToSchema; use crate::v1::UsersItemDto; #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] -pub struct GachaRequestDto { - pub email: String, - pub fullname: String, +pub struct GachaClaimRequestDto { pub transaction_number: String, } @@ -16,6 +14,12 @@ pub struct GachaCreateItemRequestDto { pub item_image: String, } +#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] +pub struct GachaCreateRollRequestDto { + pub item_id: String, + pub weight: String, +} + #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] pub struct GachaItemResponseDto { pub item_name: String, @@ -23,7 +27,7 @@ pub struct GachaItemResponseDto { } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] -pub struct GachaResponseDto { +pub struct GachaClaimResponseDto { pub transaction_number: String, pub user: UsersItemDto, } diff --git a/src/apps/v1/gacha/gacha_repository.rs b/src/apps/v1/gacha/gacha_repository.rs index 7eed740..148e16e 100644 --- a/src/apps/v1/gacha/gacha_repository.rs +++ b/src/apps/v1/gacha/gacha_repository.rs @@ -1,6 +1,6 @@ use super::{ - GachaCreateItemRequestDto, GachaItemSchema, GachaRequestDto, GachaResponseDto, - GachaSchema, + GachaClaimRequestDto, GachaClaimResponseDto, GachaCreateItemRequestDto, + GachaItemSchema, GachaSchema, }; use crate::{v1::AuthRepository, AppState, ResourceEnum}; use anyhow::{bail, Result}; @@ -18,7 +18,7 @@ impl<'a> GachaRepository<'a> { pub async fn query_gacha_by_transaction_number( &self, transaction_number: String, - ) -> Result { + ) -> Result { let db = &self.state.surrealdb; let result = db @@ -31,13 +31,15 @@ impl<'a> GachaRepository<'a> { } } - pub async fn query_create_gacha(&self, data: GachaRequestDto) -> Result { + pub async fn query_create_gacha_claims( + &self, + data: GachaClaimRequestDto, + email: String, + ) -> Result { let auth_repository = AuthRepository::new(self.state); let db = &self.state.surrealdb; - let user = auth_repository - .query_user_by_email(data.email.clone()) - .await?; + let user = auth_repository.query_user_by_email(email).await?; let user_thing = Thing::from((ResourceEnum::Users.to_string(), Id::String(user.email))); @@ -54,8 +56,8 @@ impl<'a> GachaRepository<'a> { .await?; match record { - Some(_) => Ok("Gacha successfully created".to_string()), - None => bail!("Failed to create gacha record"), + Some(_) => Ok("Gacha claims successfully created".to_string()), + None => bail!("Failed to create gacha claims"), } } @@ -78,4 +80,24 @@ impl<'a> GachaRepository<'a> { None => bail!("Failed to create gacha item record"), } } + + pub async fn query_create_gacha_roll( + &self, + data: GachaCreateItemRequestDto, + ) -> Result { + let db = &self.state.surrealdb; + + 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(), + }) + .await?; + + match record { + Some(_) => Ok("Gacha item successfully created".to_string()), + None => bail!("Failed to create gacha item record"), + } + } } diff --git a/src/apps/v1/gacha/gacha_service.rs b/src/apps/v1/gacha/gacha_service.rs index 094fdfc..3edfbdf 100644 --- a/src/apps/v1/gacha/gacha_service.rs +++ b/src/apps/v1/gacha/gacha_service.rs @@ -1,17 +1,31 @@ -use super::{GachaCreateItemRequestDto, GachaRepository, GachaRequestDto}; -use crate::{common_response, AppState}; -use axum::{http::StatusCode, response::Response}; +use super::{GachaClaimRequestDto, GachaCreateItemRequestDto, GachaRepository}; +use crate::{common_response, extract_email, AppState}; +use axum::{ + http::{HeaderMap, StatusCode}, + response::Response, +}; pub struct GachaService; impl GachaService { - pub async fn mutation_create_gacha( - payload: GachaRequestDto, + pub async fn mutation_create_gacha_claims( + payload: GachaClaimRequestDto, state: &AppState, + header: HeaderMap, ) -> Response { let repository = GachaRepository::new(state); - match repository.query_create_gacha(payload).await { + let email = match extract_email(&header) { + Some(email) => email, + None => { + return common_response( + StatusCode::UNAUTHORIZED, + "Invalid or expired token", + ); + } + }; + + match repository.query_create_gacha_claims(payload, email).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 dca347f..7b08ef7 100644 --- a/src/apps/v1/gacha/mod.rs +++ b/src/apps/v1/gacha/mod.rs @@ -13,7 +13,10 @@ pub use gacha_service::*; pub fn gacha_router() -> Router { Router::new() - .route("/create", post(gacha_controller::post_create_gacha)) + .route( + "/create/claims", + post(gacha_controller::post_create_gacha_claims), + ) .route( "/create/item", post(gacha_controller::post_create_gacha_item), diff --git a/src/libs/enviroment/mod.rs b/src/libs/enviroment/mod.rs index dc6fa7c..9b60fc7 100644 --- a/src/libs/enviroment/mod.rs +++ b/src/libs/enviroment/mod.rs @@ -4,14 +4,16 @@ pub struct Env { pub port: u16, pub access_token_secret: String, pub refresh_token_secret: String, - pub database_url: String, - pub database_schema: String, + pub surrealdb_url: String, + pub surrealdb_username: String, + pub surrealdb_password: String, + pub surrealdb_namespace: String, + pub surrealdb_dbname: String, pub smtp_email: String, pub smtp_password: String, pub smtp_name: String, - pub smpt_host: String, - pub redis_hostname: String, - pub redis_port: u16, + pub smtp_host: String, + pub redisdb_url: String, pub fe_url: String, pub rust_env: String, pub minio_endpoint: String, @@ -24,39 +26,45 @@ impl Env { pub fn new() -> Self { Self { port: env::var("PORT") - .unwrap_or("3000".to_string()) + .unwrap_or_else(|_| "3000".to_string()) .parse() .unwrap_or(3000), - redis_port: env::var("REDIS_PORT") - .unwrap_or("5436".to_string()) - .parse() - .unwrap_or(5436), access_token_secret: env::var("ACCESS_TOKEN_SECRET") - .unwrap_or("default_access_secret".to_string()), + .unwrap_or_else(|_| "default_access_secret".to_string()), refresh_token_secret: env::var("REFRESH_TOKEN_SECRET") - .unwrap_or("default_refresh_secret".to_string()), - database_url: env::var("DATABASE_URL") - .unwrap_or("postgres://localhost".to_string()), - database_schema: env::var("DATABASE_SCHEMA") - .unwrap_or("public".to_string()), + .unwrap_or_else(|_| "default_refresh_secret".to_string()), + surrealdb_url: env::var("SURREALDB_URL") + .unwrap_or_else(|_| "http://localhost:8000".to_string()), + surrealdb_username: env::var("SURREALDB_USERNAME") + .unwrap_or_else(|_| "root".to_string()), + surrealdb_password: env::var("SURREALDB_PASSWORD") + .unwrap_or_else(|_| "password".to_string()), + surrealdb_namespace: env::var("SURREALDB_NAMESPACE") + .unwrap_or_else(|_| "namespace".to_string()), + surrealdb_dbname: env::var("SURREALDB_DBNAME") + .unwrap_or_else(|_| "database".to_string()), smtp_email: env::var("SMTP_EMAIL") - .unwrap_or("no-reply@example.com".to_string()), + .unwrap_or_else(|_| "no-reply@example.com".to_string()), smtp_password: env::var("SMTP_PASSWORD") - .unwrap_or("default_smtp_password".to_string()), - smtp_name: env::var("SMTP_NAME").unwrap_or("MyApp SMTP".to_string()), - smpt_host: env::var("SMPT_HOST").unwrap_or("smpt.gmail.com".to_string()), - redis_hostname: env::var("REDIS_HOSTNAME") - .unwrap_or("localhost".to_string()), - fe_url: env::var("FE_URL").unwrap_or("http://localhost".to_string()), - rust_env: env::var("RUST_ENV").unwrap_or("development".to_string()), + .unwrap_or_else(|_| "default_smtp_password".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()), minio_endpoint: env::var("MINIO_ENDPOINT") - .unwrap_or("http://localhost:9000".to_string()), + .unwrap_or_else(|_| "http://localhost:9000".to_string()), minio_bucket_name: env::var("MINIO_BUCKET_NAME") - .unwrap_or("default_bucket".to_string()), + .unwrap_or_else(|_| "default_bucket".to_string()), minio_access_key: env::var("MINIO_ACCESS_KEY") - .unwrap_or("minio_access".to_string()), + .unwrap_or_else(|_| "minio_access".to_string()), minio_secret_key: env::var("MINIO_SECRET_KEY") - .unwrap_or("minio_secret".to_string()), + .unwrap_or_else(|_| "minio_secret".to_string()), } } } diff --git a/src/libs/lettre/mod.rs b/src/libs/lettre/mod.rs index 90e31be..9932265 100644 --- a/src/libs/lettre/mod.rs +++ b/src/libs/lettre/mod.rs @@ -10,7 +10,7 @@ pub fn send_email( body: &str, ) -> Result<(), Box> { let env = Env::new(); - let host = env.smpt_host; + let host = env.smtp_host; let sender_email = env.smtp_email; let sender_name = env.smtp_name; let sender_password = env.smtp_password; diff --git a/src/libs/redisdb/mod.rs b/src/libs/redisdb/mod.rs index 03ff721..fed6e1a 100644 --- a/src/libs/redisdb/mod.rs +++ b/src/libs/redisdb/mod.rs @@ -7,8 +7,7 @@ pub use key::*; pub async fn redisdb_init() -> RedisResult { let env = Env::new(); - let host_name = env.redis_hostname; - let url = format!("redis://{}", host_name); + let url = format!("redis://{}", env.redisdb_url); let client = Client::open(url)?; Ok(client) } diff --git a/src/libs/surrealdb/mod.rs b/src/libs/surrealdb/mod.rs index b356c64..f87913b 100644 --- a/src/libs/surrealdb/mod.rs +++ b/src/libs/surrealdb/mod.rs @@ -1,18 +1,23 @@ +use super::Env; use crate::SurrealClient; use surrealdb::engine::remote::http::{Client, Http}; +use surrealdb::opt::auth::Root; use surrealdb::{Result, Surreal}; pub mod resource; pub use resource::*; pub async fn surrealdb_init() -> Result { + let env = Env::new(); let db = Surreal::::init(); - db.connect::("localhost:8000").await?; - db.signin(surrealdb::opt::auth::Root { - username: "root", - password: "root", + db.connect::(env.surrealdb_url).await?; + db.signin(Root { + username: &env.surrealdb_username, + password: &env.surrealdb_password, }) .await?; - db.use_ns("test").use_db("test").await?; + db.use_ns(env.surrealdb_namespace) + .use_db(env.surrealdb_dbname) + .await?; Ok(db) } diff --git a/src/utils/extract_email.rs b/src/utils/extract_email.rs new file mode 100644 index 0000000..d30f074 --- /dev/null +++ b/src/utils/extract_email.rs @@ -0,0 +1,9 @@ +use crate::decode_access_token; +use axum::http::{header::AUTHORIZATION, HeaderMap}; + +pub fn extract_email(headers: &HeaderMap) -> Option { + let auth_header = headers.get(AUTHORIZATION)?.to_str().ok()?; + let token = auth_header.strip_prefix("Bearer ")?; + let token_data = decode_access_token(token).ok()?; + Some(token_data.claims.sub) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 74610f2..d6c76d6 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,5 @@ +pub mod extract_email; pub mod response_format; +pub use extract_email::*; pub use response_format::*;