diff options
| -rw-r--r-- | sqlx-data.json | 154 | ||||
| -rw-r--r-- | src/api/users.rs | 36 | ||||
| -rw-r--r-- | src/services/db.rs | 48 |
3 files changed, 238 insertions, 0 deletions
diff --git a/sqlx-data.json b/sqlx-data.json index eab27cc..244b873 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -127,6 +127,160 @@ }, "query": "SELECT EXISTS(SELECT user_id FROM users WHERE username = ?) as \"e: bool\"" }, + "370adb0e591634e2bc2e0cd904881f79192869a250eb6e1ba02e5074544a58cf": { + "describe": { + "columns": [ + { + "name": "user_id", + "ordinal": 0, + "type_info": { + "char_set": 63, + "flags": { + "bits": 4231 + }, + "max_size": 16, + "type": "String" + } + }, + { + "name": "username", + "ordinal": 1, + "type_info": { + "char_set": 224, + "flags": { + "bits": 4101 + }, + "max_size": 1020, + "type": "VarString" + } + }, + { + "name": "password_hash", + "ordinal": 2, + "type_info": { + "char_set": 63, + "flags": { + "bits": 4241 + }, + "max_size": 255, + "type": "Blob" + } + }, + { + "name": "password_salt", + "ordinal": 3, + "type_info": { + "char_set": 63, + "flags": { + "bits": 4241 + }, + "max_size": 255, + "type": "Blob" + } + }, + { + "name": "password_version", + "ordinal": 4, + "type_info": { + "char_set": 63, + "flags": { + "bits": 33 + }, + "max_size": 10, + "type": "Long" + } + } + ], + "nullable": [ + false, + false, + false, + false, + false + ], + "parameters": { + "Right": 3 + } + }, + "query": "SELECT user_id, username, password_hash, password_salt, password_version\n\t\t FROM users\n\t\t WHERE LOCATE(?, username) != 0\n\t\t LIMIT ?\n\t\t OFFSET ?" + }, + "65b0c5cb580ec3b8867ba7733cda3fddb665e1a5581911d8f0d4c64891871521": { + "describe": { + "columns": [ + { + "name": "user_id", + "ordinal": 0, + "type_info": { + "char_set": 63, + "flags": { + "bits": 4231 + }, + "max_size": 16, + "type": "String" + } + }, + { + "name": "username", + "ordinal": 1, + "type_info": { + "char_set": 224, + "flags": { + "bits": 4101 + }, + "max_size": 1020, + "type": "VarString" + } + }, + { + "name": "password_hash", + "ordinal": 2, + "type_info": { + "char_set": 63, + "flags": { + "bits": 4241 + }, + "max_size": 255, + "type": "Blob" + } + }, + { + "name": "password_salt", + "ordinal": 3, + "type_info": { + "char_set": 63, + "flags": { + "bits": 4241 + }, + "max_size": 255, + "type": "Blob" + } + }, + { + "name": "password_version", + "ordinal": 4, + "type_info": { + "char_set": 63, + "flags": { + "bits": 33 + }, + "max_size": 10, + "type": "Long" + } + } + ], + "nullable": [ + false, + false, + false, + false, + false + ], + "parameters": { + "Right": 1 + } + }, + "query": "SELECT user_id, username, password_hash, password_salt, password_version\n\t\t FROM users\n\t\t WHERE LOCATE(?, username) != 0" + }, "75241e6a872d943242a56e45e1ea9338caff4863a81c2fc6a2f02e6fa2762e29": { "describe": { "columns": [], diff --git a/src/api/users.rs b/src/api/users.rs index 6e00336..b2dd1c7 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -23,6 +23,41 @@ impl From<User> for UserResponse { } } +#[get("/")] +async fn search_users( + web::Query(username): web::Query<Option<Box<str>>>, + web::Query(limit): web::Query<Option<u32>>, + web::Query(offset): web::Query<Option<u32>>, + conn: web::Data<MySqlPool>, +) -> HttpResponse { + let conn = conn.get_ref(); + + let username = username.unwrap_or_default(); + let offset = offset.unwrap_or_default(); + + let results: Box<[UserResponse]> = if let Some(limit) = limit { + db::search_users_limit(conn, &username, offset, limit) + .await + .unwrap() + .iter() + .cloned() + .map(|u| u.into()) + .collect() + } else { + db::search_users(conn, &username) + .await + .unwrap() + .into_iter() + .skip(offset as usize) + .cloned() + .map(|u| u.into()) + .collect() + }; + + let response = HttpResponse::Ok().json(results); + response +} + #[derive(Debug, Clone, Error)] #[error("No user with the given ID exists")] struct UserNotFoundError { @@ -224,6 +259,7 @@ async fn update_password( pub fn service() -> Scope { web::scope("/users") + .service(search_users) .service(get_user) .service(get_username) .service(create_user) diff --git a/src/services/db.rs b/src/services/db.rs index b24c640..d717594 100644 --- a/src/services/db.rs +++ b/src/services/db.rs @@ -101,6 +101,54 @@ pub async fn get_user_by_username<'c>( Ok(Some(record.try_into()?)) } +pub async fn search_users<'c>( + conn: impl Executor<'c, Database = MySql>, + username: &str, +) -> Result<Box<[User]>, RawUnexpected> { + let username = format!("%{username}%"); + let records = query_as!( + UserRow, + r"SELECT user_id, username, password_hash, password_salt, password_version + FROM users + WHERE LOCATE(?, username) != 0", + username, + ) + .fetch_all(conn) + .await?; + + Ok(records + .into_iter() + .map(|u| u.try_into()) + .collect::<Result<Box<[User]>, RawUnexpected>>()?) +} + +pub async fn search_users_limit<'c>( + conn: impl Executor<'c, Database = MySql>, + username: &str, + offset: u32, + limit: u32, +) -> Result<Box<[User]>, RawUnexpected> { + let username = format!("%{username}%"); + let records = query_as!( + UserRow, + r"SELECT user_id, username, password_hash, password_salt, password_version + FROM users + WHERE LOCATE(?, username) != 0 + LIMIT ? + OFFSET ?", + username, + offset, + limit + ) + .fetch_all(conn) + .await?; + + Ok(records + .into_iter() + .map(|u| u.try_into()) + .collect::<Result<Box<[User]>, RawUnexpected>>()?) +} + pub async fn get_username<'c>( conn: impl Executor<'c, Database = MySql>, user_id: Uuid, |
