diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a0a3934..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "todo-tree.tree.showBadges": true -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 88381fe..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "type": "cargo", - "command": "build", - "problemMatcher": [ - "$rustc" - ], - "group": "build", - "label": "rust: cargo build" - } - ] -} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 639f4f3..0000000 --- a/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -FROM rust:latest AS builder -WORKDIR /app -COPY . . -RUN cargo build --release - -FROM debian:bookworm-slim - -# Create the app user with UID 1001 -RUN useradd -u 1001 -m appuser - -# Create working directory (only needed if your app expects /app) -WORKDIR /app - -# Copy binary from builder -COPY --from=builder /app/target/release/hotel-api-rs /usr/local/bin/hotel-api-rs - -# Switch to non-root user -USER 1001 - -# Expose API port -EXPOSE 8080 - -CMD ["/usr/local/bin/hotel-api-rs"] diff --git a/authbruh.rs b/authbruh.rs deleted file mode 100644 index 3701d3f..0000000 --- a/authbruh.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::time::Duration; -use axum::{ - body::{to_bytes, Body}, - http::{Request as HttpRequest, StatusCode}, - middleware::Next, - response::{Response, IntoResponse}, - Json, - extract::{Path, State, FromRequest} -}; -use axum::extract::Request as ExtractRequest; -use jsonwebtoken::{decode, DecodingKey, Validation, encode, EncodingKey, Header}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use chrono::{Utc}; -use rusqlite::{params, Connection, OptionalExtension}; - -//use crate::utils::db_pool::; -use crate::utils::db_pool::{ - HotelPool, - AppState, -}; - - -pub async fn clean_auth_loging( - State(state): State, - // LoginPayload(payload): LoginPayload, -) -> impl IntoResponse { - // You can access state.pool and state.jwt_secret here - format!("Got secret: {}", state.jwt_secret) -} - - -fn internal_error(err: E) -> (StatusCode, String) { - (StatusCode::INTERNAL_SERVER_ERROR, format!("Internal error: {}", err)) -} - - diff --git a/db/1.sqlite b/db/1.sqlite index 9dad92e..6db1f38 100644 Binary files a/db/1.sqlite and b/db/1.sqlite differ diff --git a/db/1.sqlite-shm b/db/1.sqlite-shm deleted file mode 100644 index 1168272..0000000 Binary files a/db/1.sqlite-shm and /dev/null differ diff --git a/db/1.sqlite-wal b/db/1.sqlite-wal deleted file mode 100644 index 715afd3..0000000 Binary files a/db/1.sqlite-wal and /dev/null differ diff --git a/db/2.sqlite b/db/2.sqlite deleted file mode 100644 index e69de29..0000000 diff --git a/db/3.sqlite b/db/3.sqlite deleted file mode 100644 index e69de29..0000000 diff --git a/db/auth_copy_2.sqlite b/db/auth_copy_2.sqlite index 036d95c..a591b40 100644 Binary files a/db/auth_copy_2.sqlite and b/db/auth_copy_2.sqlite differ diff --git a/db/auth_copy_2.sqlite-shm b/db/auth_copy_2.sqlite-shm deleted file mode 100644 index 91140f8..0000000 Binary files a/db/auth_copy_2.sqlite-shm and /dev/null differ diff --git a/db/auth_copy_2.sqlite-wal b/db/auth_copy_2.sqlite-wal deleted file mode 100644 index d767ff7..0000000 Binary files a/db/auth_copy_2.sqlite-wal and /dev/null differ diff --git a/db/hotel_schema.sql b/db/hotel_schema.sql deleted file mode 100644 index be20ae9..0000000 --- a/db/hotel_schema.sql +++ /dev/null @@ -1,51 +0,0 @@ -CREATE TABLE rooms ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - number TEXT NOT NULL UNIQUE, - status TEXT NOT NULL DEFAULT 'clean' -); - -CREATE TABLE IF NOT EXISTS "conversation" ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, - `creator_id` INTEGER, - `title` TEXT, - `created_at` TEXT DEFAULT (CURRENT_TIMESTAMP) -); - -CREATE TABLE IF NOT EXISTS "message" ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT, - `conversation_id` INTEGER REFERENCES `conversation`(`id`), - `sender_id` INTEGER NOT NULL, `content` TEXT NOT NULL, - `sent_at` TEXT DEFAULT (CURRENT_TIMESTAMP) -); - -CREATE TABLE "conversation_participants" ( - `conversation_id` INTEGER NOT NULL REFERENCES `conversation`(`id`), - `user_id` INTEGER NOT NULL, - `joined_at` TEXT DEFAULT (CURRENT_TIMESTAMP) - PRIMARY KEY (conversation_id, user_id) -); - -CREATE TABLE IF NOT EXISTS "inventory" ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT, - `amount` INTEGER, - `item_name` TEXT, - `user_id` INT, - `updated_at` TEXT DEFAULT (CURRENT_TIMESTAMP) - ); - -CREATE TABLE IF NOT EXISTS "room_history" ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT, - `room_id` INTEGER NOT NULL REFERENCES `rooms`(`id`), - `room_number` TEXT NOT NULL, - `status` TEXT, - `updated_at` TEXT DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS "inventory_history" ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, - `item_id` INTEGER NOT NULL REFERENCES `inventory`(`id`), - `amount` INTEGER NOT NULL, - `item_name` TEXT, - `user_id` INT, - `updated_at` TEXT DEFAULT (CURRENT_TIMESTAMP)); - diff --git a/db/1 copy.sqlite b/db/test_backup/2 copy.sqlite similarity index 100% rename from db/1 copy.sqlite rename to db/test_backup/2 copy.sqlite diff --git a/db/auth.sqlite b/db/test_backup/auth_2.sqlite similarity index 100% rename from db/auth.sqlite rename to db/test_backup/auth_2.sqlite diff --git a/src/chat/extractor.rs b/src/chat/extractor.rs index 8d7fb45..3b64ea2 100644 --- a/src/chat/extractor.rs +++ b/src/chat/extractor.rs @@ -1,17 +1,13 @@ use axum::{ - extract::{ - FromRequest, Request - }, - http::StatusCode, Json, + extract::{FromRequest, Request}, + http::StatusCode, }; use chrono::NaiveDateTime; use serde::Deserialize; - - #[derive(Deserialize, Debug)] -pub struct CreateConversationValues{ +pub struct CreateConversationValues { //pub creator_id: i32, // already in token ? pub name: String, } @@ -19,7 +15,8 @@ pub struct CreateConversationValues{ pub struct CreateConversationPayload(pub CreateConversationValues); impl FromRequest for CreateConversationPayload -where S: Send + Sync, +where + S: Send + Sync, { type Rejection = (StatusCode, String); @@ -27,23 +24,22 @@ where S: Send + Sync, let Json(payload) = Json::::from_request(req, state) .await .map_err(|err| (StatusCode::BAD_REQUEST, format!("Invalid body: {}", err)))?; - + Ok(CreateConversationPayload(payload)) - } } - #[derive(Deserialize, Debug)] -pub struct AddUserConversationValues{ +pub struct AddUserConversationValues { pub conv_id: u32, - pub users: Vec + pub users: Vec, } pub struct AddUserConversationPayload(pub AddUserConversationValues); impl FromRequest for AddUserConversationPayload -where S: Send + Sync, +where + S: Send + Sync, { type Rejection = (StatusCode, String); @@ -51,14 +47,13 @@ where S: Send + Sync, let Json(payload) = Json::::from_request(req, state) .await .map_err(|err| (StatusCode::BAD_REQUEST, format!("Invalid body: {}", err)))?; - + Ok(AddUserConversationPayload(payload)) - } } #[derive(Deserialize, Debug)] -pub struct SendMessageValues{ +pub struct SendMessageValues { pub conv_id: u32, pub message: String, } @@ -66,7 +61,8 @@ pub struct SendMessageValues{ pub struct SendMessagePayload(pub SendMessageValues); impl FromRequest for SendMessagePayload -where S: Send + Sync, +where + S: Send + Sync, { type Rejection = (StatusCode, String); @@ -74,23 +70,22 @@ where S: Send + Sync, let Json(payload) = Json::::from_request(req, state) .await .map_err(|err| (StatusCode::BAD_REQUEST, format!("Invalid body: {}", err)))?; - + Ok(SendMessagePayload(payload)) - } } - #[derive(Deserialize, Debug)] -pub struct GetMessagesValues{ +pub struct GetMessagesValues { pub conv_id: u32, - pub timestamp :Option, + pub timestamp: Option, } pub struct GetMessagesPayload(pub GetMessagesValues); impl FromRequest for GetMessagesPayload -where S: Send + Sync, +where + S: Send + Sync, { type Rejection = (StatusCode, String); @@ -98,9 +93,7 @@ where S: Send + Sync, let Json(payload) = Json::::from_request(req, state) .await .map_err(|err| (StatusCode::BAD_REQUEST, format!("Invalid body: {}", err)))?; - + Ok(GetMessagesPayload(payload)) - } } - diff --git a/src/chat/handlers.rs b/src/chat/handlers.rs index 842f1c1..7db2b71 100644 --- a/src/chat/handlers.rs +++ b/src/chat/handlers.rs @@ -1,40 +1,35 @@ - use std::collections::{HashMap, HashSet}; use axum::{ - Json, extract::{ - FromRequest,FromRequestParts, State, Path, - - }, http::StatusCode, response::{IntoResponse, sse::KeepAlive} - + Json, + extract::{FromRequest, FromRequestParts, Path, State}, + http::StatusCode, + response::{IntoResponse, sse::KeepAlive}, }; //use axum::extract::ws::Message; -use chrono::NaiveDateTime; -use rusqlite::{Name, Statement, params}; use rusqlite::OptionalExtension; +use rusqlite::{Name, Statement, params}; use serde::Serialize; use serde_json::{json, to_value}; use crate::chat::extractor::{ -AddUserConversationPayload, CreateConversationPayload, GetMessagesPayload, SendMessagePayload + AddUserConversationPayload, CreateConversationPayload, GetMessagesPayload, SendMessagePayload, }; -use crate::utils::db_pool::{AppState}; use crate::utils::auth::AuthClaims; - +use crate::utils::db_pool::AppState; //TODO: update conversation title -//FIXME: make a default title if empty +//FIXME: make a default title if empty pub async fn create_conversation( State(state): State, - AuthClaims {user_id, hotel_id}: AuthClaims, - CreateConversationPayload(payload): CreateConversationPayload + AuthClaims { user_id, hotel_id }: AuthClaims, + CreateConversationPayload(payload): CreateConversationPayload, ) -> impl IntoResponse { - let pool = state.hotel_pools.get_pool(hotel_id); - let conn = match pool.get(){ + let conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error")) + Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error")), }; let result = conn.execute( @@ -47,7 +42,7 @@ pub async fn create_conversation( Err(err) => { return ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Error when creating the conversation: {err}") + format!("Error when creating the conversation: {err}"), ); } }; @@ -55,62 +50,67 @@ pub async fn create_conversation( if rows == 0 { return ( StatusCode::NOT_FOUND, - "not able to create the conversation".to_string() + "not able to create the conversation".to_string(), ); } - let conv_id = conn.last_insert_rowid(); let user_conv_rel = conn.execute( "INSERT INTO conversation_participants (conversation_id, user_id, name) VALUES (?1, ?2, ?3)", - params![conv_id, user_id, payload.name] + params![conv_id, user_id, payload.name], ); match user_conv_rel { - Ok(r) => return (StatusCode::OK, format!("Created conversation {}", payload.name)), - Err(err) => return ( + Ok(r) => { + return ( + StatusCode::OK, + format!("Created conversation {}", payload.name), + ); + } + Err(err) => { + return ( StatusCode::INTERNAL_SERVER_ERROR, - format!("Error when creating the conversation: {err}") - ) + format!("Error when creating the conversation: {err}"), + ); + } } // extra logic here (more queries, validations, etc.) //(StatusCode::OK, format!("Created conversation {}", payload.name)) - } //FIXME: add title to conv pub async fn add_user_to_conv( State(state): State, - AuthClaims {user_id, hotel_id}: AuthClaims, - AddUserConversationPayload(payload):AddUserConversationPayload + AuthClaims { user_id, hotel_id }: AuthClaims, + AddUserConversationPayload(payload): AddUserConversationPayload, ) -> impl IntoResponse { - let pool = state.hotel_pools.get_pool(hotel_id); - let mut conn = match pool.get(){ + let mut conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error")) + Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error")), }; let creator_name: Option = match conn - .query_row( - "SELECT name FROM conversation WHERE creator_id = ?1 AND id = ?2", - params![user_id, payload.conv_id], - |row| row.get(0), - ) - .optional() { - Ok(name) => name, - //Ok(None) => false, - Err(_) => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - "Creator check failed".to_string(), + .query_row( + "SELECT name FROM conversation WHERE creator_id = ?1 AND id = ?2", + params![user_id, payload.conv_id], + |row| row.get(0), ) - } + .optional() + { + Ok(name) => name, + //Ok(None) => false, + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Creator check failed".to_string(), + ); + } }; let is_creator = creator_name.is_some(); @@ -122,42 +122,36 @@ pub async fn add_user_to_conv( ); } - //fix this + //fix this let existing: HashSet = { - let mut stmt = match conn.prepare( - "SELECT user_id FROM conversation_participants WHERE conversation_id = ?1", - ) { - Ok(s) => s, - Err(_) => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - "Prepare participants stmt failed".to_string(), - ) - } - }; + let mut stmt = match conn + .prepare("SELECT user_id FROM conversation_participants WHERE conversation_id = ?1") + { + Ok(s) => s, + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Prepare participants stmt failed".to_string(), + ); + } + }; - match stmt.query_map(params![payload.conv_id], |row| row.get(0)) { - Ok(rows) => rows.filter_map(Result::ok).collect(), + match stmt.query_map(params![payload.conv_id], |row| row.get(0)) { + Ok(rows) => rows.filter_map(Result::ok).collect(), Err(_) => { return ( StatusCode::INTERNAL_SERVER_ERROR, "Query participants failed".to_string(), - ) + ); } } }; // ← stmt dropped HERE let payload_users: HashSet = payload.users.into_iter().collect(); - let to_add: Vec = payload_users - .difference(&existing) - .copied() - .collect(); + let to_add: Vec = payload_users.difference(&existing).copied().collect(); - let to_remove : Vec = existing - .difference(&payload_users) - .copied() - .collect(); + let to_remove: Vec = existing.difference(&payload_users).copied().collect(); let tx = match conn.transaction() { Ok(t) => t, @@ -165,11 +159,11 @@ pub async fn add_user_to_conv( return ( StatusCode::INTERNAL_SERVER_ERROR, "Transaction start failed for update conv participants".to_string(), - ) + ); } }; -for user_id in to_add { + for user_id in to_add { if let Err(err) = tx.execute( "INSERT INTO conversation_participants (conversation_id, user_id, name) VALUES (?1, ?2, ?3)", @@ -202,8 +196,7 @@ for user_id in to_add { ); } - return (StatusCode::OK, "ok".to_string()); - + return (StatusCode::OK, "ok".to_string()); } /* @@ -214,36 +207,44 @@ pub struct Get pub async fn get_conv_users( State(state): State, - AuthClaims{user_id, hotel_id}: AuthClaims, + AuthClaims { user_id, hotel_id }: AuthClaims, Path(conv_id): Path<(i32)>, ) -> impl IntoResponse { - let pool = state.hotel_pools.get_pool(hotel_id); - let conn = match pool.get (){ - Ok(c)=> c, - Err(err)=> return (StatusCode::INTERNAL_SERVER_ERROR, format!("Error opening pol connection : {err}") ) + let conn = match pool.get() { + Ok(c) => c, + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Error opening pol connection : {err}"), + ); + } }; - - let mut stmt = match conn.prepare( - "SELECT conversation_id, name FROM conversation_participants WHERE user_id = ?1", - ) { + let mut stmt = match conn + .prepare("SELECT conversation_id, name FROM conversation_participants WHERE user_id = ?1") + { Ok(s) => s, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Prepare failed: {}", e)), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Prepare failed: {}", e), + ); + } }; - //fix this + //fix this let existing: HashSet = { - let mut stmt = match conn.prepare( - "SELECT user_id FROM conversation_participants WHERE conversation_id = ?1", - ) { + let mut stmt = match conn + .prepare("SELECT user_id FROM conversation_participants WHERE conversation_id = ?1") + { Ok(s) => s, Err(_) => { return ( StatusCode::INTERNAL_SERVER_ERROR, "Prepare participants stmt failed".to_string(), - ) + ); } }; @@ -253,113 +254,123 @@ pub async fn get_conv_users( return ( StatusCode::INTERNAL_SERVER_ERROR, "Query participants failed".to_string(), - ) + ); } } }; // ← stmt dropped HERE - let present: Vec = existing - .into_iter() - .collect(); + let present: Vec = existing.into_iter().collect(); match serde_json::to_string(&present) { Ok(json) => (StatusCode::OK, json), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Serialization failed: {}", e)), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Serialization failed: {}", e), + ), } } pub async fn send_message( State(state): State, - AuthClaims {user_id, hotel_id}: AuthClaims, - SendMessagePayload(payload):SendMessagePayload + AuthClaims { user_id, hotel_id }: AuthClaims, + SendMessagePayload(payload): SendMessagePayload, ) -> impl IntoResponse { //TODO: make sur the convid is valid let pool = state.hotel_pools.get_pool(hotel_id); - let conn = match pool.get(){ + let conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error")) + Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error")), }; let mut statement = match conn.prepare( - "SELECT 1 FROM conversation_participants WHERE user_id = ?1 AND conversation_id = ?2" , - ){ + "SELECT 1 FROM conversation_participants WHERE user_id = ?1 AND conversation_id = ?2", + ) { Ok(statement) => statement, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "prepare failed".to_string()) + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "prepare failed".to_string(), + ); + } }; match statement.exists(params![user_id, payload.conv_id]) { - Ok(true) => { - // user is part of the conversation — continue - } - Ok(false) => { - // early exit: not part of the conversation - return (StatusCode::FORBIDDEN, "Not part of the conversation".to_string()); - } - Err(_) => { - // query failed - return (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()); - } -} - -let (message_id, sent_at): (i64, String) = match conn.query_row( - "INSERT INTO message (sender_id, content, conversation_id) - VALUES (?1, ?2, ?3) - RETURNING id, sent_at", - params![user_id, payload.message, payload.conv_id], - |row| Ok(( - row.get::<_, i64>(0)?, - row.get::<_, String>(1)?, - )), -) { - Ok(v) => v, - Err(err) => { - return ( - StatusCode::INTERNAL_SERVER_ERROR, - format!("DB insert failed: {err}"), - ); - } -}; - //let message_id = conn.last_insert_rowid(); -// FIXME: add sent_at and message id in the response. - - - // --- send to conversation participants --- - let mut stmt_participants = conn - .prepare("SELECT user_id FROM conversation_participants WHERE conversation_id = ?1") - .expect("prepare participants failed"); - - let participant_ids: Vec = stmt_participants - .query_map(params![payload.conv_id], |row| row.get(0)) - .expect("query_map failed") - .filter_map(Result::ok) - .collect(); - -if let Some(hotel_users) = state.ws_map.get(&hotel_id) { - let update_msg = serde_json::json!({ - "event_type": "chat_message", - "conv_id": payload.conv_id, - "sender_id": user_id, - "content": payload.message, - "id": message_id, - "sent_at": sent_at, - }) - .to_string(); - - for uid in &participant_ids { - if let Some(sender) = hotel_users.get(uid) { - let _ = sender.send( - axum::extract::ws::Message::Text(update_msg.clone().into()) + Ok(true) => { + // user is part of the conversation — continue + } + Ok(false) => { + // early exit: not part of the conversation + return ( + StatusCode::FORBIDDEN, + "Not part of the conversation".to_string(), + ); + } + Err(_) => { + // query failed + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Query failed".to_string(), ); } } -} - (StatusCode::OK, format!("sent message: {}, to users:{:?}", payload.message, participant_ids)) + let (message_id, sent_at): (i64, String) = match conn.query_row( + "INSERT INTO message (sender_id, content, conversation_id) + VALUES (?1, ?2, ?3) + RETURNING id, sent_at", + params![user_id, payload.message, payload.conv_id], + |row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)), + ) { + Ok(v) => v, + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("DB insert failed: {err}"), + ); } - //Ok(_) => (StatusCode::NOT_FOUND, "Conversation not found".to_string()), + }; + //let message_id = conn.last_insert_rowid(); + // FIXME: add sent_at and message id in the response. + // --- send to conversation participants --- + let mut stmt_participants = conn + .prepare("SELECT user_id FROM conversation_participants WHERE conversation_id = ?1") + .expect("prepare participants failed"); + let participant_ids: Vec = stmt_participants + .query_map(params![payload.conv_id], |row| row.get(0)) + .expect("query_map failed") + .filter_map(Result::ok) + .collect(); + + if let Some(hotel_users) = state.ws_map.get(&hotel_id) { + let update_msg = serde_json::json!({ + "event_type": "chat_message", + "conv_id": payload.conv_id, + "sender_id": user_id, + "content": payload.message, + "id": message_id, + "sent_at": sent_at, + }) + .to_string(); + + for uid in &participant_ids { + if let Some(sender) = hotel_users.get(uid) { + let _ = sender.send(axum::extract::ws::Message::Text(update_msg.clone().into())); + } + } + } + + ( + StatusCode::OK, + format!( + "sent message: {}, to users:{:?}", + payload.message, participant_ids + ), + ) +} +//Ok(_) => (StatusCode::NOT_FOUND, "Conversation not found".to_string()), #[derive(Debug, Serialize)] struct Message { @@ -369,16 +380,15 @@ struct Message { sent_at: String, } - pub async fn get_message( State(state): State, - AuthClaims {user_id, hotel_id}: AuthClaims, - GetMessagesPayload(payload):GetMessagesPayload + AuthClaims { user_id, hotel_id }: AuthClaims, + GetMessagesPayload(payload): GetMessagesPayload, ) -> Result { - let pool = state.hotel_pools.get_pool(hotel_id); - let conn = pool.get() + let conn = pool + .get() .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Pool error".to_string()))?; let from_time = match payload.timestamp.as_deref() { @@ -386,39 +396,51 @@ pub async fn get_message( Some(ts) => ts, }; - let mut stmt = conn.prepare( - "SELECT id, sender_id, content, sent_at + let mut stmt = conn + .prepare( + "SELECT id, sender_id, content, sent_at FROM message WHERE conversation_id = ?1 AND sent_at > ?2 ORDER BY sent_at DESC - LIMIT 50" - ).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Prepare failed".to_string()))?; + LIMIT 50", + ) + .map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Prepare failed".to_string(), + ) + })?; - - let messages = stmt.query_map( - params![payload.conv_id, from_time], - |row| { + let messages = stmt + .query_map(params![payload.conv_id, from_time], |row| { Ok(Message { id: row.get(0)?, sender_id: row.get(1)?, content: row.get(2)?, sent_at: row.get(3)?, }) - } - ) - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()))? - .collect::, _>>() - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Collect failed".to_string()))?; + }) + .map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Query failed".to_string(), + ) + })? + .collect::, _>>() + .map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Collect failed".to_string(), + ) + })?; let response = serde_json::to_string(&messages) .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Serialisation failed")); - Ok((StatusCode::OK, response )) - + Ok((StatusCode::OK, response)) } - #[derive(Serialize)] struct User { id: i32, @@ -426,22 +448,30 @@ struct User { //display_name: String, } - pub async fn get_hotel_users( State(state): State, AuthClaims { hotel_id, .. }: AuthClaims, ) -> impl IntoResponse { let conn = match state.logs_pool.get() { Ok(c) => c, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error".to_string()), + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "DB connection error".to_string(), + ); + } }; - let mut stmt = match conn.prepare( - "SELECT user_id, username FROM hotel_user_link WHERE hotel_id = ?1", - ) { - Ok(s) => s, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Prepare failed: {}", e)), - }; + let mut stmt = + match conn.prepare("SELECT user_id, username FROM hotel_user_link WHERE hotel_id = ?1") { + Ok(s) => s, + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Prepare failed: {}", e), + ); + } + }; let users_iter = match stmt.query_map(params![hotel_id], |row| { Ok(User { @@ -451,17 +481,30 @@ pub async fn get_hotel_users( }) }) { Ok(iter) => iter, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Query failed: {}", e)), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Query failed: {}", e), + ); + } }; let users: Vec = match users_iter.collect::, _>>() { Ok(u) => u, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Collect failed: {}", e)), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Collect failed: {}", e), + ); + } }; match serde_json::to_string(&users) { Ok(json) => (StatusCode::OK, json), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Serialization failed: {}", e)), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Serialization failed: {}", e), + ), } } @@ -475,54 +518,71 @@ struct Conversation { pub async fn get_convs( State(state): State, //Path((item_name, item_amount)): Path<(String, i32)>, - AuthClaims{ user_id, hotel_id}: AuthClaims, + AuthClaims { user_id, hotel_id }: AuthClaims, ) -> impl IntoResponse { - let pool = state.hotel_pools.get_pool(hotel_id); - let conn = match pool.get(){ + let conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error: {err}")), + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Pool error: {err}"), + ); + } }; - - let mut stmt = match conn.prepare( - "SELECT conversation_id, name FROM conversation_participants WHERE user_id = ?1", - ) { + let mut stmt = match conn + .prepare("SELECT conversation_id, name FROM conversation_participants WHERE user_id = ?1") + { Ok(s) => s, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Prepare failed: {}", e)), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Prepare failed: {}", e), + ); + } }; let rows = match stmt.query_map(params![user_id], |row| { let conversation_id: i32 = row.get(0)?; let name: String = row.get(1)?; Ok(Conversation { - id: row.get(0)?, - title: row.get(1)?, - }) + id: row.get(0)?, + title: row.get(1)?, + }) }) { Ok(rows) => rows, //Ok(_) => {}, IMPLEMENT NO CONV ? - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Query failed: {}", e)), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Query failed: {}", e), + ); + } }; - let convs: Vec = match rows.collect::, _>>() { Ok(u) => u, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Collect failed: {}", e)), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Collect failed: {}", e), + ); + } }; //.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, Json("error".to_string()))); match serde_json::to_string(&convs) { Ok(json) => (StatusCode::OK, json), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Serialization failed: {}", e)), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Serialization failed: {}", e), + ), } } - - - /* pub async fn get_convs( State(state): State, @@ -535,18 +595,18 @@ pub async fn get_convs( let conn = match pool.get(){ Ok(conn) => conn, Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error: {}", err).into_response() ) - + }; let mut stmt = match conn.prepare( "SELECT id, title FROM conversation WHERE creator_id = ?1", ) { Ok(s) => s, - Err(e) => - + Err(e) => + return (StatusCode::INTERNAL_SERVER_ERROR, format!("Prepare failed: {}", e).into_response() ) - - + + }; let rows = match stmt.query_map(params![user_id], |row| { @@ -556,10 +616,10 @@ pub async fn get_convs( }) { Ok(rows) => rows, Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Query failed: {}", e).into_response() ) - + }; - + let mut map = HashMap::new(); // ✅ Iterate through the row results @@ -569,7 +629,7 @@ pub async fn get_convs( map.insert(title, id); } Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Row parsing failed: {}", e).into_response() ) - + } } @@ -578,7 +638,7 @@ pub async fn get_convs( Ok(c) => c, Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("List unwrapping failed: {}", e).into_response() ) }; - + let conv_map_clean_json = serde_json::to_value(map) .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Serialization failed: {}", e).into_response() )); @@ -587,4 +647,4 @@ pub async fn get_convs( (StatusCode::OK, Json(conv_map_clean_json)).into_response() } -*/ \ No newline at end of file +*/ diff --git a/src/chat/mod.rs b/src/chat/mod.rs index 4f1dd12..2b6ada4 100644 --- a/src/chat/mod.rs +++ b/src/chat/mod.rs @@ -1,5 +1,4 @@ pub mod routes; - mod extractor; -mod handlers; \ No newline at end of file +mod handlers; diff --git a/src/chat/routes.rs b/src/chat/routes.rs index d75c3da..04317b3 100644 --- a/src/chat/routes.rs +++ b/src/chat/routes.rs @@ -1,25 +1,15 @@ -use axum::{ - Router, - routing::put, - routing::post, - routing::get, -}; +use axum::{Router, routing::get, routing::post, routing::put}; -use crate::utils::db_pool::AppState; use crate::chat::handlers::*; - - - - +use crate::utils::db_pool::AppState; pub fn chat_routes() -> Router { - Router::new() - .route("/create_conversation", post (create_conversation)) + .route("/create_conversation", post(create_conversation)) .route("/add_users_conv", put(add_user_to_conv)) .route("/send_message", post(send_message)) .route("/get_conv", post(get_convs)) .route("/get_message", post(get_message)) .route("/hotel_users", post(get_hotel_users)) .route("/get_conv_users/{conv_id}", post(get_conv_users)) - } \ No newline at end of file +} diff --git a/src/inventory/handler.rs b/src/inventory/handler.rs index 09e06ed..b0227be 100644 --- a/src/inventory/handler.rs +++ b/src/inventory/handler.rs @@ -1,36 +1,48 @@ - use argon2::Params; -use axum::{extract::{ws::{close_code::STATUS, Message}, Path, State}, http::StatusCode, response::IntoResponse}; +use axum::{ + extract::{ + Path, State, + ws::{Message, close_code::STATUS}, + }, + http::StatusCode, + response::IntoResponse, +}; use rusqlite::params; use serde::Serialize; use serde_json::json; - - use crate::utils::{auth::AuthClaims, db_pool::AppState}; - - pub async fn create_inventory_item( State(state): State, Path((item_name, item_amount)): Path<(String, i32)>, - AuthClaims{ user_id, hotel_id}: AuthClaims, + AuthClaims { user_id, hotel_id }: AuthClaims, ) -> impl IntoResponse { - let pool = state.hotel_pools.get_pool(hotel_id); - let conn = match pool.get(){ + let conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't open the connection")) + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("couldn't open the connection"), + ); + } }; let result = conn.execute( "INSERT INTO inventory (item_name, amount, user_id) VALUES (?1, ?2, ?3)", - params![&item_name,&item_amount,&user_id] + params![&item_name, &item_amount, &user_id], ); match result { - Ok(rows) => (StatusCode::OK, format!("inserted item {item_name}, with {item_amount} amount")), - Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't add the new item, err: {}", err )) + Ok(rows) => ( + StatusCode::OK, + format!("inserted item {item_name}, with {item_amount} amount"), + ), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("couldn't add the new item, err: {}", err), + ), } } @@ -39,7 +51,7 @@ pub struct InventoryItems { id: i32, amount: i32, name: String, - user_id: i32, + user_id: i32, updated_at: String, } @@ -48,16 +60,20 @@ pub async fn update_inventory_item( Path((item_id, item_amount)): Path<(i32, i32)>, AuthClaims { user_id, hotel_id }: AuthClaims, ) -> impl IntoResponse { - //TODO: make better error handling : - // if wrong param collumn targeted, - // if missing path param + // if wrong param collumn targeted, + // if missing path param let pool = state.hotel_pools.get_pool(hotel_id); - - let conn = match pool.get(){ + + let conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error: {err}")), + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Pool error: {err}"), + ); + } }; let result: Result = conn.query_row( @@ -68,7 +84,7 @@ pub async fn update_inventory_item( match result { Ok(item_name) => { - if let Err(err) = conn.execute( + if let Err(err) = conn.execute( "INSERT INTO inventory_history (item_id, amount, item_name, user_id) VALUES (?1,?2,?3,?4)", params![&item_id,&item_amount,&item_name, &user_id] ){ @@ -90,30 +106,29 @@ pub async fn update_inventory_item( let _ = sender.send(Message::Text(update_msg.clone().into())); } } - (StatusCode::OK, format!("updated item history")) } - - Ok(_) => (StatusCode::NOT_FOUND, "No item found, err : {_}".to_string()), - Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Error from DB: {err}")), - + + Ok(_) => ( + StatusCode::NOT_FOUND, + "No item found, err : {_}".to_string(), + ), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Error from DB: {err}"), + ), } - - - -/* - match result { - Ok(row) => (StatusCode::OK, format!("Items updated")), - Ok(_) => (StatusCode::NOT_FOUND, format!("No item with this id exist")), - Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("error updating the item with id :{} with amount: {}", item_id, item_amount)) - } -*/ - + /* + match result { + Ok(row) => (StatusCode::OK, format!("Items updated")), + Ok(_) => (StatusCode::NOT_FOUND, format!("No item with this id exist")), + Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("error updating the item with id :{} with amount: {}", item_id, item_amount)) + } + */ } - pub async fn get_inventory_item( State(state): State, AuthClaims { user_id, hotel_id }: AuthClaims, @@ -125,10 +140,16 @@ pub async fn get_inventory_item( Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Pool error".to_string()), }; - let mut stmt = match conn.prepare("SELECT id, amount, item_name, user_id, updated_at FROM inventory") { - Ok(s) => s, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Statement error".to_string()), - }; + let mut stmt = + match conn.prepare("SELECT id, amount, item_name, user_id, updated_at FROM inventory") { + Ok(s) => s, + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Statement error".to_string(), + ); + } + }; let mut query_result = match stmt.query([]) { Ok(r) => r, @@ -151,8 +172,13 @@ pub async fn get_inventory_item( // Serialize to JSON let json = match serde_json::to_string(&items) { Ok(j) => j, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Serialization error".to_string()), + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Serialization error".to_string(), + ); + } }; (StatusCode::OK, json) -} \ No newline at end of file +} diff --git a/src/inventory/mod.rs b/src/inventory/mod.rs index 5a539d5..32e4f70 100644 --- a/src/inventory/mod.rs +++ b/src/inventory/mod.rs @@ -1,3 +1,3 @@ mod handler; -pub mod routes; \ No newline at end of file +pub mod routes; diff --git a/src/inventory/routes.rs b/src/inventory/routes.rs index b5d2b96..70adc3b 100644 --- a/src/inventory/routes.rs +++ b/src/inventory/routes.rs @@ -1,14 +1,19 @@ use axum::{ - routing::{get, put, post}, Router, + routing::{get, post, put}, }; use crate::{inventory::handler::*, utils::db_pool::AppState}; pub fn inventory_routes() -> Router { - Router::new() - .route("/update_item/{item_id}/{item_amount}", put(update_inventory_item)) - .route("/add_item/{item_name}/{item_amount}", post(create_inventory_item)) + .route( + "/update_item/{item_id}/{item_amount}", + put(update_inventory_item), + ) + .route( + "/add_item/{item_name}/{item_amount}", + post(create_inventory_item), + ) .route("/get_item/", get(get_inventory_item)) -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 0128a59..b19af3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ -#[allow(unused_imports)] -#[allow(dead_code)] - -pub mod utils; -pub mod routes; -pub mod rooms; pub mod chat; pub mod inventory; +pub mod rooms; +pub mod routes; +#[allow(unused_imports)] +#[allow(dead_code)] +pub mod utils; diff --git a/src/main.rs b/src/main.rs index 5fa9674..a2da3bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,35 +1,37 @@ -use axum::serve; use axum::Extension; -use axum::extract::{ws::{Message, WebSocket, WebSocketUpgrade}, State}; +use axum::extract::{ + State, + ws::{Message, WebSocket, WebSocketUpgrade}, +}; +use axum::serve; use jsonwebtoken::{DecodingKey, EncodingKey}; use reqwest::header::AUTHORIZATION; use reqwest::header::CONTENT_TYPE; use reqwest::header::USER_AGENT; use tokio::net::TcpListener; -use tokio::sync::mpsc; -use reqwest::Client; -mod utils; -mod routes; -mod rooms; + mod chat; mod inventory; -use r2d2::{Pool}; -use r2d2_sqlite::SqliteConnectionManager; +mod rooms; +mod routes; +mod utils; use dashmap::DashMap; +use r2d2::Pool; +use r2d2_sqlite::SqliteConnectionManager; use std::sync::Arc; -use crate::utils::db_pool::{HotelPool,AppState}; -use routes::create_router; use crate::utils::auth::JwtKeys; +use crate::utils::db_pool::{AppState, HotelPool}; +use routes::create_router; -use std::env; use dotenvy::dotenv; +use std::env; //use tower_http::cors::Origin; -use tower_http::cors::{CorsLayer, Any,}; -use axum::http::{Method, HeaderValue}; +use axum::http::{HeaderValue, Method}; +use tower_http::cors::{Any, CorsLayer}; pub async fn notify_discord(msg: &str) -> Result<(), reqwest::Error> { let payload = serde_json::json!({ @@ -45,10 +47,8 @@ pub async fn notify_discord(msg: &str) -> Result<(), reqwest::Error> { Ok(()) } - #[tokio::main(flavor = "multi_thread", worker_threads = 8)] async fn main() -> std::io::Result<()> { - dotenv().ok(); std::panic::set_hook(Box::new(|info| { @@ -78,39 +78,34 @@ async fn main() -> std::io::Result<()> { let state = AppState { hotel_pools, logs_pool, - ws_map: Arc::new(DashMap::new()), + ws_map: Arc::new(DashMap::new()), //jwt_secret: "your_jwt_secret_key s".to_string(), // better: load from env var }; //let jwt_secret = "your_jwt_secret_key".to_string(); - + let jwt_secret = env::var("JWT_SECRET") .expect("JWT_SECRET must be set") .to_string(); - + let jwt_keys = JwtKeys { encoding: EncodingKey::from_secret(jwt_secret.as_ref()), decoding: DecodingKey::from_secret(jwt_secret.as_ref()), }; - let allowed_origins = vec![ - "http://82.66.253.209", - "http://localhost:5173", - ]; + let allowed_origins = vec!["http://82.66.253.209", "http://localhost:5173"]; let cors = CorsLayer::very_permissive() .allow_credentials(true) .allow_methods([Method::GET, Method::POST, Method::PUT, Method::OPTIONS]) .allow_headers([CONTENT_TYPE, AUTHORIZATION, USER_AGENT]); - let app = create_router(state) - .layer(Extension(jwt_keys)) - .layer(cors); + let app = create_router(state).layer(Extension(jwt_keys)).layer(cors); let listener = TcpListener::bind("0.0.0.0:7080").await?; serve(listener, app).into_future().await?; Ok(()) } - async fn handler() -> &'static str { - "Hiii from localhost" - } \ No newline at end of file +async fn handler() -> &'static str { + "Hiii from localhost" +} diff --git a/src/note_endpoint_json.txt b/src/note_endpoint_json.txt deleted file mode 100644 index 73acf09..0000000 --- a/src/note_endpoint_json.txt +++ /dev/null @@ -1,51 +0,0 @@ -#[derive(Deserialize, Debug)] -pub struct GetMessagesValues { - pub conv_id: u32, - pub timestamp: Option, -} - - -pub async fn get_message( - State(state): State, - AuthClaims { user_id, hotel_id, username }: AuthClaims, - Json(payload): Json, // ✅ no custom payload wrapper -) -> Result { - - let pool = state.hotel_pools.get_pool(hotel_id); - - let conn = pool.get() - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Pool error".to_string()))?; - - let from_time = match payload.timestamp.as_deref() { - Some("0") | None => "1970-01-01 00:00:00", - Some(ts) => ts, - }; - - let mut stmt = conn.prepare( - "SELECT id, sender_id, content, sent_at - FROM message - WHERE conversation_id = ?1 - AND sent_at > ?2 - ORDER BY sent_at DESC - LIMIT 50" - ).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Prepare failed".to_string()))?; - - let messages = stmt.query_map( - params![payload.conv_id, from_time], - |row| { - Ok(Message { - id: row.get(0)?, - sender_id: row.get(1)?, - content: row.get(2)?, - sent_at: row.get(3)?, - }) - } - ) - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()))? - .collect::, _>>() - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Collect failed".to_string()))?; - - -} - - diff --git a/src/rooms/extractor.rs b/src/rooms/extractor.rs index d068ff9..f045e05 100644 --- a/src/rooms/extractor.rs +++ b/src/rooms/extractor.rs @@ -1,9 +1,8 @@ use axum::{ - extract::{Request, FromRequest, Path}, - body::{Body}, - - http::StatusCode, Json, Router, + body::Body, + extract::{FromRequest, Path, Request}, + http::StatusCode, }; use serde::Deserialize; @@ -16,7 +15,8 @@ pub struct UpdateRoomPayload(pub UpdateRoomValues); //#[async_trait] impl FromRequest for UpdateRoomPayload -where S: Send + Sync, +where + S: Send + Sync, { type Rejection = (StatusCode, String); @@ -24,14 +24,14 @@ where S: Send + Sync, let Json(payload) = Json::::from_request(req, state) .await .map_err(|err| (StatusCode::BAD_REQUEST, format!("Invalid body: {}", err)))?; - + Ok(UpdateRoomPayload(payload)) } } #[derive(Debug)] pub struct RoomIdValue { - pub room_id: i32 + pub room_id: i32, } -pub type RoomIdPath = Path; \ No newline at end of file +pub type RoomIdPath = Path; diff --git a/src/rooms/handler.rs b/src/rooms/handler.rs index 7999583..6341130 100644 --- a/src/rooms/handler.rs +++ b/src/rooms/handler.rs @@ -1,22 +1,20 @@ -use axum::{Json, extract::Path, extract::State }; -use axum::response::IntoResponse; -use axum::http::StatusCode; use axum::extract::ws::Message; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::{Json, extract::Path, extract::State}; use serde::Serialize; use serde_json::json; use crate::rooms::extractor::UpdateRoomPayload; //use crate::utils::db_pool::*; use crate::utils::auth::AuthClaims; -use crate::utils::db_pool::{HotelPool,AppState}; +use crate::utils::db_pool::{AppState, HotelPool}; -use std::sync::Arc; -use r2d2::{Pool}; -use r2d2_sqlite::SqliteConnectionManager; use dashmap::DashMap; +use r2d2::Pool; +use r2d2_sqlite::SqliteConnectionManager; use rusqlite::params; - - +use std::sync::Arc; pub async fn hello_rooms() -> String { "hello from rooms".to_string() @@ -28,11 +26,15 @@ pub async fn fake_db_update( Path(room_id): Path, UpdateRoomPayload(payload): UpdateRoomPayload, ) -> impl IntoResponse { - let pool = state.hotel_pools.get_pool(hotel_id); let conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error: {err}")), + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Pool error: {err}"), + ); + } }; let result = conn.execute( @@ -41,9 +43,15 @@ pub async fn fake_db_update( ); match result { - Ok(rows) if rows > 0 => (StatusCode::OK, format!("Updated room {room_id} in hotel {}", hotel_id)), + Ok(rows) if rows > 0 => ( + StatusCode::OK, + format!("Updated room {room_id} in hotel {}", hotel_id), + ), Ok(_) => (StatusCode::NOT_FOUND, "No room found".to_string()), - Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {err}")), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("DB error: {err}"), + ), } } @@ -53,16 +61,20 @@ pub async fn clean_db_update( AuthClaims { user_id, hotel_id }: AuthClaims, UpdateRoomPayload(payload): UpdateRoomPayload, ) -> impl IntoResponse { - //TODO: make better error handling : - // if wrong param collumn targeted, - // if missing path param + // if wrong param collumn targeted, + // if missing path param let pool = state.hotel_pools.get_pool(hotel_id); - - let conn = match pool.get(){ + + let conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error: {err}")), + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Pool error: {err}"), + ); + } }; let result: Result = conn.query_row( @@ -71,7 +83,6 @@ pub async fn clean_db_update( |row| row.get(0), ); - match result { Ok(room_number) => { // --- broadcast to all WS clients in the hotel --- @@ -79,7 +90,10 @@ pub async fn clean_db_update( "INSERT INTO room_history (room_id, room_number, status) VALUES (?1, ?2, ?3)", params![&room_id, &room_number, &payload.status], ) { - return (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to insert history: {err}")); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Failed to insert history: {err}"), + ); } if let Some(hotel_users) = state.ws_map.get(&hotel_id) { let update_msg = json!({ @@ -97,10 +111,19 @@ pub async fn clean_db_update( } } - (StatusCode::OK, format!("updated room {room_id} in hotel {hotel_id}, with status: {}", payload.status)) + ( + StatusCode::OK, + format!( + "updated room {room_id} in hotel {hotel_id}, with status: {}", + payload.status + ), + ) } Ok(_) => (StatusCode::NOT_FOUND, "No room found".to_string()), - Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Error from DB: {err}")), + Err(err) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Error from DB: {err}"), + ), } } @@ -115,50 +138,65 @@ pub async fn get_all_rooms( State(state): State, AuthClaims { hotel_id, .. }: AuthClaims, ) -> impl IntoResponse { - let pool = state.hotel_pools.get_pool(hotel_id); - - let conn = match pool.get(){ + + let conn = match pool.get() { Ok(conn) => conn, - Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Pool error: {err}")), - }; - - let mut stmt = match conn.prepare( - "SELECT id, number, status FROM rooms ", - ) { - Ok(s) => s, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Prepare failed: {}", e)), - }; - - let room_iter = match stmt.query_map( - params![],|row| { - Ok(Room { - id: row.get(0)?, - number: row.get(1)?, - status: row.get(2)?, - }) + Err(err) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Pool error: {err}"), + ); } - ) { + }; + + let mut stmt = match conn.prepare("SELECT id, number, status FROM rooms ") { + Ok(s) => s, + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Prepare failed: {}", e), + ); + } + }; + + let room_iter = match stmt.query_map(params![], |row| { + Ok(Room { + id: row.get(0)?, + number: row.get(1)?, + status: row.get(2)?, + }) + }) { Ok(iter) => iter, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Query failed: {}", e)), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Query failed: {}", e), + ); + } }; let rooms: Vec = match room_iter.collect::, _>>() { Ok(u) => u, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Collect failed: {}", e)), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Collect failed: {}", e), + ); + } }; match serde_json::to_string(&rooms) { Ok(json) => (StatusCode::OK, json), - Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Serialization failed: {}", e)), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Serialization failed: {}", e), + ), } } - - struct RoomRequest { item_id: i32, item_amount: i32, token: String, } - diff --git a/src/rooms/mod.rs b/src/rooms/mod.rs index ecc5a11..4ac15c9 100644 --- a/src/rooms/mod.rs +++ b/src/rooms/mod.rs @@ -1,6 +1,6 @@ //pub mod handler; //pub mod routes; -mod handler; mod extractor; +mod handler; pub mod routes; diff --git a/src/rooms/routes.rs b/src/rooms/routes.rs index caa2ea9..41522b0 100644 --- a/src/rooms/routes.rs +++ b/src/rooms/routes.rs @@ -1,21 +1,15 @@ use axum::{ - routing::{get, put,}, Router, + routing::{get, put}, }; use crate::rooms::handler::*; -use crate::utils::db_pool::{ - HotelPool, - AppState, -}; - - +use crate::utils::db_pool::{AppState, HotelPool}; // ROOTS pub fn rooms_routes() -> Router { - Router::new() - .route("/", get(hello_rooms) ) + .route("/", get(hello_rooms)) .route("/clean_db_update/{room_id}", put(clean_db_update)) .route("/rooms", get(get_all_rooms)) - } \ No newline at end of file +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 61988f2..9bdef9d 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,31 +1,23 @@ -use axum::{ - Router, -}; +use axum::Router; +use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; -use r2d2::{Pool}; -use crate::rooms::routes::rooms_routes; -use crate::utils::routes::utils_routes; use crate::chat::routes::chat_routes; use crate::inventory::routes::inventory_routes; +use crate::rooms::routes::rooms_routes; +use crate::utils::routes::utils_routes; -use crate::utils::db_pool::{AppState}; -//TODO: add secret fomr dotenv here +use crate::utils::db_pool::AppState; +//TODO: add secret fomr dotenv here /* -Function to build our main router +Function to build our main router that regroup all feature centered router */ pub fn create_router(state: AppState) -> Router { - Router::new() - .nest("/auth", utils_routes().with_state(state.clone())) .nest("/rooms", rooms_routes().with_state(state.clone())) .nest("/chat", chat_routes().with_state(state.clone())) .nest("/inventory", inventory_routes().with_state(state.clone())) - .with_state(state) - } - - diff --git a/src/utils/auth.rs b/src/utils/auth.rs index b5dafd8..329418b 100644 --- a/src/utils/auth.rs +++ b/src/utils/auth.rs @@ -1,35 +1,45 @@ -use std::time::Duration; use axum::{ - Json, body::{Body, to_bytes}, extract::{Extension, FromRequest, FromRequestParts, Path, State, ws::close_code::STATUS}, http::{Request as HttpRequest, StatusCode, header::{HeaderValue, SET_COOKIE}, request::Parts, status }, middleware::Next, response::{IntoResponse, IntoResponseParts, Response} + Json, + body::{Body, to_bytes}, + extract::{Extension, FromRequest, FromRequestParts, Path, State, ws::close_code::STATUS}, + http::{ + Request as HttpRequest, StatusCode, + header::{HeaderValue, SET_COOKIE}, + request::Parts, + status, + }, + middleware::Next, + response::{IntoResponse, IntoResponseParts, Response}, }; +use std::time::Duration; use axum_extra::extract::TypedHeader; //use axum_extra::TypedHeader; use futures_util::future::TrySelect; -use headers::{UserAgent, Cookie}; +use headers::{Cookie, UserAgent}; use axum::extract::FromRef; use axum::extract::Request as ExtractRequest; -use jsonwebtoken::{decode, DecodingKey, Validation, encode, EncodingKey, Header, Algorithm}; +use chrono::{Utc, format}; +use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode}; use reqwest::header::REFRESH; +use rusqlite::{Connection, OptionalExtension, params}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use chrono::{Utc, format}; -use rusqlite::{params, Connection, OptionalExtension}; -use rand_core::{RngCore, OsRng}; use argon2::{ + Argon2, password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, - Argon2}; +}; +use rand_core::{OsRng, RngCore}; use uuid::Uuid; - //use crate::utils::db_pool::; -use crate::utils::db_pool::{HotelPool,AppState}; - -use base64::{engine::general_purpose, Engine as _}; +//use crate::utils::db_pool::; +use crate::utils::db_pool::{AppState, HotelPool}; +use base64::{Engine as _, engine::general_purpose}; #[derive(Clone)] pub struct JwtKeys { @@ -42,14 +52,9 @@ pub async fn token_tester( //Extension(keys): Extension, AuthClaims { user_id, hotel_id }: AuthClaims, ) -> impl IntoResponse { - format!( - "(user_id: {}) from hotel {}", - user_id, hotel_id - ) + format!("(user_id: {}) from hotel {}", user_id, hotel_id) } - - pub struct AuthUser(pub Claims); //?? #[derive(Debug, Clone)] @@ -63,11 +68,8 @@ pub fn auth_claims_from_token( token: &str, keys: &JwtKeys, ) -> Result { - let token_data = decode::( - token, - &keys.decoding, - &Validation::new(Algorithm::HS256), - ).map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid token".into()))?; + let token_data = decode::(token, &keys.decoding, &Validation::new(Algorithm::HS256)) + .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid token".into()))?; Ok(AuthClaims { user_id: token_data.claims.id, @@ -75,7 +77,6 @@ pub fn auth_claims_from_token( }) } - impl FromRequestParts for AuthClaims where S: Send + Sync + 'static, @@ -83,21 +84,25 @@ where { type Rejection = (StatusCode, String); - async fn from_request_parts( - parts: &mut Parts, - state: &S, - ) -> Result { - let Extension(keys): Extension = - Extension::from_request_parts(parts, state) - .await - .map_err(|_| (StatusCode::UNAUTHORIZED, "Missing keys".into()))?; + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let Extension(keys): Extension = Extension::from_request_parts(parts, state) + .await + .map_err(|_| (StatusCode::UNAUTHORIZED, "Missing keys".into()))?; let auth_header = parts .headers .get(axum::http::header::AUTHORIZATION) - .ok_or((StatusCode::UNAUTHORIZED, "Missing Authorization header".into()))? + .ok_or(( + StatusCode::UNAUTHORIZED, + "Missing Authorization header".into(), + ))? .to_str() - .map_err(|_| (StatusCode::BAD_REQUEST, "Invalid Authorization header".into()))?; + .map_err(|_| { + ( + StatusCode::BAD_REQUEST, + "Invalid Authorization header".into(), + ) + })?; let token = auth_header .strip_prefix("Bearer ") @@ -122,19 +127,18 @@ fn hash_password(password: &str) -> anyhow::Result { // Verify an incoming password against stored hash fn verify_password(password: &str, stored_hash: &str) -> bool { - let parsed_hash = match PasswordHash::new(&stored_hash) { Ok(hash) => hash, Err(_) => return false, }; Argon2::default() - .verify_password(password.as_bytes(), &parsed_hash).is_ok() - + .verify_password(password.as_bytes(), &parsed_hash) + .is_ok() } #[derive(Deserialize, Debug)] -pub struct RegisterValues{ +pub struct RegisterValues { username: String, password: String, #[serde(default)] @@ -145,7 +149,8 @@ pub struct RegisterValues{ pub struct RegisterPayload(pub RegisterValues); impl FromRequest for RegisterPayload -where S: Send + Sync, +where + S: Send + Sync, { type Rejection = (StatusCode, String); @@ -157,55 +162,82 @@ where S: Send + Sync, } } - //TODO: Validate all hotel_ids first + Use a transaction + Batch query hotel names with IN (...) -pub async fn register_user ( +pub async fn register_user( State(state): State, - RegisterPayload(payload): RegisterPayload + RegisterPayload(payload): RegisterPayload, ) -> Result { + let hashed_password = hash_password(&payload.password).map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Password hashing failed: {}", e), + ) + })?; - let hashed_password = hash_password(&payload.password) - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Password hashing failed: {}", e)))?; - - let conn = state.logs_pool.get() - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB connection error: {}", e)))?; + let conn = state.logs_pool.get().map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("DB connection error: {}", e), + ) + })?; conn.execute( - "INSERT INTO users (username, password, displayname) + "INSERT INTO users (username, password, displayname) VALUES (?1, ?2, ?3)", params![payload.username, hashed_password, payload.displayname], ) - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("User insert error: {}", e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("User insert error: {}", e), + ) + })?; let user_id = conn.last_insert_rowid(); - + for &hotel_id in &payload.hotel_ids { - // more logic for security here //FIXME: needs to be the display name in the DB, scheme is currently wrong - + let hotel_name: String = conn .query_row( "SELECT hotelname FROM hotels WHERE id = ?1 ", params![hotel_id], |row| row.get(0), - ).map_err(|e| (StatusCode::BAD_REQUEST, format!("Invalid hotel id {}: {}", hotel_id, e)))?; + ) + .map_err(|e| { + ( + StatusCode::BAD_REQUEST, + format!("Invalid hotel id {}: {}", hotel_id, e), + ) + })?; - conn.execute( + conn.execute( "INSERT INTO hotel_user_link (user_id, hotel_id, username, hotelname) VALUES (?1, ?2, ?3, ?4)", params![user_id, hotel_id, payload.username, hotel_name], ) - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Link insert error for user_id={} hotel_id={}: {}", user_id, hotel_id, e)))?; + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!( + "Link insert error for user_id={} hotel_id={}: {}", + user_id, hotel_id, e + ), + ) + })?; } - Ok((StatusCode::CREATED, "User registered successfully".to_string())) + Ok(( + StatusCode::CREATED, + "User registered successfully".to_string(), + )) } #[derive(Serialize, Deserialize, Debug)] -pub struct ForceUpdatePasswordValues{ +pub struct ForceUpdatePasswordValues { username: String, newpassword: String, hotel_id: i32, @@ -218,42 +250,43 @@ pub async fn force_update_password( State(state): State, Json(payload): Json, ) -> impl IntoResponse { - let conn = match state.logs_pool.get() { Ok(c) => c, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB, conn failed").into_response() + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB, conn failed").into_response(), }; - let user_row = match conn.query_row( - "SELECT id FROM users WHERE username = ?1 AND hotel_id = ?2", - params![&payload.username, &payload.hotel_id], - |row|{ - let user_id: i32 = row.get(0)?; - //let hotel_id: i32 = row.get(1)?; - Ok(user_id) - }, - ).optional() { + let user_row = match conn + .query_row( + "SELECT id FROM users WHERE username = ?1 AND hotel_id = ?2", + params![&payload.username, &payload.hotel_id], + |row| { + let user_id: i32 = row.get(0)?; + //let hotel_id: i32 = row.get(1)?; + Ok(user_id) + }, + ) + .optional() + { Ok(opt) => opt, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error") - .into_response(), + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(), }; - + let user_id = match user_row { Some(u) => u, - None => return (StatusCode::UNAUTHORIZED, "Not correct user") - .into_response(), + None => return (StatusCode::UNAUTHORIZED, "Not correct user").into_response(), }; - + let admin_check: String = "my_admin_password".to_string(); if &payload.admin_pass != &admin_check { - return (StatusCode::UNAUTHORIZED, "Invalid Amin Password").into_response() + return (StatusCode::UNAUTHORIZED, "Invalid Amin Password").into_response(); }; - let hashed_password = match hash_password(&payload.newpassword) { Ok(h) => h, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Password hashing failed").into_response(), + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "Password hashing failed").into_response(); + } }; let result = conn.execute( @@ -264,58 +297,67 @@ pub async fn force_update_password( match result { Ok(rows) if rows > 0 => (StatusCode::OK, "Password updated").into_response(), Ok(_) => (StatusCode::NOT_FOUND, "User not found").into_response(), - Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Failed to update password").into_response(), + Err(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to update password", + ) + .into_response(), } - } #[derive(Serialize, Deserialize, Debug)] -pub struct UpdatePasswordValues{ +pub struct UpdatePasswordValues { username: String, current_password: String, newpassword: String, //hotel_id: i32, - } pub async fn update_password( State(state): State, Json(payload): Json, ) -> impl IntoResponse { - let conn = match state.logs_pool.get() { Ok(c) => c, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB, conn failed").into_response() + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB, conn failed").into_response(), }; - let user_row = match conn.query_row( - "SELECT password, id FROM users WHERE username = ?1", - params![&payload.username], - |row|{ - let password: String = row.get(0)?; - let id: i32 = row.get(1)?; - Ok((password, id)) - }, - ).optional() { + let user_row = match conn + .query_row( + "SELECT password, id FROM users WHERE username = ?1", + params![&payload.username], + |row| { + let password: String = row.get(0)?; + let id: i32 = row.get(1)?; + Ok((password, id)) + }, + ) + .optional() + { Ok(opt) => opt, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("DB query error: {}", e )) - .into_response(), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("DB query error: {}", e), + ) + .into_response(); + } }; - + let (password, user_id) = match user_row { Some(u) => u, - None => return (StatusCode::UNAUTHORIZED, "Not correct user") - .into_response(), - }; - - if !verify_password( &payload.current_password, &password ) { - return (StatusCode::UNAUTHORIZED, "Invalid Password").into_response() + None => return (StatusCode::UNAUTHORIZED, "Not correct user").into_response(), + }; + + if !verify_password(&payload.current_password, &password) { + return (StatusCode::UNAUTHORIZED, "Invalid Password").into_response(); }; - let hashed_password = match hash_password(&payload.newpassword) { Ok(h) => h, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Password hashing failed").into_response(), + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "Password hashing failed").into_response(); + } }; let result = conn.execute( @@ -326,22 +368,26 @@ pub async fn update_password( match result { Ok(rows) if rows > 0 => (StatusCode::OK, "Password updated").into_response(), Ok(_) => (StatusCode::NOT_FOUND, "User not found").into_response(), - Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Failed to update password").into_response(), + Err(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to update password", + ) + .into_response(), } - } #[derive(Deserialize, Debug)] pub struct LoginValues { - username : String, - password : String, + username: String, + password: String, //hotel_id: i32, } pub struct LoginPayload(pub LoginValues); impl FromRequest for LoginPayload -where S: Send + Sync, +where + S: Send + Sync, { type Rejection = (StatusCode, String); @@ -349,13 +395,13 @@ where S: Send + Sync, let Json(payload) = Json::::from_request(req, state) .await .map_err(|err| (StatusCode::BAD_REQUEST, format!("Invalid body: {}", err)))?; - + Ok(LoginPayload(payload)) } } -#[derive(Deserialize,Debug, Serialize, Clone)] -struct Claims{ +#[derive(Deserialize, Debug, Serialize, Clone)] +struct Claims { id: i32, hotel_id: i32, //display_name @@ -382,41 +428,49 @@ pub async fn clean_auth_loging( // 1️⃣ Get a connection from logs pool let conn = match state.logs_pool.get() { Ok(c) => c, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(), + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(); + } }; - - let user_row = match conn.query_row( - "SELECT id, password, hotel_id, displayname FROM users WHERE username = ?1", - params![&payload.username], - |row| { - let user_id: i32 = row.get(0)?; - let password: String = row.get(1)?; - let hotel_id: i32 = row.get(2)?; - let displayname: String = row.get(3)?; - Ok((user_id, password, hotel_id, displayname)) - }, - ).optional() { + + let user_row = match conn + .query_row( + "SELECT id, password, hotel_id, displayname FROM users WHERE username = ?1", + params![&payload.username], + |row| { + let user_id: i32 = row.get(0)?; + let password: String = row.get(1)?; + let hotel_id: i32 = row.get(2)?; + let displayname: String = row.get(3)?; + Ok((user_id, password, hotel_id, displayname)) + }, + ) + .optional() + { Ok(opt) => opt, Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(), }; let (user_id, stored_hash, hotel_id, _displayname) = match user_row { - Some(u) => u, - None => return (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response(), + Some(u) => u, + None => return (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response(), }; if !verify_password(&payload.password, &stored_hash) { return (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response(); } - - let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) { - Some(time) => time.timestamp() as usize, - None => { - // Handle overflow — probably a 500, since this should never happen - return (StatusCode::INTERNAL_SERVER_ERROR, "Time overflow".to_string()).into_response(); - } -}; + let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) { + Some(time) => time.timestamp() as usize, + None => { + // Handle overflow — probably a 500, since this should never happen + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Time overflow".to_string(), + ) + .into_response(); + } + }; let claims = serde_json::json!({ "id": user_id, @@ -425,13 +479,11 @@ pub async fn clean_auth_loging( "exp": expiration }); - let token = match encode( - &Header::default(), - &claims, - &keys.encoding - ) { + let token = match encode(&Header::default(), &claims, &keys.encoding) { Ok(t) => t, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "JWT creation failed").into_response(), + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "JWT creation failed").into_response(); + } }; Json(LoginResponse { token }).into_response() } @@ -442,18 +494,17 @@ pub struct CreateRefreshTokenValue { pub password: String, pub device_id: Uuid, //pub timestamp: Option, - } - //FIXME: weird return type, returning result ? #[axum::debug_handler] pub async fn create_refresh_token( State(state): State, user_agent: Option>, - Json(payload): Json -) -> Result { // ← Add Result here + Json(payload): Json, +) -> Result { + // ← Add Result here let user_agent_str = user_agent .map(|ua| ua.to_string()) @@ -461,18 +512,19 @@ pub async fn create_refresh_token( let device_id_str = payload.device_id.to_string(); - let conn = state.logs_pool.get() - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error".to_string()))?; - - + let conn = state.logs_pool.get().map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "DB connection error".to_string(), + ) + })?; let argon2 = Argon2::default(); let salt = SaltString::generate(&mut OsRng); let mut bytes = [0u8; 64]; - OsRng.fill_bytes(&mut bytes); + OsRng.fill_bytes(&mut bytes); let raw_token = Uuid::new_v4().to_string(); - let hashed_token = &raw_token; /* let hashed_token = argon2 @@ -481,8 +533,8 @@ pub async fn create_refresh_token( .to_string(); */ - // let mut stmt = conn.prepare( - // "SELECT id, password FROM users WHERE username = ?1" + // let mut stmt = conn.prepare( + // "SELECT id, password FROM users WHERE username = ?1" let credentials = match conn.query_row( "SELECT id, password FROM users WHERE username = ?1", @@ -493,53 +545,72 @@ pub async fn create_refresh_token( Ok((user_id, password)) }, ) { - Ok(cr) => cr, - Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, format!("error fetching credentials: {e}").to_string())), - + Ok(cr) => cr, + Err(e) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + format!("error fetching credentials: {e}").to_string(), + )); + } }; let (user_id, user_password) = credentials; -/* - let (user_id, stored_hash, hotel_id) = user_row - .ok_or((StatusCode::NOT_FOUND, "User not found".to_string()))?; -*/ + /* + let (user_id, stored_hash, hotel_id) = user_row + .ok_or((StatusCode::NOT_FOUND, "User not found".to_string()))?; + */ //let mut tokens = Vec::new(); //TODO: validate password if !verify_password(&payload.password, &user_password) { - return Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid credential".to_string())); // Skip rows with invalid password + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Invalid credential".to_string(), + )); // Skip rows with invalid password } - - - //TODO: get hotel name to return a map/tuple of hotel name - let mut stmt = match conn.prepare( - "SELECt hotel_id FROM hotel_user_link WHERE user_id = ?1" - ) { + //TODO: get hotel name to return a map/tuple of hotel name + let mut stmt = match conn.prepare("SELECt hotel_id FROM hotel_user_link WHERE user_id = ?1") { Ok(stmt) => stmt, - Err(_) => return Err((StatusCode::INTERNAL_SERVER_ERROR, "error building user_id fetch stmt".to_string())), + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "error building user_id fetch stmt".to_string(), + )); + } }; //TODO: compiler les hotel id dans un vecteur pour le feed dans le refresh token - let hotel_ids: Vec = match stmt - .query_map(params![&user_id],|row| row.get (0)) - { - Ok(rows) => rows.collect::,_>>() - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Error collecting hotel_ids".to_string()))?, - Err(_) => return Err((StatusCode::INTERNAL_SERVER_ERROR, "Error mapping hotel_ids".to_string())), - }; + let hotel_ids: Vec = match stmt.query_map(params![&user_id], |row| row.get(0)) { + Ok(rows) => rows.collect::, _>>().map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Error collecting hotel_ids".to_string(), + ) + })?, + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Error mapping hotel_ids".to_string(), + )); + } + }; let hotel_ids_json = match serde_json::to_string(&hotel_ids) { Ok(json) => json, - Err(_) => return Err((StatusCode::INTERNAL_SERVER_ERROR, "Error mapping hotel_ids".to_string())), + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Error mapping hotel_ids".to_string(), + )); + } }; - - /*.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Error mapping hotel_ids".to_string())); */ - + /*.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Error mapping hotel_ids".to_string())); */ + //FIXME: might not need the hotel list on tconflict ? - - //TODO: remove user agent entirely from auth ,it is mutable and not stable + + //TODO: remove user agent entirely from auth ,it is mutable and not stable //TODO: make the token refresh on login conn.execute( r#" @@ -555,51 +626,50 @@ pub async fn create_refresh_token( token_hash = excluded.token_hash, hotel_id_list = excluded.hotel_id_list "#, - params![ - user_id, - hashed_token, - device_id_str, - hotel_ids_json - ], - ).map_err(|e| { - (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {}", e)) + params![user_id, hashed_token, device_id_str, hotel_ids_json], + ) + .map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("DB error: {}", e), + ) })?; //TODO: add a map/tupple of of the allowed hotels and their id+name, maybe update the token ? println!("RAW write refresh_token bytes: {:?}", &raw_token.as_bytes()); - println!("RAW refresh_token : {}", &raw_token.to_string()); + println!("RAW refresh_token : {}", &raw_token.to_string()); println!("RAW write refresh_token len: {}", &raw_token.len()); - let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=60480000000;Path=/", raw_token); - - let mut response = (StatusCode::CREATED, "Refresh token created successfully").into_response(); - response.headers_mut().insert( - SET_COOKIE, - HeaderValue::from_str(&cookie_value).unwrap(), + let cookie_value = format!( + "refresh_token={}; HttpOnly; Secure; Max-Age=60480000000;Path=/", + raw_token ); - Ok(response) // ← Wrap in Ok() + let mut response = (StatusCode::CREATED, "Refresh token created successfully").into_response(); + response + .headers_mut() + .insert(SET_COOKIE, HeaderValue::from_str(&cookie_value).unwrap()); + + Ok(response) // ← Wrap in Ok() } #[derive(Deserialize)] -pub struct LoginRefreshTokenValues{ +pub struct LoginRefreshTokenValues { device_id: Uuid, - //refresh_token: String, + //refresh_token: String, } //TODO: LATER : implement hotel-id-selected to allow user to only get part hotels ? -pub async fn login_refresh_token ( +pub async fn login_refresh_token( State(state): State, Extension(keys): Extension, user_agent: Option>, cookie_header: Option>, - Json(payload): Json + Json(payload): Json, ) -> impl IntoResponse { - println!("login_refresh_token called"); // Log cookies - let cookies = match cookie_header { Some(token) => token, @@ -619,7 +689,9 @@ pub async fn login_refresh_token ( let conn = match state.logs_pool.get() { Ok(c) => c, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(), + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(); + } }; let user_agent_str = match user_agent { @@ -630,7 +702,7 @@ pub async fn login_refresh_token ( let device_id_str = payload.device_id.to_string(); println!("device id: {:?}", &device_id_str); - + //"SELECT user_id, token_hash, hotel_id FROM refresh_token WHERE device_id = ?1 AND user_agent = ?2", //TODO: swap to query row and get hotel-id's list and not single hotel per row @@ -640,41 +712,59 @@ pub async fn login_refresh_token ( "SELECT user_id, hotel_id_list FROM refresh_token WHERE device_id = ?1 AND token_hash = ?2 - LIMIT 1;" + LIMIT 1;", ) { Ok(s) => s, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Error prepatring hotel_id_list stmt").into_response(), - }; - - let rows = match stmt.query_one(params![&device_id_str, &refresh_token], |row| { - Ok(( - row.get::<_, i32>(0)?, // user_id - row.get::<_, String>(1)?, // token_hash - //row.get::<_, String>(2)?, // hotel_id //FIXME: this is supposed to be vectore maybe ? - )) - }).optional() { - Ok(r) => r, - Err(e) => { - eprintln!("DB ERROR: {:?}", e); - return (StatusCode::INTERNAL_SERVER_ERROR, format!("DB query error: {}", e)).into_response() + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Error prepatring hotel_id_list stmt", + ) + .into_response(); } }; - //TODO: extraction of the blob + + let rows = match stmt + .query_one(params![&device_id_str, &refresh_token], |row| { + Ok(( + row.get::<_, i32>(0)?, // user_id + row.get::<_, String>(1)?, // token_hash + //row.get::<_, String>(2)?, // hotel_id //FIXME: this is supposed to be vectore maybe ? + )) + }) + .optional() + { + Ok(r) => r, + Err(e) => { + eprintln!("DB ERROR: {:?}", e); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("DB query error: {}", e), + ) + .into_response(); + } + }; + //TODO: extraction of the blob //let json_hotel_ids = rows.2; let (user_id, json_hotel_ids) = match rows { - Some(r) => r, - None => { - return ( - StatusCode::UNAUTHORIZED, - "No refresh token found for this device", - ) - .into_response(); - } -}; + Some(r) => r, + None => { + return ( + StatusCode::UNAUTHORIZED, + "No refresh token found for this device", + ) + .into_response(); + } + }; let hotel_ids: Vec = match serde_json::from_str(&json_hotel_ids) { - Ok(ids) => ids, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Hotel ids are not deserializable to Vec").into_response(), - + Ok(ids) => ids, + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Hotel ids are not deserializable to Vec", + ) + .into_response(); + } }; //FIXME: still problems when corrupted token exist @@ -682,23 +772,27 @@ pub async fn login_refresh_token ( return (StatusCode::UNAUTHORIZED, "No matching device").into_response(); } -/* + /* - eprintln!("DB ERROR: {:?}", &refresh_token); - eprintln!("DB ERROR: {:?}", &token); + eprintln!("DB ERROR: {:?}", &refresh_token); + eprintln!("DB ERROR: {:?}", &token); - //still not auto adding hotel user link when creating account - if (&refresh_token != &token) { - // skip rows with wrong hash - return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response(); - } - */ + //still not auto adding hotel user link when creating account + if (&refresh_token != &token) { + // skip rows with wrong hash + return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response(); + } + */ let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) { Some(time) => time.timestamp() as usize, None => { // Handle overflow — probably a 500, since this should never happen - return (StatusCode::INTERNAL_SERVER_ERROR, "Time overflow".to_string()).into_response(); + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Time overflow".to_string(), + ) + .into_response(); } }; @@ -713,7 +807,9 @@ pub async fn login_refresh_token ( let token = match encode(&Header::default(), &claims, &keys.encoding) { Ok(token) => token, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "JWT creation failed").into_response(), + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "JWT creation failed").into_response(); + } }; tokens.push(token); @@ -723,23 +819,21 @@ pub async fn login_refresh_token ( return (StatusCode::UNAUTHORIZED, "Invalid or mismatched token").into_response(); } -//Json(tokens).into_response() + //Json(tokens).into_response() Json(MultiLoginResponse { user_id, tokens }).into_response() - } #[axum::debug_handler] -pub async fn logout_from_single_device ( +pub async fn logout_from_single_device( State(state): State, Extension(keys): Extension, user_agent: Option>, cookie_header: Option>, - Json(payload): Json + Json(payload): Json, ) -> impl IntoResponse { - - let user_agent_str = user_agent - .map(|TypedHeader(ua)| ua.as_str().to_owned()) - .unwrap_or_else(|| "Unknown".to_string()); + let user_agent_str = user_agent + .map(|TypedHeader(ua)| ua.as_str().to_owned()) + .unwrap_or_else(|| "Unknown".to_string()); let cookies = match cookie_header { Some(token) => token, @@ -751,30 +845,39 @@ pub async fn logout_from_single_device ( None => return (StatusCode::UNAUTHORIZED, "Missing refresh token cookie").into_response(), }; - let device_id_str = payload.device_id.to_string(); let conn = match state.logs_pool.get() { Ok(c) => c, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(), + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(); + } }; - - let device_row = match conn.query_row( - "SELECT user_id, hotel_id_list, id + let device_row = match conn + .query_row( + "SELECT user_id, hotel_id_list, id FROM refresh_token WHERE token_hash = ?1 AND revoked = 0 ", - params![&refresh_token], - |row| { - let user_id: i32 = row.get(0)?; - let json_hotel_id_list: String = row.get(1)?; - let id:i32 = row.get(2)?; - //let displayname: String = row.get(3)?; - Ok((user_id,json_hotel_id_list ,id)) - }, - ).optional() { + params![&refresh_token], + |row| { + let user_id: i32 = row.get(0)?; + let json_hotel_id_list: String = row.get(1)?; + let id: i32 = row.get(2)?; + //let displayname: String = row.get(3)?; + Ok((user_id, json_hotel_id_list, id)) + }, + ) + .optional() + { Ok(opt) => opt, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("DB query error : {}", e )).into_response(), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("DB query error : {}", e), + ) + .into_response(); + } }; let (user_id, json_hotel_id_list, token_id) = match device_row { @@ -782,19 +885,23 @@ pub async fn logout_from_single_device ( None => return (StatusCode::UNAUTHORIZED, "No matching device").into_response(), }; - let hotel_ids: Vec = match serde_json::from_str(&json_hotel_id_list) { - Ok(ids) => ids, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Hotel ids are not deserializable to Vec").into_response(), - + let hotel_ids: Vec = match serde_json::from_str(&json_hotel_id_list) { + Ok(ids) => ids, + Err(_) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Hotel ids are not deserializable to Vec", + ) + .into_response(); + } }; //FIXME: need to chang the way we get refresh token from the cookies instead /* - if !verify_password(&payload.refresh_token, &token_hash) { - return (StatusCode::UNAUTHORIZED, "Invalid or mismatched token").into_response(); - } -*/ - + if !verify_password(&payload.refresh_token, &token_hash) { + return (StatusCode::UNAUTHORIZED, "Invalid or mismatched token").into_response(); + } + */ let revoked: Result = conn.query_row( "DELETE FROM refresh_token @@ -806,83 +913,93 @@ pub async fn logout_from_single_device ( let revoked_id = match (revoked) { Ok(r) => r, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, "Hotel ids are not deserializable to Vec").into_response() + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Hotel ids are not deserializable to Vec", + ) + .into_response(); + } }; - let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=0;Path=/", "loggedout"); - - let mut response = (StatusCode::CREATED, format!("Token deleted for device id {}", &revoked_id)) - .into_response(); - - response.headers_mut().insert( - SET_COOKIE, - HeaderValue::from_str(&cookie_value).unwrap(), + let cookie_value = format!( + "refresh_token={}; HttpOnly; Secure; Max-Age=0;Path=/", + "loggedout" ); - response + let mut response = ( + StatusCode::CREATED, + format!("Token deleted for device id {}", &revoked_id), + ) + .into_response(); + response + .headers_mut() + .insert(SET_COOKIE, HeaderValue::from_str(&cookie_value).unwrap()); + + response } -pub async fn logout_from_all_devices ( +pub async fn logout_from_all_devices( State(state): State, Extension(keys): Extension, AuthClaims { user_id, hotel_id }: AuthClaims, //Json(payload): Json ) -> impl IntoResponse { - - - //let device_id_str = payload.device_id.to_string(); let conn = match state.logs_pool.get() { Ok(c) => c, - Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(), + Err(_) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(); + } }; - + let result = conn.execute( "DELETE FROM refresh_token WHERE user_id = ?1", params![&user_id], ); - /* + /* - match result { - //Ok(count) if count > 0 => { - // (StatusCode::OK, format!("Revoked {} active tokens", count)).into_response() - //} - //Ok(_) => (StatusCode::NOT_FOUND, "No active tokens to revoke").into_response(), - Err(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Database update error".to_string(), - ) - .into_response(), - } - */ + match result { + //Ok(count) if count > 0 => { + // (StatusCode::OK, format!("Revoked {} active tokens", count)).into_response() + //} + //Ok(_) => (StatusCode::NOT_FOUND, "No active tokens to revoke").into_response(), + Err(_) => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Database update error".to_string(), + ) + .into_response(), + } + */ - - let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=0;Path=/", "loggedout"); - - let mut response = (StatusCode::CREATED, format!("Token deleted for device id ")) - .into_response(); - - response.headers_mut().insert( - SET_COOKIE, - HeaderValue::from_str(&cookie_value).unwrap(), + let cookie_value = format!( + "refresh_token={}; HttpOnly; Secure; Max-Age=0;Path=/", + "loggedout" ); + let mut response = + (StatusCode::CREATED, format!("Token deleted for device id ")).into_response(); + + response + .headers_mut() + .insert(SET_COOKIE, HeaderValue::from_str(&cookie_value).unwrap()); + match result { //Ok(count) if count > 0 => { // (StatusCode::OK, format!("Revoked {} active tokens", count)).into_response() //} Ok(_) => response, Err(err) => ( - StatusCode::INTERNAL_SERVER_ERROR, err.to_string(), // or format!("{err:?}") + StatusCode::INTERNAL_SERVER_ERROR, + err.to_string(), // or format!("{err:?}") ) - .into_response(), + .into_response(), } //response - } #[derive(Serialize)] @@ -891,103 +1008,118 @@ struct HotelData { hotel_name: String, } -pub async fn get_hotel( - State(state): State - -)-> impl IntoResponse { - +pub async fn get_hotel(State(state): State) -> impl IntoResponse { let try_conn = state.logs_pool.get(); let conn = match try_conn { - Ok(conn)=> conn, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, "bruh").into_response() + Ok(conn) => conn, + Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, "bruh").into_response(), }; - let try_stmt = conn.prepare(" + let try_stmt = conn.prepare( + " SELECT id, hotelname - FROM hotels"); + FROM hotels", + ); let mut stmt = match try_stmt { - Ok(stmt) =>stmt , - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - "failed buildin statement") - .into_response() + Ok(stmt) => stmt, + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "failed buildin statement", + ) + .into_response(); + } }; - let try_hotels = stmt.query_map(params![], - |row| { - Ok(HotelData { - id: row.get(0)?, - hotel_name: row.get(1)?, - }) - } - - ); + let try_hotels = stmt.query_map(params![], |row| { + Ok(HotelData { + id: row.get(0)?, + hotel_name: row.get(1)?, + }) + }); let hotel_itter = match try_hotels { Ok(hotels) => hotels, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - "error processing hotel list") - .into_response() + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "error processing hotel list", + ) + .into_response(); + } }; let hotels: Vec = match hotel_itter.collect::, _>>() { Ok(hotel) => hotel, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - format!("failed collection of hotel : {e}")) - .into_response() + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("failed collection of hotel : {e}"), + ) + .into_response(); + } }; match serde_json::to_string(&hotels) { - Ok(json)=> return (StatusCode::OK, json).into_response(), - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("Serialization failed: {}", e)).into_response() - + Ok(json) => return (StatusCode::OK, json).into_response(), + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Serialization failed: {}", e), + ) + .into_response(); + } }; - - - //.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error".to_string()))?; - //return (StatusCode::OK).into_response(); + //.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error".to_string()))?; + //return (StatusCode::OK).into_response(); } - #[derive(Deserialize, Debug)] -pub struct addHotelUser{ - user_id:i32, +pub struct addHotelUser { + user_id: i32, #[serde(default)] hotel_ids: Vec, } - pub async fn add_hotel_user( - State(state): State, Extension(keys): Extension, - Json(payload): Json + Json(payload): Json, ) -> impl IntoResponse { - let conn = match state.logs_pool.get() { Ok(c) => c, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - "DB connection error").into_response() + Err(e) => { + return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(); + } }; - let user_name: String= match conn.query_row( + let user_name: String = match conn.query_row( "SELECT username FROM users WHERE id = ?1", params![&payload.user_id], |row| row.get(0), ) { Ok(name) => name, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - format!("user not found {e} ")).into_response() + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("user not found {e} "), + ) + .into_response(); + } }; - let mut get_hotel_name_stmt = match conn.prepare( - "SELECT hotelname FROM hotels WHERE id = ?1" - ) { + let mut get_hotel_name_stmt = match conn.prepare("SELECT hotelname FROM hotels WHERE id = ?1") { Ok(stmt) => stmt, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - format!("could't prepare stmt for hotel : {e} ")).into_response() + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("could't prepare stmt for hotel : {e} "), + ) + .into_response(); + } }; let mut insert_hotel_link_stmt = match conn.prepare( @@ -996,52 +1128,53 @@ pub async fn add_hotel_user( VALUES (?1,?2,?3,?4)", ) { Ok(stmt) => stmt, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - format!("could't prepare stmt to insert hotel : {e} ")).into_response() + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("could't prepare stmt to insert hotel : {e} "), + ) + .into_response(); + } }; - for &hotel_id in &payload.hotel_ids{ + for &hotel_id in &payload.hotel_ids { + let hotel_name: String = + match get_hotel_name_stmt.query_row(params![hotel_id], |row| row.get(0)) { + Ok(name) => name, + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("hotel not found {e} "), + ) + .into_response(); + } + }; - let hotel_name: String = match get_hotel_name_stmt.query_row( - params![hotel_id], - |row| row.get(0), - ) { - Ok(name) => name, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - format!("hotel not found {e} ")).into_response() - }; - - let add_link = match conn.execute( - "INSERT INTO hotel_user_link + let add_link = match conn.execute( + "INSERT INTO hotel_user_link (user_id,hotel_id,username,hotelname) VALUES (?1,?2,?3,?4)", - params![ - payload.user_id, - hotel_id, - user_name, - hotel_name - ],) { - + params![payload.user_id, hotel_id, user_name, hotel_name], + ) { Ok(_) => true, - Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, - format!("hotel not found {e} ")).into_response() - + Err(e) => { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("hotel not found {e} "), + ) + .into_response(); + } }; -//TODO: still need to build the add hotel to user here + //TODO: still need to build the add hotel to user here + } - }; - - - - - - - return(StatusCode::OK, "goo").into_response(); + return (StatusCode::OK, "goo").into_response(); } - - fn internal_error(err: E) -> (StatusCode, String) { - (StatusCode::INTERNAL_SERVER_ERROR, format!("Internal error: {}", err)) -} \ No newline at end of file + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Internal error: {}", err), + ) +} diff --git a/src/utils/db_pool.rs b/src/utils/db_pool.rs index 6be8c1f..150b5ef 100644 --- a/src/utils/db_pool.rs +++ b/src/utils/db_pool.rs @@ -1,19 +1,21 @@ -use std::sync::Arc; +use axum::extract::{ + State, + ws::{Message, WebSocket, WebSocketUpgrade}, +}; use dashmap::DashMap; -use r2d2::{Pool}; +use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; +use std::sync::Arc; use tokio::sync::mpsc; -use axum::extract::{ws::{Message, WebSocket, WebSocketUpgrade}, State}; use crate::utils::websocket::WsMap; - #[derive(Clone)] - pub struct AppState { - pub hotel_pools: HotelPool, - pub logs_pool: Pool, - pub ws_map: WsMap, - } - +#[derive(Clone)] +pub struct AppState { + pub hotel_pools: HotelPool, + pub logs_pool: Pool, + pub ws_map: WsMap, +} type HotelId = i32; // or i32 if you want numeric ids @@ -44,4 +46,4 @@ impl HotelPool { self.hotel_pools.insert(hotel_id, db_pool.clone()); db_pool } -} \ No newline at end of file +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 3ec42dc..59d0bfa 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,4 +1,4 @@ -pub mod db_pool; pub mod auth; +pub mod db_pool; pub mod routes; -pub mod websocket; \ No newline at end of file +pub mod websocket; diff --git a/src/utils/routes.rs b/src/utils/routes.rs index 2c74457..9314b0d 100644 --- a/src/utils/routes.rs +++ b/src/utils/routes.rs @@ -1,37 +1,29 @@ use axum::{ - routing::{get, put, post}, Router, + routing::{get, post, put}, }; use crate::utils::auth::*; -use crate::utils::db_pool::{HotelPool, AppState, }; +use crate::utils::db_pool::{AppState, HotelPool}; use crate::utils::websocket::ws_handler; -use headers::UserAgent; use axum_extra::TypedHeader; - +use headers::UserAgent; // ROOTS pub fn utils_routes() -> Router { - Router::new() .route("/login", put(clean_auth_loging)) .route("/register", put(register_user)) - .route("/tokentest", put(token_tester)) .route("/update_password", put(update_password)) - .route("/create_refresh", post(create_refresh_token)) .route("/login_refresh_token", post(login_refresh_token)) - .route("/logout_single_device", post(logout_from_single_device)) .route("/logout_all_devices", post(logout_from_all_devices)) - .route("/ws/{req_token}", get(ws_handler)) - .route("/force_update_password", put(force_update_password)) .route("/get_hotels", get(get_hotel)) .route("/add_hotel_user", put(add_hotel_user)) - - //.with_state(state) - } \ No newline at end of file + //.with_state(state) +} diff --git a/src/utils/websocket.rs b/src/utils/websocket.rs index 60b7411..89ba323 100644 --- a/src/utils/websocket.rs +++ b/src/utils/websocket.rs @@ -1,32 +1,33 @@ +use axum::extract::Path; +use axum::response::IntoResponse; +use axum::{ + Extension, + extract::{ + State, + ws::{Message, WebSocket, WebSocketUpgrade}, + }, +}; use dashmap::DashMap; use reqwest::StatusCode; use std::sync::Arc; -use axum::{Extension, extract::{State, ws::{Message, WebSocket, WebSocketUpgrade}}}; use tokio::sync::mpsc; -use axum::extract::Path; -use axum::response::IntoResponse; //use futures_util::stream::stream::StreamExt; -use futures_util::{StreamExt, SinkExt}; +use futures_util::{SinkExt, StreamExt}; -use crate::utils::{auth::{AuthClaims, JwtKeys, auth_claims_from_token}, db_pool::{AppState, HotelPool}}; +use crate::utils::{ + auth::{AuthClaims, JwtKeys, auth_claims_from_token}, + db_pool::{AppState, HotelPool}, +}; - - - /// Type alias: user_id → sender to that user +/// Type alias: user_id → sender to that user pub type UserMap = DashMap>; /// hotel_id → users pub type HotelMap = DashMap>; /// global map of all hotels pub type WsMap = Arc; - /// Type alias: user_id → sender to that user +/// Type alias: user_id → sender to that user - -async fn handle_socket( - mut socket: WebSocket, - state: AppState, - hotel_id: i32, - user_id: i32, -) { +async fn handle_socket(mut socket: WebSocket, state: AppState, hotel_id: i32, user_id: i32) { // channel for sending messages TO this client let (tx, mut rx) = mpsc::unbounded_channel::(); @@ -45,8 +46,6 @@ async fn handle_socket( // split socket into sender/receiver let (mut sender, mut receiver) = socket.split(); - - // task for sending messages from server to client let mut rx_task = tokio::spawn(async move { while let Some(msg) = rx.recv().await { @@ -96,21 +95,17 @@ pub async fn ws_handler( State(state): State, Path((req_token)): Path<(String)>, ) -> impl IntoResponse { - - let token = req_token; - - let claims = match auth_claims_from_token(&token, &keys) { + + let claims = match auth_claims_from_token(&token, &keys) { Err(_) => { print!("error during auth claims processing"); return StatusCode::UNAUTHORIZED.into_response(); - } - Ok(c) => c - + Ok(c) => c, }; - - print!("{token}, web socket tried to connect", ); + + print!("{token}, web socket tried to connect",); /* let claims = match auth_claims_from_token(&token, &keys) { @@ -119,7 +114,6 @@ pub async fn ws_handler( }; */ - ws.on_upgrade(move |socket| handle_socket(socket, state, claims.hotel_id, claims.user_id)) } @@ -132,4 +126,4 @@ fn print_ws_state(state: &AppState) { println!("Hotel {hotel_id}: users {:?}", users); } println!("--------------------------------"); -} \ No newline at end of file +} diff --git a/utils command.txt b/utils command.txt deleted file mode 100644 index 29726f9..0000000 --- a/utils command.txt +++ /dev/null @@ -1,125 +0,0 @@ -docker build -t rust-api:1.0.5 . -docker save -o rust-api-1-0-4.tar rust-api:1.0.4 - - -cross build --release --target aarch64-unknown-linux-gnu - -docker run -p 8080:8080 \ - -v ${PWD}/db:/db \ - -e JWT_SECRET="my-dev-secret" \ - rust-api:1.0.0 - -GOOD - - docker run - --hostname=58ff54b2464c - --env=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - --volume=/w/DEV/hotel-api-rs/db:/app/db - --network=bridge - --workdir=/app - -p 8080:8080 - --restart=no - --runtime=runc - -d rust-api:1.0.0 - - - - docker run ` - --name hotel-api ` - -e JWT_SECRET=your_jwt_secret_key ` - -v "/w/DEV/hotel-api-rs/db:/app/db" ` - -p 8080:8080 ` - rust-api:1.0.1 - -linux distro - - docker run ` - --name hotel-api ` - -e JWT_SECRET=your_jwt_secret_key ` - -v "/var/lib/hotel-api/db:/app/db" ` - -p 8080:8080 ` - rust-api:1.0.2 - -docker run \ ---name hotel-api-2 \ --e JWT_SECRET=your_jwt_secret_key \ --v "/var/lib/hotel-api/db:/app/db" \ --p 8080:8080 \ -rust-api:1.0.3 - - -FORM UBUNUTU REMOTE HOST - -sudo docker run -d \ ---name hotel-demo \ --e JWT_SECRET=your_jwt_secret_key \ --v "/var/lib/hotel-api/db:/app/db" \ --p 5090:7080 \ -hotel-demo:1.0.0 - - -FOR WINDOWS -vvvv -sudo docker run ` - --name hotel-demo ` - -e JWT_SECRET=your_jwt_secret_key ` - -v "/var/lib/hotel-api/db:/app/db" ` - -p 5090:5090 ` - hotel-demo:1.0.0 - - - -BAD - - "Mounts": [ - { - "Type": "bind", - "Source": "/w/DEV/hotel-api-rs/db", - "Destination": "/app/db ", - "Mode": "", - "RW": true, - "Propagation": "rprivate" - } - ], - - -"Mounts": [ - { - "Type": "bind", - "Source": "/w/DEV/hotel-api-rs/db", - "Destination": "/app/db", - "Mode": "", - "RW": true, - "Propagation": "rprivate" - } - ], - - - "Mounts": [ - { - "Type": "bind", - "Source": "W:\\DEV\\hotel-api-rs\\db\\db.sqlite", - "Destination": "/app/db/db.sqlite", - "Mode": "", - "RW": true, - "Propagation": "rprivate" - } - ], - -GOOD - - "Mounts": [ - { - "Type": "bind", - "Source": "/w/DEV/hotel-api-rs/db", - "Destination": "/app/db", - "Mode": "", - "RW": true, - "Propagation": "rprivate" - } - ], - - - - -