From 6e8bc721d06d8a95aa46f4fb9d489b20c3a556b4 Mon Sep 17 00:00:00 2001 From: hsn2004 Date: Sun, 23 Nov 2025 16:50:00 -0800 Subject: [PATCH 1/3] Adding the user authentication for basic user API's: create, login, get all, delete --- ...236ecc769f3a38594dd41aaac230fac16b62f.json | 42 ++++ ...c1620736e9ad3720f6145df4c609309c927f6.json | 41 ++++ ...71cef087a159c6ee7182d8ca929ecb748f3b7.json | 14 ++ ...afb487c1b540df63cf7998cce53476baaf253.json | 43 ++++ ...f7262346d7647e4c279806f2e9c6172f0b89f.json | 41 ++++ ...69456beff2b3f5187ee3794b24aa55babdf5.json} | 11 +- ...af172d41e1c7a5aa8e7b843f7973bb345e727.json | 42 ++++ ...d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json | 41 ++++ ...5af3582be51fcfd8a99336f5594c649674e1.json} | 10 +- ...7c461a755fccf5c37ebc642e9a53f1af1bcd3.json | 42 ++++ ...8b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json | 42 ++++ backend/Cargo.lock | 59 ++++++ backend/api-server/Cargo.toml | 3 + backend/api-server/src/main.rs | 191 ++++++++++++------ backend/shared-logic/Cargo.toml | 5 + backend/shared-logic/src/db.rs | 142 +++++++++---- backend/shared-logic/src/models.rs | 17 +- backend/test-lsl/Cargo.toml | 7 + backend/test-lsl/src/main.rs | 3 + 19 files changed, 683 insertions(+), 113 deletions(-) create mode 100644 backend/.sqlx/query-0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f.json create mode 100644 backend/.sqlx/query-4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6.json create mode 100644 backend/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json create mode 100644 backend/.sqlx/query-5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253.json create mode 100644 backend/.sqlx/query-68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f.json rename backend/.sqlx/{query-171f07975cf260d23d1077aa57c85bbb8741b6f136122653fefac674eadb3dca.json => query-7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5.json} (63%) create mode 100644 backend/.sqlx/query-ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727.json create mode 100644 backend/.sqlx/query-c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json rename backend/.sqlx/{query-88f23b59e72f3d364e0c83c1046b05c8fa7243caa6e1a6aba4a9228531156474.json => query-d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1.json} (64%) create mode 100644 backend/.sqlx/query-db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3.json create mode 100644 backend/.sqlx/query-f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json create mode 100644 backend/test-lsl/Cargo.toml create mode 100644 backend/test-lsl/src/main.rs diff --git a/backend/.sqlx/query-0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f.json b/backend/.sqlx/query-0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f.json new file mode 100644 index 0000000..f9f5238 --- /dev/null +++ b/backend/.sqlx/query-0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f.json @@ -0,0 +1,42 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE users SET username = $1, email = $2 WHERE id = $3 RETURNING id, username, email, password_hash", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f" +} diff --git a/backend/.sqlx/query-4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6.json b/backend/.sqlx/query-4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6.json new file mode 100644 index 0000000..0f5b6f0 --- /dev/null +++ b/backend/.sqlx/query-4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6.json @@ -0,0 +1,41 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE users SET password_hash = $1 WHERE id = $2 RETURNING id, username, email, password_hash", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6" +} diff --git a/backend/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json b/backend/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json new file mode 100644 index 0000000..cf6c805 --- /dev/null +++ b/backend/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM users WHERE id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [] + }, + "hash": "50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7" +} diff --git a/backend/.sqlx/query-5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253.json b/backend/.sqlx/query-5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253.json new file mode 100644 index 0000000..effed01 --- /dev/null +++ b/backend/.sqlx/query-5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253.json @@ -0,0 +1,43 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE users SET username = $1, email = $2, password_hash = $3 WHERE id = $4 RETURNING id, username, email, password_hash", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253" +} diff --git a/backend/.sqlx/query-68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f.json b/backend/.sqlx/query-68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f.json new file mode 100644 index 0000000..7213d2c --- /dev/null +++ b/backend/.sqlx/query-68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f.json @@ -0,0 +1,41 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE users SET email = $1 WHERE id = $2 RETURNING id, username, email, password_hash", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f" +} diff --git a/backend/.sqlx/query-171f07975cf260d23d1077aa57c85bbb8741b6f136122653fefac674eadb3dca.json b/backend/.sqlx/query-7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5.json similarity index 63% rename from backend/.sqlx/query-171f07975cf260d23d1077aa57c85bbb8741b6f136122653fefac674eadb3dca.json rename to backend/.sqlx/query-7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5.json index 22226f3..c7b8471 100644 --- a/backend/.sqlx/query-171f07975cf260d23d1077aa57c85bbb8741b6f136122653fefac674eadb3dca.json +++ b/backend/.sqlx/query-7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO users (username, email) VALUES ($1, $2) RETURNING id, username, email", + "query": "SELECT id, username, email, password_hash FROM users WHERE email = $1", "describe": { "columns": [ { @@ -17,19 +17,24 @@ "ordinal": 2, "name": "email", "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" } ], "parameters": { "Left": [ - "Text", "Text" ] }, "nullable": [ + false, false, false, false ] }, - "hash": "171f07975cf260d23d1077aa57c85bbb8741b6f136122653fefac674eadb3dca" + "hash": "7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5" } diff --git a/backend/.sqlx/query-ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727.json b/backend/.sqlx/query-ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727.json new file mode 100644 index 0000000..130fc60 --- /dev/null +++ b/backend/.sqlx/query-ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727.json @@ -0,0 +1,42 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING id, username, email, password_hash", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727" +} diff --git a/backend/.sqlx/query-c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json b/backend/.sqlx/query-c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json new file mode 100644 index 0000000..b317d54 --- /dev/null +++ b/backend/.sqlx/query-c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json @@ -0,0 +1,41 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE users SET username = $1 WHERE id = $2 RETURNING id, username, email, password_hash", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd" +} diff --git a/backend/.sqlx/query-88f23b59e72f3d364e0c83c1046b05c8fa7243caa6e1a6aba4a9228531156474.json b/backend/.sqlx/query-d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1.json similarity index 64% rename from backend/.sqlx/query-88f23b59e72f3d364e0c83c1046b05c8fa7243caa6e1a6aba4a9228531156474.json rename to backend/.sqlx/query-d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1.json index 4b34b5b..05ee42d 100644 --- a/backend/.sqlx/query-88f23b59e72f3d364e0c83c1046b05c8fa7243caa6e1a6aba4a9228531156474.json +++ b/backend/.sqlx/query-d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT id, username, email FROM users", + "query": "SELECT id, username, email, password_hash FROM users", "describe": { "columns": [ { @@ -17,16 +17,22 @@ "ordinal": 2, "name": "email", "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" } ], "parameters": { "Left": [] }, "nullable": [ + false, false, false, false ] }, - "hash": "88f23b59e72f3d364e0c83c1046b05c8fa7243caa6e1a6aba4a9228531156474" + "hash": "d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1" } diff --git a/backend/.sqlx/query-db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3.json b/backend/.sqlx/query-db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3.json new file mode 100644 index 0000000..b4ba2a5 --- /dev/null +++ b/backend/.sqlx/query-db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3.json @@ -0,0 +1,42 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE users SET username = $1, password_hash = $2 WHERE id = $3 RETURNING id, username, email, password_hash", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3" +} diff --git a/backend/.sqlx/query-f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json b/backend/.sqlx/query-f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json new file mode 100644 index 0000000..3c3c686 --- /dev/null +++ b/backend/.sqlx/query-f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json @@ -0,0 +1,42 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE users SET email = $1, password_hash = $2 WHERE id = $3 RETURNING id, username, email, password_hash", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "password_hash", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Int4" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f531389..de35fc5 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -114,12 +114,14 @@ dependencies = [ name = "api-server" version = "0.1.0" dependencies = [ + "argon2 0.4.1", "axum", "chrono", "dotenvy", "env_logger", "log", "pyo3", + "rand_core 0.6.4", "serde", "serde_json", "shared-logic", @@ -128,6 +130,29 @@ dependencies = [ "tower-http", ] +[[package]] +name = "argon2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73" +dependencies = [ + "base64ct", + "blake2", + "password-hash 0.4.2", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash 0.5.0", +] + [[package]] name = "async-trait" version = "0.1.88" @@ -263,6 +288,15 @@ dependencies = [ "serde", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -1309,6 +1343,28 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -1792,6 +1848,7 @@ dependencies = [ name = "shared-logic" version = "0.1.0" dependencies = [ + "argon2 0.5.3", "chrono", "dotenvy", "futures-util", @@ -1799,8 +1856,10 @@ dependencies = [ "lsl", "numpy", "once_cell", + "password-hash 0.5.0", "pyo3", "rand 0.8.5", + "rand_core 0.6.4", "serde", "serde_json", "sqlx", diff --git a/backend/api-server/Cargo.toml b/backend/api-server/Cargo.toml index 4215795..0bdef5e 100644 --- a/backend/api-server/Cargo.toml +++ b/backend/api-server/Cargo.toml @@ -9,6 +9,9 @@ edition = "2021" axum = { version = "0.7", features = ["macros"] } # Tower for HTTP services (used by Axum) tower-http = { version = "0.5", features = ["cors"] } # For CORS, logging, etc. +argon2 = "0.4" +rand_core = "0.6" + # Serialization/Deserialization for JSON serde = { version = "1.0", features = ["derive"] } diff --git a/backend/api-server/src/main.rs b/backend/api-server/src/main.rs index 88671c6..da11164 100644 --- a/backend/api-server/src/main.rs +++ b/backend/api-server/src/main.rs @@ -1,6 +1,5 @@ use axum::{ extract::State, - extract::Path, http::StatusCode, routing::{get, post}, Json, @@ -17,100 +16,153 @@ use pyo3::Python; use pyo3::types::{PyList, PyModule, PyTuple}; use pyo3::PyResult; use pyo3::{IntoPy, ToPyObject}; +use rand_core::OsRng; // shared logic library use shared_logic::db::{initialize_connection, DbClient}; use shared_logic::models::{User, NewUser, UpdateUser, Session, FrontendState}; +// Argon2 imports +use argon2::{ + Argon2, + password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, +}; + + // Define application state #[derive(Clone)] struct AppState { db_client: DbClient, } + +#[derive(Debug, Clone, Deserialize)] +pub struct LoginRequest { + pub email: String, + pub password: String, +} + +#[derive(Debug, Serialize)] +struct PublicUser { + id: i32, + username: String, + email: String, +} + +#[derive(Debug, Deserialize)] +struct DeleteUserRequest { + id: i32, +} + + // creates new user when POST /users is called async fn create_user( State(app_state): State, Json(new_user_data): Json, -) -> Result, (StatusCode, String)> { +) -> Result, (StatusCode, String)> { info!("Received request to create user: {:?}", new_user_data); - match shared_logic::db::add_user( + // Generate a random salt + let salt = SaltString::generate(&mut OsRng); + + // Hash the password + let argon2 = Argon2::default(); + let password_hash = argon2 + .hash_password(new_user_data.password.as_bytes(), &salt) + .map_err(|e| { + error!("Failed to hash password: {}", e); + (StatusCode::INTERNAL_SERVER_ERROR, "Password hashing failed".into()) + })? + .to_string(); + + // Store user in DB + let created_user = shared_logic::db::add_user( &app_state.db_client, new_user_data, + password_hash, ) .await - { - Ok(created_user) => { - info!("User created successfully: {:?}", created_user); - Ok(Json(created_user)) - } - Err(e) => { - error!("Failed to create user: {}", e); - Err(( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to create user: {}", e), - )) - } + .map_err(|e| { + error!("Failed to create user: {}", e); + (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to create user: {}", e)) + })?; + + // Convert to PublicUser to hide password + let public_user = PublicUser { + id: created_user.id, + username: created_user.username, + email: created_user.email, + }; + + info!("User created successfully: {:?}", public_user); + Ok(Json(public_user)) +} + + + +async fn login_user( + State(app_state): State, + Json(login_data): Json, +) -> Result, (StatusCode, String)> { + info!("Login attempt for email: {}", login_data.email); + + // Fetch user from DB by email + let user = match shared_logic::db::get_user_by_email(&app_state.db_client, &login_data.email).await { + Ok(u) => u, + Err(_) => return Err((StatusCode::UNAUTHORIZED, "Invalid email or password".into())), + }; + + // Verify password + let parsed_hash = PasswordHash::new(&user.password_hash) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Password parsing failed".into()))?; + + if Argon2::default().verify_password(login_data.password.as_bytes(), &parsed_hash).is_ok() { + let public_user = PublicUser { + id: user.id, + username: user.username, + email: user.email, + }; + Ok(Json(public_user)) + } else { + Err((StatusCode::UNAUTHORIZED, "Invalid email or password".into())) } } + // Handler for GET /users // This function will retrieve all users from the database. async fn get_all_users( - State(app_state): State, // Access shared database client -) -> Result>, (StatusCode, String)> { + State(app_state): State, +) -> Result>, (StatusCode, String)> { info!("Received request to get all users"); - match shared_logic::db::get_users(&app_state.db_client).await { - Ok(users) => { - info!("Retrieved {} users.", users.len()); - Ok(Json(users)) // Return list of users as JSON - } - Err(e) => { - error!("Failed to retrieve users: {}", e); - Err(( - StatusCode::INTERNAL_SERVER_ERROR, - format!("Failed to retrieve users: {}", e), - )) - } - } -} + let users = shared_logic::db::get_users(&app_state.db_client).await.map_err(|e| { + error!("Failed to retrieve users: {}", e); + (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to retrieve users: {}", e)) + })?; -// Handler for PUT /users/:id -async fn update_user( - State(app_state): State, - Path(id): Path, - Json(update_data): Json, -) -> Result, (StatusCode, String)> { - info!("Received request to update user {}: {:?}", id, update_data); - - match shared_logic::db::update_user(&app_state.db_client, id, update_data).await { - Ok(user) => { - info!("User updated successfully: {:?}", user); - Ok(Json(user)) - } - Err(e) => { - error!("Failed to update user: {}", e); - Err((StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to update user: {}", e))) - } - } + let public_users: Vec = users.into_iter().map(|u| PublicUser { + id: u.id, + username: u.username, + email: u.email, + }).collect(); + + Ok(Json(public_users)) } -// Handler for DELETE /users/:id async fn delete_user( State(app_state): State, - Path(id): Path, + Json(payload): Json, ) -> Result { - info!("Received request to delete user {}", id); + let user_id = payload.id; - match shared_logic::db::delete_user(&app_state.db_client, id).await { + match shared_logic::db::delete_user(&app_state.db_client, user_id).await { Ok(_) => { - info!("User {} deleted", id); - Ok(StatusCode::NO_CONTENT) + log::info!("User {} deleted successfully", user_id); + Ok(StatusCode::OK) } Err(e) => { - error!("Failed to delete user: {}", e); + log::error!("Failed to delete user {}: {}", user_id, e); Err((StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to delete user: {}", e))) } } @@ -195,9 +247,12 @@ async fn get_frontend_state( } + + async fn run_python_script_handler() -> Result, (StatusCode, String)> { info!("Received request to run Python script."); + // Python::with_gil needs to be run in a blocking context for async Rust let result = tokio::task::spawn_blocking(move || { Python::with_gil(|py| { @@ -205,6 +260,7 @@ async fn run_python_script_handler() -> Result, (StatusCode, String) let current_dir = env::current_dir()?; info!("API Server CWD for Python scripts: {:?}", current_dir); + // Adjust Python script directory to sys.path // Assuming 'python' and 'scripts' folders are at the workspace root level // relative to the `api-server` crate, it would be `../python` and `../scripts`. @@ -212,50 +268,59 @@ async fn run_python_script_handler() -> Result, (StatusCode, String) // the CWD is `backend-server`. So paths are relative to that. let sys = py.import("sys")?; let paths: &PyList = sys.getattr("path")?.downcast()?; - + // Add the directory containing your EyeBlink Python source // This path is relative to the backend-server/ directory - paths.insert(0, "./python/EyeBlink/src")?; + paths.insert(0, "./python/EyeBlink/src")?; info!("Added './python/EyeBlink/src' to Python sys.path"); + // Read and execute test.py let test_py_path = "./python/EyeBlink/src/test.py"; let test_py_src = fs::read_to_string(test_py_path)?; PyModule::from_code(py, &test_py_src, "test.py", "__main__")?; info!("Executed test.py"); + // Read and execute hello.py let hello_py_path = "./scripts/hello.py"; let hello_py_src = fs::read_to_string(hello_py_path)?; let module = PyModule::from_code(py, &hello_py_src, "hello.py", "hello")?; info!("Loaded hello.py module"); + // Call the 'test' function from hello.py let greet_func = module.getattr("test")?.to_object(py); let args = PyTuple::new(py, &[20_i32.into_py(py), 30_i32.into_py(py)]); let py_result = greet_func.call1(py, args)?; + let result_str: String = py_result.extract(py)?; info!("Result from Python: {}", result_str); + Ok(result_str) as PyResult }) }).await.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("Python blocking task failed: {}", e)))?; + match result { Ok(s) => Ok(Json(json!({"python_output": s}))), Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, format!("Python script execution failed: {}", e))), } } + #[tokio::main] async fn main() { env_logger::init(); info!("Starting API server..."); + dotenv().ok(); info!("Environment variables loaded."); + let db_client = match initialize_connection().await { Ok(client) => { info!("Database connection initialized successfully."); @@ -267,16 +332,18 @@ async fn main() { } }; + let app_state = AppState { db_client: db_client.clone(), }; + // Build Axum router let app = Router::new() .route("/users", post(create_user)) + .route("/users/login", post(login_user)) // Login route .route("/users", get(get_all_users)) - .route("/users/:id", axum::routing::put(update_user)) - .route("/users/:id", axum::routing::delete(delete_user)) + .route("/users/delete", post(delete_user)) // .route("/run-python-script", get(run_python_script_handler)) .route("/api/sessions", post(create_session)) @@ -288,6 +355,7 @@ async fn main() { // Share application state with all handlers .with_state(app_state); + // Define the address and port for the server to listen on. let host = std::env::var("API_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()); let port = std::env::var("API_PORT") @@ -296,13 +364,16 @@ async fn main() { .expect("Invalid API_PORT"); let addr = format!("{}:{}", host, port); + let listener = TcpListener::bind(&addr).await.unwrap_or_else(|e| { error!("Failed to bind to address {}: {}", addr, e); panic!("Exiting due to address binding failure."); }); + info!("API server listening on {}", addr); + // Start the server and wait for it to run. axum::serve(listener, app) .await diff --git a/backend/shared-logic/Cargo.toml b/backend/shared-logic/Cargo.toml index ed01e32..6003901 100644 --- a/backend/shared-logic/Cargo.toml +++ b/backend/shared-logic/Cargo.toml @@ -41,6 +41,11 @@ once_cell = "1.18" #lsl lsl = "0.1.1" +# Argon2 password hashing +argon2 = "0.5" +password-hash = "0.5" +rand_core = "0.6" + # working with python pyo3 = { version = "0.18.0", features = ["auto-initialize"] } numpy = "0.18" \ No newline at end of file diff --git a/backend/shared-logic/src/db.rs b/backend/shared-logic/src/db.rs index b62018b..d082c4b 100644 --- a/backend/shared-logic/src/db.rs +++ b/backend/shared-logic/src/db.rs @@ -11,6 +11,11 @@ use super::models::{User, NewUser, TimeSeriesData, UpdateUser, Session, Frontend use crate::{lsl::EEGDataPacket}; use once_cell::sync::OnceCell; use std::sync::Arc; +use argon2::password_hash::SaltString; +use rand_core::OsRng; +use argon2::{Argon2, password_hash::{PasswordHasher, PasswordHash, PasswordVerifier}}; + + pub static DB_POOL: OnceCell> = OnceCell::new(); @@ -59,13 +64,14 @@ pub fn get_db_client() -> DbClient { DB_POOL.get().expect("DB not initialized").clone() } -pub async fn add_user(client: &DbClient, new_user: NewUser) -> Result { +pub async fn add_user(client: &DbClient, new_user: NewUser, password_hash: String) -> Result { info!("Adding user: {} ({})", new_user.username, new_user.email); let user = sqlx::query_as!( User, - "INSERT INTO users (username, email) VALUES ($1, $2) RETURNING id, username, email", + "INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING id, username, email, password_hash", new_user.username, new_user.email, + password_hash ) .fetch_one(&**client) .await?; @@ -75,13 +81,24 @@ pub async fn add_user(client: &DbClient, new_user: NewUser) -> Result Result, Error> { info!("Retrieving users..."); - let users = sqlx::query_as!(User, "SELECT id, username, email FROM users") + let users = sqlx::query_as!(User, "SELECT id, username, email, password_hash FROM users") .fetch_all(&**client) .await?; info!("Retrieved {} users.", users.len()); Ok(users) } +pub async fn get_user_by_email(client: &DbClient, email: &str) -> Result { + sqlx::query_as!( + User, + "SELECT id, username, email, password_hash FROM users WHERE email = $1", + email + ) + .fetch_one(&**client) + .await +} + + /// Insert a new record into testtime_series using chrono's DateTime. pub async fn add_testtime_series_data( client: &DbClient, @@ -163,73 +180,114 @@ pub async fn insert_batch_eeg(client: &DbClient, packet: &EEGDataPacket) -> Resu /// Update a user by id. /// /// Returns the updated User on success. -pub async fn update_user(client: &DbClient, user_id: i32, updated: UpdateUser) -> Result { - info!("Updating user id {}", user_id); - - - // see what fields are being updated - if let Some(ref username) = updated.username { - info!("Updating username: {}", username); - } - - if let Some(ref email) = updated.email { - info!("Updating email: {}", email); - } +pub async fn update_user( + client: &DbClient, + user_id: i32, + updated: UpdateUser, +) -> Result { + log::info!("Updating user id {}", user_id); + + // Hash password if provided + let password_hash = if let Some(password) = &updated.password { + let salt = SaltString::generate(&mut OsRng); + let argon2 = Argon2::default(); + Some( + argon2 + .hash_password(password.as_bytes(), &salt) + .map_err(|e| { + log::error!("Password hashing failed: {}", e); + Error::RowNotFound // fallback error + })? + .to_string(), + ) + } else { + None + }; - // sql query to update user - let user = match (updated.username, updated.email) { - (Some(username), Some(email)) => { + // Build SQL query dynamically based on which fields are Some + let user = match (updated.username, updated.email, password_hash) { + (Some(username), Some(email), Some(password_hash)) => { sqlx::query_as!( User, - "UPDATE users SET username = $1, email = $2 WHERE id = $3 RETURNING id, username, email", + "UPDATE users SET username = $1, email = $2, password_hash = $3 WHERE id = $4 RETURNING id, username, email, password_hash", username, email, + password_hash, user_id ) - .fetch_one(&**client) + .fetch_one(&**client) .await? } - (Some(username), None) => { + (Some(username), Some(email), None) => { sqlx::query_as!( User, - "UPDATE users SET username = $1 WHERE id = $2 RETURNING id, username, email", + "UPDATE users SET username = $1, email = $2 WHERE id = $3 RETURNING id, username, email, password_hash", username, + email, user_id ) .fetch_one(&**client) .await? } - (None, Some(email)) => { + (Some(username), None, Some(password_hash)) => { sqlx::query_as!( User, - "UPDATE users SET email = $1 WHERE id = $2 RETURNING id, username, email", + "UPDATE users SET username = $1, password_hash = $2 WHERE id = $3 RETURNING id, username, email, password_hash", + username, + password_hash, + user_id + ) + .fetch_one(&**client) + .await? + } + (None, Some(email), Some(password_hash)) => { + sqlx::query_as!( + User, + "UPDATE users SET email = $1, password_hash = $2 WHERE id = $3 RETURNING id, username, email, password_hash", + email, + password_hash, + user_id + ) + .fetch_one(&**client) + .await? + } + (Some(username), None, None) => { + sqlx::query_as!( + User, + "UPDATE users SET username = $1 WHERE id = $2 RETURNING id, username, email, password_hash", + username, + user_id + ) + .fetch_one(&**client) + .await? + } + (None, Some(email), None) => { + sqlx::query_as!( + User, + "UPDATE users SET email = $1 WHERE id = $2 RETURNING id, username, email, password_hash", email, user_id ) .fetch_one(&**client) .await? } - (None, None) => { - info!("No fields to update for user id {}, user not updated", user_id); // unsure of what behavior to do in this case - return Err(Error::RowNotFound); // returning error for now, should this be allowed/be a different error? + (None, None, Some(password_hash)) => { + sqlx::query_as!( + User, + "UPDATE users SET password_hash = $1 WHERE id = $2 RETURNING id, username, email, password_hash", + password_hash, + user_id + ) + .fetch_one(&**client) + .await? + } + (None, None, None) => { + log::info!("No fields to update for user id {}, user not updated", user_id); + return Err(Error::RowNotFound); } }; - /// let user = sqlx::query_as!( - /// User, - /// r#" UPDATE users SET - /// username = COALESCE($1, username), - // email = COALESCE($2, email) - // WHERE id = $3 - // RETURNING id, username, email "#, - /// updated.username, - /// updated.email, - /// user_id - /// ) - /// .fetch_one(&**client) - /// .await?; - - info!("=User updated: {:?}", user); + log::info!("User updated: {:?}", user); Ok(user) } diff --git a/backend/shared-logic/src/models.rs b/backend/shared-logic/src/models.rs index d9fac77..dcee0e4 100644 --- a/backend/shared-logic/src/models.rs +++ b/backend/shared-logic/src/models.rs @@ -1,5 +1,6 @@ use serde::{Deserialize, Serialize}; use chrono::{DateTime, Utc}; + use serde_json::Value; // Existing User struct (used for data coming OUT of the DB) @@ -8,8 +9,10 @@ pub struct User { pub id: i32, pub username: String, pub email: String, + pub password_hash: String, // store hashed password } + // Struct for creating a user (used for data coming INTO the API) // Because User derived Deserialize, the serde library (which Axum used to process incoming JSON request body) // expected all fields in User struct to be present in JSON you sent (id was not part of payload) @@ -17,14 +20,9 @@ pub struct User { pub struct NewUser { pub username: String, pub email: String, + pub password: String, // raw password comes from API request } -// Struct for updating a user (partial updates allowed) -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct UpdateUser { // the fields are optional, allowing you to update them individually if needed - pub username: Option, - pub email: Option, -} #[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)] pub struct TimeSeriesData { @@ -34,6 +32,13 @@ pub struct TimeSeriesData { pub metadata: Option, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateUser { + pub username: Option, + pub email: Option, + pub password: Option, // new field for updating password +} + // Struct for session data #[derive(Debug, Serialize, Deserialize, sqlx::FromRow)] pub struct Session { diff --git a/backend/test-lsl/Cargo.toml b/backend/test-lsl/Cargo.toml new file mode 100644 index 0000000..30cb15f --- /dev/null +++ b/backend/test-lsl/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "test-lsl" +version = "0.1.0" +edition = "2024" + +[dependencies] +lsl-sys = "0.1.1" diff --git a/backend/test-lsl/src/main.rs b/backend/test-lsl/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/backend/test-lsl/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 9c05975a7f17dd3c0b09c1e33deed81153df1dc0 Mon Sep 17 00:00:00 2001 From: hsn2004 Date: Sun, 23 Nov 2025 21:31:44 -0800 Subject: [PATCH 2/3] Adding changes in main.rs to align with user authentication implementation --- backend/api-server/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/api-server/src/main.rs b/backend/api-server/src/main.rs index da11164..8e98605 100644 --- a/backend/api-server/src/main.rs +++ b/backend/api-server/src/main.rs @@ -1,5 +1,6 @@ use axum::{ extract::State, + extract::Path, http::StatusCode, routing::{get, post}, Json, From 37605c4688c6bc27fe256224c1fd997449e01a56 Mon Sep 17 00:00:00 2001 From: hsn2004 Date: Thu, 19 Feb 2026 23:06:18 -0800 Subject: [PATCH 3/3] Deleted the auto-generated sqlx files --- ...236ecc769f3a38594dd41aaac230fac16b62f.json | 42 ------------------ ...17e5da23e90f92adbcb658e59aad31d9e4150.json | 42 ------------------ ...c1620736e9ad3720f6145df4c609309c927f6.json | 41 ------------------ ...71cef087a159c6ee7182d8ca929ecb748f3b7.json | 14 ------ ...afb487c1b540df63cf7998cce53476baaf253.json | 43 ------------------- ...f7262346d7647e4c279806f2e9c6172f0b89f.json | 41 ------------------ ...f69456beff2b3f5187ee3794b24aa55babdf5.json | 40 ----------------- ...292c9ce6cb8040a0f1c9e880ae9f1ca44eb1b.json | 38 ---------------- ...af172d41e1c7a5aa8e7b843f7973bb345e727.json | 42 ------------------ ...d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json | 41 ------------------ ...45af3582be51fcfd8a99336f5594c649674e1.json | 38 ---------------- ...7c461a755fccf5c37ebc642e9a53f1af1bcd3.json | 42 ------------------ ...8b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json | 42 ------------------ 13 files changed, 506 deletions(-) delete mode 100644 backend/.sqlx/query-0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f.json delete mode 100644 backend/.sqlx/query-232ccd067e3781e88d05778d24417e5da23e90f92adbcb658e59aad31d9e4150.json delete mode 100644 backend/.sqlx/query-4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6.json delete mode 100644 backend/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json delete mode 100644 backend/.sqlx/query-5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253.json delete mode 100644 backend/.sqlx/query-68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f.json delete mode 100644 backend/.sqlx/query-7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5.json delete mode 100644 backend/.sqlx/query-7bafdc18c840980e7fe701659f2292c9ce6cb8040a0f1c9e880ae9f1ca44eb1b.json delete mode 100644 backend/.sqlx/query-ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727.json delete mode 100644 backend/.sqlx/query-c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json delete mode 100644 backend/.sqlx/query-d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1.json delete mode 100644 backend/.sqlx/query-db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3.json delete mode 100644 backend/.sqlx/query-f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json diff --git a/backend/.sqlx/query-0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f.json b/backend/.sqlx/query-0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f.json deleted file mode 100644 index f9f5238..0000000 --- a/backend/.sqlx/query-0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET username = $1, email = $2 WHERE id = $3 RETURNING id, username, email, password_hash", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Int4" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "0defdb6b38ff44d5d4ba73e45c0236ecc769f3a38594dd41aaac230fac16b62f" -} diff --git a/backend/.sqlx/query-232ccd067e3781e88d05778d24417e5da23e90f92adbcb658e59aad31d9e4150.json b/backend/.sqlx/query-232ccd067e3781e88d05778d24417e5da23e90f92adbcb658e59aad31d9e4150.json deleted file mode 100644 index a086902..0000000 --- a/backend/.sqlx/query-232ccd067e3781e88d05778d24417e5da23e90f92adbcb658e59aad31d9e4150.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO test_time_series (timestamp, value, metadata) VALUES ($1, $2, $3) RETURNING id, timestamp, value, metadata", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "timestamp", - "type_info": "Timestamptz" - }, - { - "ordinal": 2, - "name": "value", - "type_info": "Float8" - }, - { - "ordinal": 3, - "name": "metadata", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Timestamptz", - "Float8", - "Text" - ] - }, - "nullable": [ - false, - false, - false, - true - ] - }, - "hash": "232ccd067e3781e88d05778d24417e5da23e90f92adbcb658e59aad31d9e4150" -} diff --git a/backend/.sqlx/query-4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6.json b/backend/.sqlx/query-4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6.json deleted file mode 100644 index 0f5b6f0..0000000 --- a/backend/.sqlx/query-4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET password_hash = $1 WHERE id = $2 RETURNING id, username, email, password_hash", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Int4" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "4ca9e69e9cd8d371c8f3f62115bc1620736e9ad3720f6145df4c609309c927f6" -} diff --git a/backend/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json b/backend/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json deleted file mode 100644 index cf6c805..0000000 --- a/backend/.sqlx/query-50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM users WHERE id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [] - }, - "hash": "50293c2e54af11d4c2a553e29b671cef087a159c6ee7182d8ca929ecb748f3b7" -} diff --git a/backend/.sqlx/query-5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253.json b/backend/.sqlx/query-5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253.json deleted file mode 100644 index effed01..0000000 --- a/backend/.sqlx/query-5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET username = $1, email = $2, password_hash = $3 WHERE id = $4 RETURNING id, username, email, password_hash", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text", - "Int4" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "5c1ab37e0e930c0f3049ea98fdbafb487c1b540df63cf7998cce53476baaf253" -} diff --git a/backend/.sqlx/query-68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f.json b/backend/.sqlx/query-68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f.json deleted file mode 100644 index 7213d2c..0000000 --- a/backend/.sqlx/query-68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET email = $1 WHERE id = $2 RETURNING id, username, email, password_hash", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Int4" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "68ed910f4a417aa54a35db00a93f7262346d7647e4c279806f2e9c6172f0b89f" -} diff --git a/backend/.sqlx/query-7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5.json b/backend/.sqlx/query-7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5.json deleted file mode 100644 index c7b8471..0000000 --- a/backend/.sqlx/query-7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, username, email, password_hash FROM users WHERE email = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "7359e2da92b45a195b98b059989f69456beff2b3f5187ee3794b24aa55babdf5" -} diff --git a/backend/.sqlx/query-7bafdc18c840980e7fe701659f2292c9ce6cb8040a0f1c9e880ae9f1ca44eb1b.json b/backend/.sqlx/query-7bafdc18c840980e7fe701659f2292c9ce6cb8040a0f1c9e880ae9f1ca44eb1b.json deleted file mode 100644 index 5f1a24f..0000000 --- a/backend/.sqlx/query-7bafdc18c840980e7fe701659f2292c9ce6cb8040a0f1c9e880ae9f1ca44eb1b.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, timestamp, value, metadata FROM test_time_series", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "timestamp", - "type_info": "Timestamptz" - }, - { - "ordinal": 2, - "name": "value", - "type_info": "Float8" - }, - { - "ordinal": 3, - "name": "metadata", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false, - false, - true - ] - }, - "hash": "7bafdc18c840980e7fe701659f2292c9ce6cb8040a0f1c9e880ae9f1ca44eb1b" -} diff --git a/backend/.sqlx/query-ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727.json b/backend/.sqlx/query-ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727.json deleted file mode 100644 index 130fc60..0000000 --- a/backend/.sqlx/query-ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users (username, email, password_hash) VALUES ($1, $2, $3) RETURNING id, username, email, password_hash", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "ac14da374cf38c2625ed478155baf172d41e1c7a5aa8e7b843f7973bb345e727" -} diff --git a/backend/.sqlx/query-c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json b/backend/.sqlx/query-c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json deleted file mode 100644 index b317d54..0000000 --- a/backend/.sqlx/query-c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET username = $1 WHERE id = $2 RETURNING id, username, email, password_hash", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Int4" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "c90b5ff74f3f075f08b371a0fb6d18dd3fabd7c5bd5e5eaf3f618a5fbcd77abd" -} diff --git a/backend/.sqlx/query-d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1.json b/backend/.sqlx/query-d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1.json deleted file mode 100644 index 05ee42d..0000000 --- a/backend/.sqlx/query-d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT id, username, email, password_hash FROM users", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "d8a2e5a84611aba22339339296a45af3582be51fcfd8a99336f5594c649674e1" -} diff --git a/backend/.sqlx/query-db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3.json b/backend/.sqlx/query-db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3.json deleted file mode 100644 index b4ba2a5..0000000 --- a/backend/.sqlx/query-db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET username = $1, password_hash = $2 WHERE id = $3 RETURNING id, username, email, password_hash", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Int4" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "db084199deff63e4f835ebd091e7c461a755fccf5c37ebc642e9a53f1af1bcd3" -} diff --git a/backend/.sqlx/query-f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json b/backend/.sqlx/query-f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json deleted file mode 100644 index 3c3c686..0000000 --- a/backend/.sqlx/query-f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE users SET email = $1, password_hash = $2 WHERE id = $3 RETURNING id, username, email, password_hash", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - }, - { - "ordinal": 1, - "name": "username", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "password_hash", - "type_info": "Text" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Int4" - ] - }, - "nullable": [ - false, - false, - false, - false - ] - }, - "hash": "f7d9635ff56737d43d1afb740f08b90a4f22fe46d0a5aa8df53b28f79ecfcdae" -}