chore: setup libs and utils
This commit is contained in:
@@ -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"
|
||||
@@ -0,0 +1,4 @@
|
||||
PORT=
|
||||
DATABASE_URL=
|
||||
ACCESS_TOKEN_SECRET=
|
||||
REFRESH_TOKEN_SECRET=
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
use flake --impure
|
||||
|
||||
Generated
+2629
-18
File diff suppressed because it is too large
Load Diff
+14
@@ -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"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
hard_tabs = true
|
||||
edition = "2021"
|
||||
max_width = 85
|
||||
@@ -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,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::*;
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod common_dto;
|
||||
|
||||
pub use common_dto::*;
|
||||
+5
-5
@@ -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::*;
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
@@ -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
@@ -1 +1,3 @@
|
||||
pub mod password_hash;
|
||||
pub mod response_format;
|
||||
|
||||
pub use response_format::*;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user