Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f63131ebc7 | ||
|
|
6c1c13e088 | ||
|
|
834a0b7aa4 | ||
|
|
c4d374dbfe |
Generated
+16
@@ -150,6 +150,8 @@ dependencies = [
|
|||||||
"sqlx",
|
"sqlx",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2243,6 +2245,20 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ chrono = { version = "0.4.44", features = ["serde"] }
|
|||||||
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
|
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
|
||||||
argon2 = "0.5.3"
|
argon2 = "0.5.3"
|
||||||
time = "0.3.47"
|
time = "0.3.47"
|
||||||
|
tower = "0.5.3"
|
||||||
|
tower-http = { version = "0.6.8", features = ["cors"] }
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
pub mod jwt;
|
pub mod jwt;
|
||||||
|
pub mod validation;
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
Json,
|
||||||
|
body::Body,
|
||||||
|
extract::State,
|
||||||
|
http::{Request, StatusCode, header},
|
||||||
|
middleware::Next,
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
|
use jsonwebtoken::DecodingKey;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{AppState, cookie::jwt::decode_token, models::LoginModel};
|
||||||
|
|
||||||
|
pub async fn validate_token(
|
||||||
|
cookies: CookieJar,
|
||||||
|
State(data): State<Arc<AppState>>,
|
||||||
|
mut request: Request<Body>,
|
||||||
|
next: Next,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
let token = cookies
|
||||||
|
.get("token")
|
||||||
|
.map(|cookie| cookie.value().to_string())
|
||||||
|
.or_else(|| {
|
||||||
|
request
|
||||||
|
.headers()
|
||||||
|
.get(header::AUTHORIZATION)
|
||||||
|
.and_then(|header| header.to_str().ok())
|
||||||
|
.and_then(|value| {
|
||||||
|
if value.starts_with("Bearer ") {
|
||||||
|
Some(value[7..].to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let token = token.ok_or_else(|| {
|
||||||
|
let error = json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": "Please provide a valid token"
|
||||||
|
});
|
||||||
|
(StatusCode::UNAUTHORIZED, Json(error))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let claims = decode_token(
|
||||||
|
token,
|
||||||
|
&DecodingKey::from_secret(data.env.token_secret.as_ref()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let uuid = (&claims.subject).parse::<i64>().map_err(|_| {
|
||||||
|
let error = json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": "Invalid user id"
|
||||||
|
});
|
||||||
|
(StatusCode::UNAUTHORIZED, Json(error))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let user = sqlx::query_as::<_, LoginModel>(r#"SELECT * FROM users WHERE id = $1"#)
|
||||||
|
.bind(uuid)
|
||||||
|
.fetch_optional(&data.db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
let error = json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": format!("Database error: {}", e)
|
||||||
|
});
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(error))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let user = user.ok_or_else(|| {
|
||||||
|
let error = json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": "Invalid user"
|
||||||
|
});
|
||||||
|
(StatusCode::UNAUTHORIZED, Json(error))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
request.extensions_mut().insert(user);
|
||||||
|
Ok(next.run(request).await)
|
||||||
|
}
|
||||||
@@ -2,15 +2,18 @@
|
|||||||
pub struct Env {
|
pub struct Env {
|
||||||
pub db_url: String,
|
pub db_url: String,
|
||||||
pub token_secret: String,
|
pub token_secret: String,
|
||||||
|
pub origin: String
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Env {
|
impl Env {
|
||||||
pub fn load() -> Env {
|
pub fn load() -> Env {
|
||||||
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
let db_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
let token_secret = std::env::var("TOKEN_SECRET").expect("TOKEN_SECRET must be set");
|
let token_secret = std::env::var("TOKEN_SECRET").expect("TOKEN_SECRET must be set");
|
||||||
|
let origin = std::env::var("ORIGIN").expect("ORIGIN must be set")
|
||||||
Env {
|
Env {
|
||||||
db_url,
|
db_url,
|
||||||
token_secret,
|
token_secret,
|
||||||
|
origin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use argon2::{
|
|||||||
password_hash::{SaltString, rand_core::OsRng},
|
password_hash::{SaltString, rand_core::OsRng},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
Json,
|
Extension, Json,
|
||||||
extract::State,
|
extract::{Path, State},
|
||||||
http::{Response, StatusCode, header},
|
http::{Response, StatusCode, header},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
@@ -18,7 +18,7 @@ use serde_json::json;
|
|||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
cookie::jwt::encode_token,
|
cookie::jwt::encode_token,
|
||||||
models::{FilteredUser, LoginModel, LoginScheme, UserCreateScheme},
|
models::{FilteredUser, LoginScheme, User, UserCreateScheme, UserUpdateScheme},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn create_user(
|
pub async fn create_user(
|
||||||
@@ -88,7 +88,7 @@ pub async fn login(
|
|||||||
State(data): State<Arc<AppState>>,
|
State(data): State<Arc<AppState>>,
|
||||||
Json(request): Json<LoginScheme>,
|
Json(request): Json<LoginScheme>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
let user = sqlx::query_as::<_, LoginModel>(r#"SELECT * FROM users WHERE username = $1"#)
|
let user = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE username = $1"#)
|
||||||
.bind(request.username)
|
.bind(request.username)
|
||||||
.fetch_optional(&data.db)
|
.fetch_optional(&data.db)
|
||||||
.await
|
.await
|
||||||
@@ -131,7 +131,7 @@ pub async fn login(
|
|||||||
.http_only(true);
|
.http_only(true);
|
||||||
|
|
||||||
let mut response = Response::new(
|
let mut response = Response::new(
|
||||||
json!({"status": "success", "token": token, "user": filter_users(&user)}).to_string(),
|
json!({"status": "success", "token": token, "user": filter_user(&user)}).to_string(),
|
||||||
);
|
);
|
||||||
response
|
response
|
||||||
.headers_mut()
|
.headers_mut()
|
||||||
@@ -155,11 +155,159 @@ pub async fn logout() -> Result<impl IntoResponse, (StatusCode, Json<serde_json:
|
|||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter_users(user: &LoginModel) -> FilteredUser {
|
pub async fn get_current_user(
|
||||||
|
Extension(state): Extension<User>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
let response = json!({
|
||||||
|
"status": "success",
|
||||||
|
"data": json!({
|
||||||
|
"first_name": filter_user(&state).first_name,
|
||||||
|
"last_name": filter_user(&state).last_name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_user(
|
||||||
|
Path(id): Path<i32>,
|
||||||
|
State(data): State<Arc<AppState>>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
let query = sqlx::query(r#"DELETE FROM users WHERE id = $1"#)
|
||||||
|
.bind(id)
|
||||||
|
.execute(&data.db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({"status": "error", "message": format!("{:?}", e)})),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if query.rows_affected() == 0 {
|
||||||
|
let error = json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": format!("User with ID {} not found", id)
|
||||||
|
});
|
||||||
|
return Err((StatusCode::NOT_FOUND, Json(error)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(StatusCode::NO_CONTENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_users(
|
||||||
|
State(data): State<Arc<AppState>>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
let users = sqlx::query_as::<_, User>(r#"SELECT * FROM users ORDER BY last_name ASC"#)
|
||||||
|
.fetch_all(&data.db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
let error = json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": format!("{:?}", e)
|
||||||
|
});
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(error))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let response = users
|
||||||
|
.iter()
|
||||||
|
.map(|user| filter_user(&user))
|
||||||
|
.collect::<Vec<FilteredUser>>();
|
||||||
|
let json_respnse = json!(response);
|
||||||
|
Ok(Json(json_respnse))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_by_id(
|
||||||
|
Path(id): Path<i32>,
|
||||||
|
State(data): State<Arc<AppState>>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
let query = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE id = $1"#)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&data.db)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match query {
|
||||||
|
Ok(user) => {
|
||||||
|
let response = serde_json::json!(filter_user(&user));
|
||||||
|
return Ok(Json(response));
|
||||||
|
}
|
||||||
|
Err(sqlx::Error::RowNotFound) => {
|
||||||
|
let error_response = serde_json::json!({
|
||||||
|
"status": "fail",
|
||||||
|
"message": format!("User with ID {} not found", id)
|
||||||
|
});
|
||||||
|
return Err((StatusCode::NOT_FOUND, Json(error_response)));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({"status": "error", "message": format!("{:?}", e)})),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_user(
|
||||||
|
Path(id): Path<i32>,
|
||||||
|
State(data): State<Arc<AppState>>,
|
||||||
|
Json(body): Json<UserUpdateScheme>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
|
||||||
|
let argon = Argon2::default();
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
let hashed_pwd = match argon.hash_password(body.new_pwd.clone().as_bytes(), &salt) {
|
||||||
|
Ok(h) => h.to_string(),
|
||||||
|
Err(e) => panic!("Error hashing {:}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
let update_result = sqlx::query(r#"UPDATE users SET first_name = $1, last_name = $2, username = $3, pwd = $4, is_admin = $5 WHERE id = $6"#)
|
||||||
|
.bind(body.first_name.to_owned())
|
||||||
|
.bind(body.last_name.to_owned())
|
||||||
|
.bind(body.username.to_owned())
|
||||||
|
.bind(hashed_pwd)
|
||||||
|
.bind(body.make_admin.to_owned())
|
||||||
|
.bind(id)
|
||||||
|
.execute(&data.db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({"status": "error", "message": format!("{:?}", e)})),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if update_result.rows_affected() == 0 {
|
||||||
|
let error_response = serde_json::json!({
|
||||||
|
"status": "error",
|
||||||
|
"message": format!("User with ID {} not found", id)
|
||||||
|
});
|
||||||
|
return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated_user = sqlx::query_as::<_, User>(r#"SELECT * FROM users WHERE id = $1"#)
|
||||||
|
.bind(id)
|
||||||
|
.fetch_one(&data.db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(json!({"status": "error", "message": format!("{:?}", e)})),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let response = serde_json::json!({
|
||||||
|
"user": filter_user(&updated_user),
|
||||||
|
"status": "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_user(user: &User) -> FilteredUser {
|
||||||
FilteredUser {
|
FilteredUser {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
first_name: user.first_name.clone(),
|
first_name: user.first_name.clone(),
|
||||||
last_name: user.last_name.clone(),
|
last_name: user.last_name.clone(),
|
||||||
|
username: user.username.clone(),
|
||||||
is_admin: user.is_admin.clone(),
|
is_admin: user.is_admin.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-3
@@ -6,12 +6,20 @@ mod models;
|
|||||||
mod router;
|
mod router;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{Router, routing};
|
use axum::{
|
||||||
|
Router,
|
||||||
|
http::{
|
||||||
|
HeaderValue, Method,
|
||||||
|
header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE},
|
||||||
|
},
|
||||||
|
routing,
|
||||||
|
};
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use models::*;
|
use models::*;
|
||||||
use router::create_router;
|
use router::create_router;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{PgPool, postgres::PgPoolOptions};
|
use sqlx::{PgPool, postgres::PgPoolOptions};
|
||||||
|
use tower_http::cors::CorsLayer;
|
||||||
|
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
|
|
||||||
@@ -24,7 +32,7 @@ pub struct AppState {
|
|||||||
async fn main() {
|
async fn main() {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
let env = Env::load();
|
let env = Env::load();
|
||||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL variable not set");
|
let database_url = &env.db_url;
|
||||||
let pool = match PgPoolOptions::new().connect(&database_url).await {
|
let pool = match PgPoolOptions::new().connect(&database_url).await {
|
||||||
Ok(pool) => {
|
Ok(pool) => {
|
||||||
println!("Database connection successful");
|
println!("Database connection successful");
|
||||||
@@ -35,10 +43,19 @@ async fn main() {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cors = CorsLayer::new()
|
||||||
|
.allow_origin(env.origin.parse::<HeaderValue>().unwrap())
|
||||||
|
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
|
||||||
|
.allow_credentials(true)
|
||||||
|
.allow_headers([AUTHORIZATION, ACCEPT, CONTENT_TYPE]);
|
||||||
|
|
||||||
let app = create_router(Arc::new(AppState {
|
let app = create_router(Arc::new(AppState {
|
||||||
db: pool.clone(),
|
db: pool.clone(),
|
||||||
env: env.clone(),
|
env: env.clone(),
|
||||||
}));
|
}))
|
||||||
|
.layer(cors);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8001").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:8001").await.unwrap();
|
||||||
axum::serve(listener, app).await;
|
axum::serve(listener, app).await;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-10
@@ -27,11 +27,12 @@ pub struct TicketResponse {
|
|||||||
pub user_id: i16,
|
pub user_id: i16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, PartialEq, Debug)]
|
#[derive(Deserialize, Serialize, PartialEq, Debug, Clone, sqlx::FromRow)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: i16,
|
pub id: i16,
|
||||||
pub last_name: String,
|
pub last_name: String,
|
||||||
pub first_name: String,
|
pub first_name: String,
|
||||||
|
pub username: String,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
pub pwd: String,
|
pub pwd: String,
|
||||||
}
|
}
|
||||||
@@ -49,6 +50,16 @@ pub struct TicketUpdateScheme {
|
|||||||
pub status: String,
|
pub status: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct UserUpdateScheme {
|
||||||
|
pub id: i16,
|
||||||
|
pub first_name: String,
|
||||||
|
pub last_name: String,
|
||||||
|
pub username: String,
|
||||||
|
pub make_admin: bool,
|
||||||
|
pub new_pwd: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, sqlx::FromRow)]
|
#[derive(Deserialize, Serialize, Debug, sqlx::FromRow)]
|
||||||
pub struct UserCreateScheme {
|
pub struct UserCreateScheme {
|
||||||
pub first_name: String,
|
pub first_name: String,
|
||||||
@@ -64,20 +75,12 @@ pub struct LoginScheme {
|
|||||||
pub pwd: String,
|
pub pwd: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, sqlx::FromRow)]
|
|
||||||
pub struct LoginModel {
|
|
||||||
pub id: i16,
|
|
||||||
pub last_name: String,
|
|
||||||
pub first_name: String,
|
|
||||||
pub is_admin: bool,
|
|
||||||
pub pwd: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct FilteredUser {
|
pub struct FilteredUser {
|
||||||
pub id: i16,
|
pub id: i16,
|
||||||
pub first_name: String,
|
pub first_name: String,
|
||||||
pub last_name: String,
|
pub last_name: String,
|
||||||
|
pub username: String,
|
||||||
pub is_admin: bool,
|
pub is_admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -8,7 +8,10 @@ use axum::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
handlers::{
|
handlers::{
|
||||||
auth::{create_user, login, logout},
|
auth::{
|
||||||
|
create_user, delete_user, get_current_user, get_user_by_id, get_users, login, logout,
|
||||||
|
update_user,
|
||||||
|
},
|
||||||
ticket::{create_ticket, delete_ticket, edit_ticket, get_ticket_by_id, get_tickets},
|
ticket::{create_ticket, delete_ticket, edit_ticket, get_ticket_by_id, get_tickets},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -26,5 +29,11 @@ pub fn create_router(state: Arc<AppState>) -> Router {
|
|||||||
.route("/api/register", post(create_user))
|
.route("/api/register", post(create_user))
|
||||||
.route("/api/login", post(login))
|
.route("/api/login", post(login))
|
||||||
.route("/api/logout", get(logout))
|
.route("/api/logout", get(logout))
|
||||||
|
.route("/api/users", get(get_users))
|
||||||
|
.route("/api/users/current", get(get_current_user))
|
||||||
|
.route(
|
||||||
|
"/api/users/{id}",
|
||||||
|
get(get_user_by_id).delete(delete_user).patch(update_user),
|
||||||
|
)
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user