feat: apply env

This commit is contained in:
Maulana Sodiqin
2025-03-17 09:27:19 +07:00
parent 271b00c2c4
commit 0c28d34d57
14 changed files with 185 additions and 98 deletions
+6 -1
View File
@@ -1,4 +1,9 @@
PORT= PORT=
DATABASE_URL= SURREALDB_URL=
SURREALDB_USERNAME=
SURREALDB_PASSWORD=
SURREALDB_NAMESPACE=
SURREALDB_DBNAME=
REDISDB_URL=
ACCESS_TOKEN_SECRET= ACCESS_TOKEN_SECRET=
REFRESH_TOKEN_SECRET= REFRESH_TOKEN_SECRET=
+30
View File
@@ -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"
+17 -32
View File
@@ -1,9 +1,6 @@
use crate::{common_response, decode_access_token, AppState}; use crate::{common_response, extract_email, AppState};
use axum::{ use axum::{
extract::Request, extract::Request, http::StatusCode, middleware::Next, response::Response,
http::{header::AUTHORIZATION, StatusCode},
middleware::Next,
response::Response,
Extension, Extension,
}; };
use std::convert::Infallible; use std::convert::Infallible;
@@ -15,42 +12,30 @@ pub async fn auth_middleware(
mut req: Request, mut req: Request,
next: Next, next: Next,
) -> Result<Response, Infallible> { ) -> Result<Response, Infallible> {
let auth_header = match req.headers().get(AUTHORIZATION) { let headers = req.headers();
Some(h) => h.to_str().unwrap_or_default(),
let email = match extract_email(headers) {
Some(email) => email,
None => { None => {
return Ok(common_response( return Ok(common_response(
StatusCode::UNAUTHORIZED, StatusCode::UNAUTHORIZED,
"You are not authorized", "Invalid or expired token",
));
}
};
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"),
)); ));
} }
}; };
let repository = AuthRepository::new(&state); let repository = AuthRepository::new(&state);
let user: Option<AuthQueryByEmailResponse> = match repository let user: Option<AuthQueryByEmailResponse> =
.query_user_by_email(token_data.claims.sub.clone()) match repository.query_user_by_email(email).await {
.await Ok(user) => Some(user),
{ Err(err) => {
Ok(user) => Some(user), return Ok(common_response(
Err(err) => { StatusCode::INTERNAL_SERVER_ERROR,
return Ok(common_response( &format!("DB error: {}", err),
StatusCode::INTERNAL_SERVER_ERROR, ))
&format!("DB error: {}", err), }
)) };
}
};
if user.is_none() { if user.is_none() {
return Ok(common_response( return Ok(common_response(
+10 -9
View File
@@ -1,22 +1,23 @@
use super::{GachaRequestDto, GachaService}; use super::{GachaClaimRequestDto, GachaService};
use crate::{v1::GachaCreateItemRequestDto, AppState, MessageResponseDto}; use crate::{v1::GachaCreateItemRequestDto, AppState, MessageResponseDto};
use axum::{response::IntoResponse, Extension, Json}; use axum::{http::HeaderMap, response::IntoResponse, Extension, Json};
#[utoipa::path( #[utoipa::path(
post, post,
path = "/v1/gacha/create", path = "/v1/gacha/create/claims",
request_body = GachaRequestDto, request_body = GachaClaimRequestDto,
responses( responses(
(status = 200, description = "Create gacha successful", body = MessageResponseDto), (status = 200, description = "Create gacha claims successful", body = MessageResponseDto),
(status = 401, description = "Create gacha failed", body = MessageResponseDto) (status = 401, description = "Create gacha claims failed", body = MessageResponseDto)
), ),
tag = "Gacha" tag = "Gacha"
)] )]
pub async fn post_create_gacha( pub async fn post_create_gacha_claims(
header: HeaderMap,
Extension(state): Extension<AppState>, Extension(state): Extension<AppState>,
Json(payload): Json<GachaRequestDto>, Json(payload): Json<GachaClaimRequestDto>,
) -> impl IntoResponse { ) -> impl IntoResponse {
GachaService::mutation_create_gacha(payload, &state).await GachaService::mutation_create_gacha_claims(payload, &state, header).await
} }
#[utoipa::path( #[utoipa::path(
+8 -4
View File
@@ -4,9 +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 GachaRequestDto { pub struct GachaClaimRequestDto {
pub email: String,
pub fullname: String,
pub transaction_number: String, pub transaction_number: String,
} }
@@ -16,6 +14,12 @@ pub struct GachaCreateItemRequestDto {
pub item_image: String, 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)] #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaItemResponseDto { pub struct GachaItemResponseDto {
pub item_name: String, pub item_name: String,
@@ -23,7 +27,7 @@ pub struct GachaItemResponseDto {
} }
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct GachaResponseDto { pub struct GachaClaimResponseDto {
pub transaction_number: String, pub transaction_number: String,
pub user: UsersItemDto, pub user: UsersItemDto,
} }
+31 -9
View File
@@ -1,6 +1,6 @@
use super::{ use super::{
GachaCreateItemRequestDto, GachaItemSchema, GachaRequestDto, GachaResponseDto, GachaClaimRequestDto, GachaClaimResponseDto, GachaCreateItemRequestDto,
GachaSchema, GachaItemSchema, GachaSchema,
}; };
use crate::{v1::AuthRepository, AppState, ResourceEnum}; use crate::{v1::AuthRepository, AppState, ResourceEnum};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
@@ -18,7 +18,7 @@ impl<'a> GachaRepository<'a> {
pub async fn query_gacha_by_transaction_number( pub async fn query_gacha_by_transaction_number(
&self, &self,
transaction_number: String, transaction_number: String,
) -> Result<GachaResponseDto> { ) -> Result<GachaClaimResponseDto> {
let db = &self.state.surrealdb; let db = &self.state.surrealdb;
let result = db let result = db
@@ -31,13 +31,15 @@ impl<'a> GachaRepository<'a> {
} }
} }
pub async fn query_create_gacha(&self, data: GachaRequestDto) -> Result<String> { pub async fn query_create_gacha_claims(
&self,
data: GachaClaimRequestDto,
email: String,
) -> Result<String> {
let auth_repository = AuthRepository::new(self.state); let auth_repository = AuthRepository::new(self.state);
let db = &self.state.surrealdb; let db = &self.state.surrealdb;
let user = auth_repository let user = auth_repository.query_user_by_email(email).await?;
.query_user_by_email(data.email.clone())
.await?;
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)));
@@ -54,8 +56,8 @@ impl<'a> GachaRepository<'a> {
.await?; .await?;
match record { match record {
Some(_) => Ok("Gacha successfully created".to_string()), Some(_) => Ok("Gacha claims successfully created".to_string()),
None => bail!("Failed to create gacha record"), None => bail!("Failed to create gacha claims"),
} }
} }
@@ -78,4 +80,24 @@ impl<'a> GachaRepository<'a> {
None => bail!("Failed to create gacha item record"), None => bail!("Failed to create gacha item record"),
} }
} }
pub async fn query_create_gacha_roll(
&self,
data: GachaCreateItemRequestDto,
) -> Result<String> {
let db = &self.state.surrealdb;
let record: Option<GachaItemSchema> = 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"),
}
}
} }
+20 -6
View File
@@ -1,17 +1,31 @@
use super::{GachaCreateItemRequestDto, GachaRepository, GachaRequestDto}; use super::{GachaClaimRequestDto, GachaCreateItemRequestDto, GachaRepository};
use crate::{common_response, AppState}; use crate::{common_response, extract_email, AppState};
use axum::{http::StatusCode, response::Response}; use axum::{
http::{HeaderMap, StatusCode},
response::Response,
};
pub struct GachaService; pub struct GachaService;
impl GachaService { impl GachaService {
pub async fn mutation_create_gacha( pub async fn mutation_create_gacha_claims(
payload: GachaRequestDto, payload: GachaClaimRequestDto,
state: &AppState, state: &AppState,
header: HeaderMap,
) -> Response { ) -> Response {
let repository = GachaRepository::new(state); 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), 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()),
} }
+4 -1
View File
@@ -13,7 +13,10 @@ pub use gacha_service::*;
pub fn gacha_router() -> Router { pub fn gacha_router() -> Router {
Router::new() Router::new()
.route("/create", post(gacha_controller::post_create_gacha)) .route(
"/create/claims",
post(gacha_controller::post_create_gacha_claims),
)
.route( .route(
"/create/item", "/create/item",
post(gacha_controller::post_create_gacha_item), post(gacha_controller::post_create_gacha_item),
+36 -28
View File
@@ -4,14 +4,16 @@ pub struct Env {
pub port: u16, pub port: u16,
pub access_token_secret: String, pub access_token_secret: String,
pub refresh_token_secret: String, pub refresh_token_secret: String,
pub database_url: String, pub surrealdb_url: String,
pub database_schema: String, pub surrealdb_username: String,
pub surrealdb_password: String,
pub surrealdb_namespace: String,
pub surrealdb_dbname: String,
pub smtp_email: String, pub smtp_email: String,
pub smtp_password: String, pub smtp_password: String,
pub smtp_name: String, pub smtp_name: String,
pub smpt_host: String, pub smtp_host: String,
pub redis_hostname: String, pub redisdb_url: String,
pub redis_port: u16,
pub fe_url: String, pub fe_url: String,
pub rust_env: String, pub rust_env: String,
pub minio_endpoint: String, pub minio_endpoint: String,
@@ -24,39 +26,45 @@ impl Env {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
port: env::var("PORT") port: env::var("PORT")
.unwrap_or("3000".to_string()) .unwrap_or_else(|_| "3000".to_string())
.parse() .parse()
.unwrap_or(3000), .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") 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") refresh_token_secret: env::var("REFRESH_TOKEN_SECRET")
.unwrap_or("default_refresh_secret".to_string()), .unwrap_or_else(|_| "default_refresh_secret".to_string()),
database_url: env::var("DATABASE_URL") surrealdb_url: env::var("SURREALDB_URL")
.unwrap_or("postgres://localhost".to_string()), .unwrap_or_else(|_| "http://localhost:8000".to_string()),
database_schema: env::var("DATABASE_SCHEMA") surrealdb_username: env::var("SURREALDB_USERNAME")
.unwrap_or("public".to_string()), .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") 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") smtp_password: env::var("SMTP_PASSWORD")
.unwrap_or("default_smtp_password".to_string()), .unwrap_or_else(|_| "default_smtp_password".to_string()),
smtp_name: env::var("SMTP_NAME").unwrap_or("MyApp SMTP".to_string()), smtp_name: env::var("SMTP_NAME")
smpt_host: env::var("SMPT_HOST").unwrap_or("smpt.gmail.com".to_string()), .unwrap_or_else(|_| "MyApp SMTP".to_string()),
redis_hostname: env::var("REDIS_HOSTNAME") smtp_host: env::var("SMTP_HOST")
.unwrap_or("localhost".to_string()), .unwrap_or_else(|_| "smtp.gmail.com".to_string()),
fe_url: env::var("FE_URL").unwrap_or("http://localhost".to_string()), redisdb_url: env::var("REDISDB_URL")
rust_env: env::var("RUST_ENV").unwrap_or("development".to_string()), .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") 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") 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") 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") minio_secret_key: env::var("MINIO_SECRET_KEY")
.unwrap_or("minio_secret".to_string()), .unwrap_or_else(|_| "minio_secret".to_string()),
} }
} }
} }
+1 -1
View File
@@ -10,7 +10,7 @@ pub fn send_email(
body: &str, body: &str,
) -> Result<(), Box<dyn Error>> { ) -> Result<(), Box<dyn Error>> {
let env = Env::new(); let env = Env::new();
let host = env.smpt_host; let host = env.smtp_host;
let sender_email = env.smtp_email; let sender_email = env.smtp_email;
let sender_name = env.smtp_name; let sender_name = env.smtp_name;
let sender_password = env.smtp_password; let sender_password = env.smtp_password;
+1 -2
View File
@@ -7,8 +7,7 @@ pub use key::*;
pub async fn redisdb_init() -> RedisResult<Client> { pub async fn redisdb_init() -> RedisResult<Client> {
let env = Env::new(); let env = Env::new();
let host_name = env.redis_hostname; let url = format!("redis://{}", env.redisdb_url);
let url = format!("redis://{}", host_name);
let client = Client::open(url)?; let client = Client::open(url)?;
Ok(client) Ok(client)
} }
+10 -5
View File
@@ -1,18 +1,23 @@
use super::Env;
use crate::SurrealClient; use crate::SurrealClient;
use surrealdb::engine::remote::http::{Client, Http}; use surrealdb::engine::remote::http::{Client, Http};
use surrealdb::opt::auth::Root;
use surrealdb::{Result, Surreal}; use surrealdb::{Result, Surreal};
pub mod resource; pub mod resource;
pub use resource::*; pub use resource::*;
pub async fn surrealdb_init() -> Result<SurrealClient> { pub async fn surrealdb_init() -> Result<SurrealClient> {
let env = Env::new();
let db = Surreal::<Client>::init(); let db = Surreal::<Client>::init();
db.connect::<Http>("localhost:8000").await?; db.connect::<Http>(env.surrealdb_url).await?;
db.signin(surrealdb::opt::auth::Root { db.signin(Root {
username: "root", username: &env.surrealdb_username,
password: "root", password: &env.surrealdb_password,
}) })
.await?; .await?;
db.use_ns("test").use_db("test").await?; db.use_ns(env.surrealdb_namespace)
.use_db(env.surrealdb_dbname)
.await?;
Ok(db) Ok(db)
} }
+9
View File
@@ -0,0 +1,9 @@
use crate::decode_access_token;
use axum::http::{header::AUTHORIZATION, HeaderMap};
pub fn extract_email(headers: &HeaderMap) -> Option<String> {
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)
}
+2
View File
@@ -1,3 +1,5 @@
pub mod extract_email;
pub mod response_format; pub mod response_format;
pub use extract_email::*;
pub use response_format::*; pub use response_format::*;