feat: ✨ one time password
feat: ✨ json web token feat: ✨ mail template
This commit is contained in:
parent
c7863c806a
commit
bb2b70ccac
16 changed files with 536 additions and 9 deletions
|
@ -17,7 +17,8 @@ strip = "symbols"
|
|||
[dependencies]
|
||||
axum = "0.7.9"
|
||||
chrono = { version = "0.4.39", features = ["serde"] }
|
||||
lettre = { version = "0.11.11", default-features = false, features = ["builder", "smtp-transport", "tokio1-rustls-tls"] }
|
||||
jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] }
|
||||
lettre = { version = "0.11.11", default-features = false, features = ["builder", "serde", "smtp-transport", "tokio1-rustls-tls"] }
|
||||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
serde_json = "1.0.133"
|
||||
sqlx = { version = "0.8.2", features = ["chrono", "macros", "postgres", "runtime-tokio-rustls"] }
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
[server_config]
|
||||
address = "localhost:2344"
|
||||
address = "localhost:2344"
|
||||
otp_time_limit = 15
|
||||
login_token_time_limit = 15
|
||||
concurrency_limit = -1
|
3
mail_templates/one_time_password.toml
Normal file
3
mail_templates/one_time_password.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[one_time_password]
|
||||
subject = "Your One Time Password"
|
||||
body = "Dear *, \nHere is your One Time Password = * \n Best Wishes"
|
2
migrations/20250110224901_login.down.sql
Normal file
2
migrations/20250110224901_login.down.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- Add down migration script here
|
||||
DROP TABLE IF EXISTS "login";
|
6
migrations/20250110224901_login.up.sql
Normal file
6
migrations/20250110224901_login.up.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
-- Add up migration script here
|
||||
CREATE TABLE IF NOT EXISTS "login"(
|
||||
user_id BIGSERIAL NOT NULL REFERENCES "user"(id),
|
||||
token VARCHAR(1024) NOT NULL,
|
||||
PRIMARY KEY (user_id, token)
|
||||
);
|
|
@ -2,6 +2,7 @@ pub mod comment;
|
|||
pub mod comment_interaction;
|
||||
pub mod contact;
|
||||
pub mod interaction;
|
||||
pub mod login;
|
||||
pub mod permission;
|
||||
pub mod post;
|
||||
pub mod post_interaction;
|
||||
|
|
124
src/database/login.rs
Normal file
124
src/database/login.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use crate::feature::login::Login;
|
||||
|
||||
pub async fn create(
|
||||
user_id: &i64,
|
||||
token: &String,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Login, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Login,
|
||||
r#"
|
||||
INSERT INTO "login"(user_id, token)
|
||||
VALUES ($1, $2)
|
||||
RETURNING *
|
||||
"#,
|
||||
user_id,
|
||||
token,
|
||||
)
|
||||
.fetch_one(database_connection)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn read(
|
||||
user_id: &i64,
|
||||
token: &String,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Login, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Login,
|
||||
r#"
|
||||
SELECT * FROM "login" WHERE "user_id" = $1 AND "token" = $2
|
||||
"#,
|
||||
user_id,
|
||||
token
|
||||
)
|
||||
.fetch_one(database_connection)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
user_id: &i64,
|
||||
token: &String,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Login, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Login,
|
||||
r#"
|
||||
UPDATE "login" SET "token" = $2 WHERE "user_id" = $1
|
||||
RETURNING *
|
||||
"#,
|
||||
user_id,
|
||||
token,
|
||||
)
|
||||
.fetch_one(database_connection)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
user_id: &i64,
|
||||
token: &String,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Login, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Login,
|
||||
r#"
|
||||
DELETE FROM "login" WHERE "user_id" = $1 AND "token" = $2
|
||||
RETURNING *
|
||||
"#,
|
||||
user_id,
|
||||
token,
|
||||
)
|
||||
.fetch_one(database_connection)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn read_all_for_user(
|
||||
user_id: &i64,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Vec<Login>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Login,
|
||||
r#"
|
||||
SELECT * FROM "login" WHERE "user_id" = $1
|
||||
"#,
|
||||
user_id,
|
||||
)
|
||||
.fetch_all(database_connection)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_all_for_user(
|
||||
user_id: &i64,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Vec<Login>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Login,
|
||||
r#"
|
||||
DELETE FROM "login" WHERE "user_id" = $1
|
||||
RETURNING *
|
||||
"#,
|
||||
user_id,
|
||||
)
|
||||
.fetch_all(database_connection)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn count_all_for_user(
|
||||
user_id: &i64,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<u64, sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
SELECT COUNT(user_id) FROM "login" WHERE "user_id" = $1
|
||||
"#,
|
||||
user_id,
|
||||
)
|
||||
.fetch_one(database_connection)
|
||||
.await?
|
||||
.count
|
||||
.map_or(0, |count| count)
|
||||
.try_into()
|
||||
.or(Ok(0))
|
||||
}
|
25
src/error.rs
25
src/error.rs
|
@ -26,3 +26,28 @@ impl std::error::Error for ForumInputError {
|
|||
self.source()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum ForumMailError {
|
||||
TemplateHeader,
|
||||
TemplateLackOfParameter,
|
||||
Send(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ForumMailError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ForumMailError::TemplateHeader => write!(f, "Template Header is Wrong"),
|
||||
ForumMailError::TemplateLackOfParameter => {
|
||||
write!(f, "Template Parameters Are Not Enough")
|
||||
}
|
||||
ForumMailError::Send(error) => write!(f, "Sending | {}", error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ForumMailError {
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||
self.source()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
pub mod auth;
|
||||
pub mod comment;
|
||||
pub mod comment_interaction;
|
||||
pub mod contact;
|
||||
pub mod interaction;
|
||||
pub mod login;
|
||||
pub mod permission;
|
||||
pub mod post;
|
||||
pub mod post_interaction;
|
||||
|
|
62
src/feature/auth.rs
Normal file
62
src/feature/auth.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
error::ForumMailError,
|
||||
mail::{MailFieldsOneTimePassword, MailTemplate},
|
||||
ONE_TIME_PASSWORDS,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct OneTimePassword {
|
||||
pub user_id: i64,
|
||||
pub one_time_password: String,
|
||||
}
|
||||
|
||||
impl OneTimePassword {
|
||||
pub fn init() -> RwLock<Vec<OneTimePassword>> {
|
||||
RwLock::new(vec![])
|
||||
}
|
||||
|
||||
pub async fn new(
|
||||
user_id: &i64,
|
||||
user_name: &String,
|
||||
user_email: &String,
|
||||
) -> Result<(), ForumMailError> {
|
||||
let one_time_password = "123".to_owned();
|
||||
let new_self = Self {
|
||||
user_id: *user_id,
|
||||
one_time_password,
|
||||
};
|
||||
|
||||
let mail_template =
|
||||
MailTemplate::OneTimePassword(MailFieldsOneTimePassword::new(user_name, &new_self));
|
||||
|
||||
mail_template.send_mail(user_email).await?;
|
||||
|
||||
let mut one_time_passwords = ONE_TIME_PASSWORDS.write().await;
|
||||
one_time_passwords.push(new_self);
|
||||
one_time_passwords.sort_by_key(|one_time_password| one_time_password.user_id);
|
||||
drop(one_time_passwords);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn verify(one_time_password: &OneTimePassword) -> bool {
|
||||
let one_time_password_search = ONE_TIME_PASSWORDS
|
||||
.read()
|
||||
.await
|
||||
.binary_search_by(|one_time_password_| one_time_password_.cmp(one_time_password));
|
||||
match one_time_password_search {
|
||||
Ok(one_time_password_index) => {
|
||||
let mut one_time_passwords = ONE_TIME_PASSWORDS.write().await;
|
||||
one_time_passwords.swap_remove(one_time_password_index);
|
||||
one_time_passwords.sort_by_key(|one_time_password| one_time_password.user_id);
|
||||
drop(one_time_passwords);
|
||||
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
77
src/feature/login.rs
Normal file
77
src/feature/login.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use jwt_simple::{
|
||||
claims::Claims,
|
||||
common::VerificationOptions,
|
||||
prelude::{HS256Key, MACLike},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use crate::{database::login, SERVER_CONFIG};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Login {
|
||||
pub user_id: i64,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
impl Login {
|
||||
pub async fn create(
|
||||
user_id: &i64,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Login, sqlx::Error> {
|
||||
let key = HS256Key::generate();
|
||||
let claims = Claims::create(jwt_simple::prelude::Duration::from_mins(
|
||||
SERVER_CONFIG.login_token_time_limit as u64,
|
||||
));
|
||||
let mut verification_options = VerificationOptions::default();
|
||||
verification_options.time_tolerance = Some(jwt_simple::prelude::Duration::from(0));
|
||||
|
||||
let token = key.authenticate(claims).unwrap();
|
||||
|
||||
login::create(user_id, &token, database_connection).await
|
||||
}
|
||||
|
||||
pub async fn read(
|
||||
user_id: &i64,
|
||||
token: &String,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Login, sqlx::Error> {
|
||||
login::read(user_id, token, database_connection).await
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
user_id: &i64,
|
||||
token: &String,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Login, sqlx::Error> {
|
||||
login::update(user_id, token, database_connection).await
|
||||
}
|
||||
pub async fn delete(
|
||||
user_id: &i64,
|
||||
token: &String,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Login, sqlx::Error> {
|
||||
login::delete(user_id, token, database_connection).await
|
||||
}
|
||||
|
||||
pub async fn read_all_for_user(
|
||||
user_id: &i64,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Vec<Login>, sqlx::Error> {
|
||||
login::read_all_for_user(user_id, database_connection).await
|
||||
}
|
||||
|
||||
pub async fn delete_all_for_user(
|
||||
user_id: &i64,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<Vec<Login>, sqlx::Error> {
|
||||
login::delete_all_for_user(user_id, database_connection).await
|
||||
}
|
||||
|
||||
pub async fn count_all_for_user(
|
||||
user_id: &i64,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> Result<u64, sqlx::Error> {
|
||||
login::count_all_for_user(user_id, database_connection).await
|
||||
}
|
||||
}
|
15
src/lib.rs
15
src/lib.rs
|
@ -6,9 +6,17 @@ pub mod routing;
|
|||
pub mod server;
|
||||
pub mod utils;
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use feature::auth::OneTimePassword;
|
||||
use sqlx::{Pool, Postgres};
|
||||
use tokio::sync::RwLock;
|
||||
use utils::naive_toml_parser;
|
||||
|
||||
pub static SERVER_CONFIG: LazyLock<ServerConfig> = LazyLock::new(ServerConfig::default);
|
||||
pub static ONE_TIME_PASSWORDS: LazyLock<RwLock<Vec<OneTimePassword>>> =
|
||||
LazyLock::new(OneTimePassword::init);
|
||||
|
||||
const DATABASE_CONFIG_FILE_LOCATION: &str = "./configs/database_config.toml";
|
||||
const SERVER_CONFIG_FILE_LOCATION: &str = "./configs/server_config.toml";
|
||||
|
||||
|
@ -43,15 +51,22 @@ impl Default for DatabaseConfig {
|
|||
#[derive(Debug)]
|
||||
pub struct ServerConfig {
|
||||
pub address: String,
|
||||
pub otp_time_limit: usize,
|
||||
pub login_token_time_limit: usize,
|
||||
pub concurrency_limit: usize,
|
||||
}
|
||||
|
||||
impl Default for ServerConfig {
|
||||
fn default() -> Self {
|
||||
let (header, mut server_configs) = naive_toml_parser(SERVER_CONFIG_FILE_LOCATION);
|
||||
let value_or_max = |value: String| value.parse().map_or(usize::MAX, |value| value);
|
||||
|
||||
if header == "[server_config]" {
|
||||
Self {
|
||||
address: server_configs.pop_front().unwrap().parse().unwrap(),
|
||||
otp_time_limit: value_or_max(server_configs.pop_front().unwrap()),
|
||||
login_token_time_limit: value_or_max(server_configs.pop_front().unwrap()),
|
||||
concurrency_limit: value_or_max(server_configs.pop_front().unwrap()),
|
||||
}
|
||||
} else {
|
||||
panic!("Server Config File Must Include [server_config] at the First Line")
|
||||
|
|
57
src/mail.rs
57
src/mail.rs
|
@ -4,9 +4,11 @@ use lettre::{
|
|||
AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
|
||||
};
|
||||
|
||||
use crate::utils::naive_toml_parser;
|
||||
use crate::{error::ForumMailError, feature::auth::OneTimePassword, utils::naive_toml_parser};
|
||||
|
||||
const MAIL_CONFIG_FILE_LOCATION: &str = "./configs/mail_config.toml";
|
||||
const ONE_TIME_PASSWORD_MAIL_TEMPLATE_FILE_LOCATION: &str =
|
||||
"./mail_templates/one_time_password.toml";
|
||||
|
||||
pub struct MailConfig {
|
||||
name: String,
|
||||
|
@ -38,7 +40,58 @@ impl Default for MailConfig {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn send_mail(
|
||||
pub struct MailFieldsOneTimePassword {
|
||||
receiver_name: String,
|
||||
one_time_password: OneTimePassword,
|
||||
}
|
||||
|
||||
impl MailFieldsOneTimePassword {
|
||||
pub fn new(receiver_name: &String, one_time_password: &OneTimePassword) -> Self {
|
||||
Self {
|
||||
receiver_name: receiver_name.to_owned(),
|
||||
one_time_password: one_time_password.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub enum MailTemplate {
|
||||
OneTimePassword(MailFieldsOneTimePassword),
|
||||
}
|
||||
|
||||
impl MailTemplate {
|
||||
pub async fn send_mail(
|
||||
&self,
|
||||
receiver: &String,
|
||||
) -> Result<smtp::response::Response, ForumMailError> {
|
||||
match self {
|
||||
MailTemplate::OneTimePassword(mail_fields) => {
|
||||
let mut mail_template_from_file =
|
||||
naive_toml_parser(ONE_TIME_PASSWORD_MAIL_TEMPLATE_FILE_LOCATION);
|
||||
if mail_template_from_file.0 == "one_time_password" {
|
||||
let subject = match mail_template_from_file.1.pop_front() {
|
||||
Some(subject) => subject,
|
||||
None => return Err(ForumMailError::TemplateLackOfParameter),
|
||||
};
|
||||
let body = match mail_template_from_file.1.pop_front() {
|
||||
Some(body) => body,
|
||||
None => return Err(ForumMailError::TemplateLackOfParameter),
|
||||
};
|
||||
|
||||
let body = body.replacen('*', &mail_fields.receiver_name, 1);
|
||||
let body =
|
||||
body.replacen('*', &mail_fields.one_time_password.one_time_password, 1);
|
||||
|
||||
send_mail(receiver, &subject, &body)
|
||||
.await
|
||||
.map_err(|error| ForumMailError::Send(error.to_string()))
|
||||
} else {
|
||||
Err(ForumMailError::TemplateHeader)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_mail(
|
||||
receiver: &String,
|
||||
subject: &String,
|
||||
body: &String,
|
||||
|
|
|
@ -2,6 +2,7 @@ pub mod comment;
|
|||
pub mod comment_interaction;
|
||||
pub mod contact;
|
||||
pub mod interaction;
|
||||
pub mod login;
|
||||
pub mod permission;
|
||||
pub mod post;
|
||||
pub mod post_interaction;
|
||||
|
@ -18,7 +19,7 @@ use tower_http::cors::CorsLayer;
|
|||
|
||||
use crate::{database, AppState};
|
||||
|
||||
pub async fn route(State(app_state): State<AppState>) -> Router {
|
||||
pub async fn route(concurrency_limit: &usize, State(app_state): State<AppState>) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(alive))
|
||||
.nest(
|
||||
|
@ -70,7 +71,7 @@ pub async fn route(State(app_state): State<AppState>) -> Router {
|
|||
routing_permission::route(axum::extract::State(app_state.clone())),
|
||||
)
|
||||
.layer(CorsLayer::permissive())
|
||||
.layer(ConcurrencyLimitLayer::new(100))
|
||||
.layer(ConcurrencyLimitLayer::new(*concurrency_limit))
|
||||
.with_state(app_state)
|
||||
}
|
||||
|
||||
|
|
148
src/routing/login.rs
Normal file
148
src/routing/login.rs
Normal file
|
@ -0,0 +1,148 @@
|
|||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{delete, get, patch, post},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
feature::{auth::OneTimePassword, login::Login},
|
||||
AppState,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct CreateLogin {
|
||||
pub one_time_password: OneTimePassword,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct UpdateLogin {
|
||||
pub user_id: i64,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
pub fn route(State(app_state): State<AppState>) -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", post(create))
|
||||
.route("/users/:user_id/token/:token", get(read))
|
||||
.route("/", patch(update))
|
||||
.route("/users/:user_id/token/:token", delete(delete_))
|
||||
.route("/users/:user_id", get(read_all_for_user))
|
||||
.route("/users/:user_id", delete(delete_all_for_user))
|
||||
.route("/count/users/:user_id", get(count_all_for_user))
|
||||
.with_state(app_state)
|
||||
}
|
||||
|
||||
async fn create(
|
||||
State(app_state): State<AppState>,
|
||||
Json(create_login): Json<CreateLogin>,
|
||||
) -> impl IntoResponse {
|
||||
match OneTimePassword::verify(&create_login.one_time_password).await {
|
||||
true => {
|
||||
match Login::create(
|
||||
&create_login.one_time_password.user_id,
|
||||
&app_state.database_connection,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(login) => (StatusCode::CREATED, Json(serde_json::json!(login))),
|
||||
Err(err_val) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!(err_val.to_string())),
|
||||
),
|
||||
}
|
||||
}
|
||||
false => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!(
|
||||
"One Time Password Authentication Failed".to_string()
|
||||
)),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn read(
|
||||
State(app_state): State<AppState>,
|
||||
Path((user_id, token)): Path<(i64, String)>,
|
||||
) -> impl IntoResponse {
|
||||
match Login::read(&user_id, &token, &app_state.database_connection).await {
|
||||
Ok(login) => (StatusCode::OK, Json(serde_json::json!(login))),
|
||||
Err(err_val) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!(err_val.to_string())),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn update(
|
||||
State(app_state): State<AppState>,
|
||||
Json(update_role): Json<UpdateLogin>,
|
||||
) -> impl IntoResponse {
|
||||
match Login::update(
|
||||
&update_role.user_id,
|
||||
&update_role.token,
|
||||
&app_state.database_connection,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(login) => (StatusCode::ACCEPTED, Json(serde_json::json!(login))),
|
||||
Err(err_val) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!(err_val.to_string())),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_(
|
||||
State(app_state): State<AppState>,
|
||||
Path((user_id, token)): Path<(i64, String)>,
|
||||
) -> impl IntoResponse {
|
||||
match Login::delete(&user_id, &token, &app_state.database_connection).await {
|
||||
Ok(login) => (StatusCode::NO_CONTENT, Json(serde_json::json!(login))),
|
||||
Err(err_val) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!(err_val.to_string())),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_all_for_user(
|
||||
State(app_state): State<AppState>,
|
||||
Path(user_id): Path<i64>,
|
||||
) -> impl IntoResponse {
|
||||
match Login::read_all_for_user(&user_id, &app_state.database_connection).await {
|
||||
Ok(logins) => (StatusCode::OK, Json(serde_json::json!(logins))),
|
||||
Err(err_val) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!(err_val.to_string())),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete_all_for_user(
|
||||
State(app_state): State<AppState>,
|
||||
Path(user_id): Path<i64>,
|
||||
) -> impl IntoResponse {
|
||||
match Login::delete_all_for_user(&user_id, &app_state.database_connection).await {
|
||||
Ok(logins) => (StatusCode::OK, Json(serde_json::json!(logins))),
|
||||
Err(err_val) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!(err_val.to_string())),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn count_all_for_user(
|
||||
State(app_state): State<AppState>,
|
||||
Path(user_id): Path<i64>,
|
||||
) -> impl IntoResponse {
|
||||
match Login::count_all_for_user(&user_id, &app_state.database_connection).await {
|
||||
Ok(login_count) => (StatusCode::OK, Json(serde_json::json!(login_count))),
|
||||
Err(err_val) => (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(serde_json::json!(err_val.to_string())),
|
||||
),
|
||||
}
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
use tokio::net::TcpListener;
|
||||
|
||||
use crate::{AppState, ServerConfig};
|
||||
use crate::{AppState, SERVER_CONFIG};
|
||||
|
||||
pub async fn start_server(app_state: AppState) {
|
||||
let server_config = ServerConfig::default();
|
||||
let server_config = &SERVER_CONFIG;
|
||||
|
||||
let router = crate::routing::route(axum::extract::State(app_state)).await;
|
||||
let router = crate::routing::route(
|
||||
&server_config.concurrency_limit,
|
||||
axum::extract::State(app_state),
|
||||
)
|
||||
.await;
|
||||
let listener = TcpListener::bind(&server_config.address).await.unwrap();
|
||||
println!("\n\thttp://{}", server_config.address);
|
||||
axum::serve(listener, router).await.unwrap()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue