chore: setup libs and utils

This commit is contained in:
Maulana Sodiqin
2025-02-07 22:26:32 +07:00
parent 8a5a051e7a
commit c637a356eb
24 changed files with 2935 additions and 41 deletions
-9
View File
@@ -1,9 +0,0 @@
[env]
RUST_LOG="imphnen-cms-api=debug"
RUST_ENV="development"
[profile.release]
lto = "fat"
codegen-units = 1
panic = "abort"
opt-level = "z"
+4
View File
@@ -0,0 +1,4 @@
PORT=
DATABASE_URL=
ACCESS_TOKEN_SECRET=
REFRESH_TOKEN_SECRET=
+1
View File
@@ -0,0 +1 @@
use flake --impure
Generated
+2629 -18
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -5,6 +5,20 @@ edition = "2021"
[dependencies]
axum = { version = "0.8.1", features = ["multipart"] }
log = "0.4.25"
sea-orm = { version = "1.1.4", features = ["sqlx-postgres", "macros", "with-json", "with-uuid", "runtime-tokio", "runtime-tokio-native-tls"] }
serde = { version = "1.0.217", features = ["derive"] }
serde_json = "1.0.138"
tokio = { version = "1.43.0", features = ["full"] }
argon2 = { version = "0.5.3", features = ["password-hash"] }
jsonwebtoken = "9.3.1"
chrono = "0.4.39"
utoipa = { version = "5.3.1", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "9.0.0", features = ["axum"] }
redis = "0.28.2"
[profile.release]
lto = "fat"
codegen-units = 1
panic = "abort"
opt-level = "z"
+3
View File
@@ -0,0 +1,3 @@
hard_tabs = true
edition = "2021"
max_width = 85
+8
View File
@@ -1,2 +1,10 @@
use axum::{routing::get, Router};
pub mod v1;
pub mod v2;
pub async fn apps() -> Router {
let v1_routes = Router::new().route("/", get(|| async { "Comming Soon v1" }));
let v2_routes = Router::new().route("/", get(|| async { "Comming Soon v2" }));
Router::new().nest("/v1", v1_routes).nest("/v2", v2_routes)
}
+1 -6
View File
@@ -1,9 +1,4 @@
pub mod auth_controller;
pub mod auth_dto;
pub mod auth_middleware;
pub mod auth_model;
pub mod auth_repository;
pub use auth_controller::*;
pub use auth_middleware::*;
pub use auth_model::*;
pub use auth_repository::*;
+37
View File
@@ -0,0 +1,37 @@
use serde::{Deserialize, Serialize};
use utoipa::{IntoParams, ToSchema};
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct MessageResponseDto {
pub message: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)]
pub struct MetaRequestDto {
pub page: Option<u64>,
pub per_page: Option<u64>,
pub search: Option<String>,
pub sort_by: Option<String>,
pub order: Option<String>,
pub filter: Option<String>,
pub filter_by: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)]
pub struct MetaResponseDto {
pub page: Option<u64>,
pub per_page: Option<u64>,
pub total: Option<u64>,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct ResponseSuccessDto<T: Serialize> {
pub data: T,
}
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
pub struct ResponseListSuccessDto<T: Serialize> {
pub data: T,
pub meta: Option<MetaResponseDto>,
}
+3
View File
@@ -0,0 +1,3 @@
pub mod common_dto;
pub use common_dto::*;
+5 -5
View File
@@ -1,9 +1,9 @@
pub mod apps;
pub mod entities;
pub mod libs;
pub mod utils;
pub use apps::v1;
pub use apps::v2;
pub use libs::axum;
pub use libs::seaorm;
pub use apps::*;
pub use entities::*;
pub use libs::*;
pub use utils::*;
+24
View File
@@ -0,0 +1,24 @@
use argon2::{
password_hash::{
rand_core::OsRng, Error, PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
},
Argon2,
};
pub fn hash_password(password: &str) -> Result<String, Error> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(password.as_bytes(), &salt)?
.to_string();
Ok(password_hash)
}
pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
let parsed_hash = PasswordHash::new(hash)?;
let argon2 = Argon2::default();
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
Ok(_) => Ok(true),
Err(_) => Ok(false),
}
}
View File
+25
View File
@@ -0,0 +1,25 @@
use axum::{serve, Router};
use std::env;
use std::{future::Future, net::SocketAddr};
use tokio::net::TcpListener;
pub async fn axum_init<F, Fut>(router_fn: F)
where
F: Fn() -> Fut,
Fut: Future<Output = Router>,
{
let router = router_fn().await;
let addr = SocketAddr::from((
[0, 0, 0, 0],
env::var("PORT")
.unwrap_or("3000".to_string())
.parse()
.unwrap(),
));
let listener = TcpListener::bind(&addr).await.unwrap();
println!("Listening on http://{}", addr);
match serve(listener, router).await {
Ok(_) => println!("Server stopped gracefully."),
Err(err) => println!("Server encountered an error: {}", err),
}
}
+80
View File
@@ -0,0 +1,80 @@
use axum::http::StatusCode;
use chrono::{Duration, TimeDelta, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
use serde::{Deserialize, Serialize};
use std::env;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
pub exp: usize,
pub iat: usize,
pub email: String,
}
pub fn encode_access_token(email: &str) -> Result<String, StatusCode> {
let secret: String = env::var("ACCESS_TOKEN_SECRET")
.unwrap_or("this-is-secret".to_string())
.to_string();
let now = Utc::now();
let expire: TimeDelta = Duration::minutes(15);
let exp: usize = (now + expire).timestamp() as usize;
let iat: usize = now.timestamp() as usize;
let claim = Claims {
iat,
exp,
email: email.to_string(),
};
encode(
&Header::default(),
&claim,
&EncodingKey::from_secret(secret.as_ref()),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
pub fn decode_access_token(jwt_token: &str) -> Result<TokenData<Claims>, StatusCode> {
let secret = env::var("ACCESS_TOKEN_SECRET")
.unwrap_or("this-is-secret".to_string())
.to_string();
let result: Result<TokenData<Claims>, StatusCode> = decode(
&jwt_token,
&DecodingKey::from_secret(secret.as_ref()),
&Validation::default(),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);
result
}
pub fn encode_refresh_token(email: &str) -> Result<String, StatusCode> {
let secret: String = env::var("REFRESH_TOKEN_SECRET")
.unwrap_or("this-is-secret".to_string())
.to_string();
let now = Utc::now();
let expire: TimeDelta = Duration::days(1);
let exp: usize = (now + expire).timestamp() as usize;
let iat: usize = now.timestamp() as usize;
let claim = Claims {
iat,
exp,
email: email.to_string(),
};
encode(
&Header::default(),
&claim,
&EncodingKey::from_secret(secret.as_ref()),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
}
pub fn decode_refresh_token(jwt_token: &str) -> Result<TokenData<Claims>, StatusCode> {
let secret = env::var("REFRESH_TOKEN_SECRET")
.unwrap_or("this-is-secret".to_string())
.to_string();
let result: Result<TokenData<Claims>, StatusCode> = decode(
&jwt_token,
&DecodingKey::from_secret(secret.as_ref()),
&Validation::default(),
)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR);
result
}
+9
View File
@@ -1,2 +1,11 @@
pub mod argon;
pub mod axum;
pub mod jsonwebtoken;
pub mod redis;
pub mod seaorm;
pub use argon::*;
pub use axum::*;
pub use jsonwebtoken::*;
pub use redis::*;
pub use seaorm::*;
+20
View File
@@ -0,0 +1,20 @@
use std::env;
use redis::Client;
pub fn db_redis() -> redis::Connection {
let host_name = env::var("REDIS_HOSTNAME").unwrap_or("localhost".to_string());
let uri_scheme = if env::var("IS_TLS").is_ok() {
"rediss"
} else {
"redis"
};
let url = format!("{}://{}", uri_scheme, host_name);
Client::open(url)
.expect("Invalid connection URL")
.get_connection()
.expect("Failed to connect to Redis")
}
View File
+21
View File
@@ -0,0 +1,21 @@
use log::LevelFilter;
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
use std::{env, time::Duration};
pub async fn db_pgsql() -> DatabaseConnection {
let url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let mut opt = ConnectOptions::new(&url);
opt.max_connections(100)
.min_connections(5)
.connect_timeout(Duration::from_secs(5))
.acquire_timeout(Duration::from_secs(5))
.idle_timeout(Duration::from_secs(3))
.max_lifetime(Duration::from_secs(10))
.sqlx_logging(true)
.sqlx_logging_level(LevelFilter::Info)
.set_schema_search_path("public");
match Database::connect(opt).await {
Ok(connect) => connect,
Err(error) => panic!("{}", error),
}
}
+5 -2
View File
@@ -1,3 +1,6 @@
fn main() {
println!("Hello, world!");
use imphnen_cms_be::{apps, libs::axum_init};
#[tokio::main]
async fn main() {
axum_init(apps).await;
}
+3 -1
View File
@@ -1 +1,3 @@
pub mod password_hash;
pub mod response_format;
pub use response_format::*;
View File
+43
View File
@@ -0,0 +1,43 @@
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde::Serialize;
use serde_json::json;
use crate::{ResponseListSuccessDto, ResponseSuccessDto};
pub fn success_response<T: Serialize>(params: ResponseSuccessDto<T>) -> Response {
(
StatusCode::OK,
Json(json!({
"data": params.data,
"version": "0.1.0",
})),
)
.into_response()
}
pub fn success_list_response<T: Serialize>(params: ResponseListSuccessDto<T>) -> Response {
(
StatusCode::OK,
Json(json!({
"data": params.data,
"meta": params.meta,
"version": "0.1.0",
})),
)
.into_response()
}
pub fn common_response(status: StatusCode, message: &str) -> Response {
(
status,
Json(json!({
"message": message,
"version": "0.1.0",
})),
)
.into_response()
}