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=
DATABASE_URL=
SURREALDB_URL=
SURREALDB_USERNAME=
SURREALDB_PASSWORD=
SURREALDB_NAMESPACE=
SURREALDB_DBNAME=
REDISDB_URL=
ACCESS_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::{
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<Response, Infallible> {
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<AuthQueryByEmailResponse> = 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<AuthQueryByEmailResponse> =
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(
+10 -9
View File
@@ -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<AppState>,
Json(payload): Json<GachaRequestDto>,
Json(payload): Json<GachaClaimRequestDto>,
) -> impl IntoResponse {
GachaService::mutation_create_gacha(payload, &state).await
GachaService::mutation_create_gacha_claims(payload, &state, header).await
}
#[utoipa::path(
+8 -4
View File
@@ -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,
}
+31 -9
View File
@@ -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<GachaResponseDto> {
) -> Result<GachaClaimResponseDto> {
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<String> {
pub async fn query_create_gacha_claims(
&self,
data: GachaClaimRequestDto,
email: String,
) -> Result<String> {
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<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 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()),
}
+4 -1
View File
@@ -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),
+36 -28
View File
@@ -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()),
}
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ pub fn send_email(
body: &str,
) -> Result<(), Box<dyn Error>> {
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;
+1 -2
View File
@@ -7,8 +7,7 @@ pub use key::*;
pub async fn redisdb_init() -> RedisResult<Client> {
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)
}
+10 -5
View File
@@ -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<SurrealClient> {
let env = Env::new();
let db = Surreal::<Client>::init();
db.connect::<Http>("localhost:8000").await?;
db.signin(surrealdb::opt::auth::Root {
username: "root",
password: "root",
db.connect::<Http>(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)
}
+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 use extract_email::*;
pub use response_format::*;