fix(client): fixes for user and token CRUD operations

This commit is contained in:
Scott Lyons 2024-08-11 00:37:15 -07:00
commit 1a438119a3
6 changed files with 180 additions and 55 deletions

1
Cargo.lock generated
View file

@ -1421,6 +1421,7 @@ dependencies = [
name = "szurubooru-integration-test"
version = "0.1.0"
dependencies = [
"chrono",
"szurubooru-client",
"tokio",
"tracing",

View file

@ -13,10 +13,7 @@ use serde_json::Value;
use sha1::{Digest, Sha1};
use std::fmt::{Display, Formatter};
use std::path::Path;
use std::{
fs::File,
io::{BufReader, Read},
};
use std::{fs::File, io::Read};
use url::Url;
///
@ -324,7 +321,7 @@ impl<'a> SzurubooruRequest<'a> {
// This doesn't detect the required `mut` for some reason
#[allow(unused_mut)]
let mut req = self.client.client.request(method, req_url);
let req = match &self.client.auth {
match &self.client.auth {
SzurubooruAuth::TokenAuth(t) => {
let mut header_map = HeaderMap::new();
header_map.append(AUTHORIZATION, t.parse().unwrap());
@ -333,8 +330,7 @@ impl<'a> SzurubooruRequest<'a> {
}
SzurubooruAuth::BasicAuth(u, p) => req.basic_auth(u, Some(p)),
SzurubooruAuth::None => req,
};
req
}
}
#[tracing::instrument(skip(self), fields(base_url=self.client.base_url.to_string()))]
@ -767,7 +763,7 @@ impl<'a> SzurubooruRequest<'a> {
};
let mut thumbnail_file = if let Some(t) = thumbnail {
if let None = filename {
if filename.is_none() {
filename = Some(
t.as_ref()
.file_name()
@ -832,7 +828,7 @@ impl<'a> SzurubooruRequest<'a> {
content_response
.bytes()
.await
.map_err(|e| SzurubooruClientError::RequestError(e))
.map_err(SzurubooruClientError::RequestError)
}
/// Retrieves posts that look like the input image
@ -861,24 +857,12 @@ impl<'a> SzurubooruRequest<'a> {
/// Searches for an exact match of a file based on the SHA1 checksum
pub async fn posts_for_file(
&self,
file: &mut File,
mut file: &mut File,
) -> SzurubooruResult<PagedSearchResult<PostResource>> {
let mut buffer = [0; 1024];
let mut hasher = Sha1::new();
let mut reader = BufReader::new(file);
loop {
let n = reader
.read(&mut buffer)
.map_err(SzurubooruClientError::IOError)?;
hasher.update(buffer);
if n == 0 {
break;
}
}
let result = hasher.finalize();
let hex_string = hex::encode(result);
std::io::copy(&mut file, &mut hasher).map_err(SzurubooruClientError::IOError)?;
let hash = hasher.finalize();
let hex_string = hex::encode(hash);
let qt = QueryToken::token(PostNamedToken::ContentChecksum, hex_string);
self.list_posts(Some(&vec![qt])).await
@ -1188,6 +1172,7 @@ impl<'a> SzurubooruRequest<'a> {
path: &str,
new_user: &CreateUpdateUser,
file: Option<&mut File>,
file_name: Option<impl AsRef<str>>,
) -> SzurubooruResult<UserResource> {
match file {
None => self.do_request(method, path, None, Some(new_user)).await,
@ -1198,7 +1183,9 @@ impl<'a> SzurubooruRequest<'a> {
.map_err(SzurubooruClientError::JSONSerializationError)?;
let metadata_part = Part::text(metadata_str);
let content_part = self.part_from_file(file)?;
let content_part = self
.part_from_file(file)?
.file_name(file_name.unwrap().as_ref().to_string());
let form = Form::new()
.part("avatar", content_part)
@ -1228,9 +1215,16 @@ impl<'a> SzurubooruRequest<'a> {
pub async fn create_user_with_avatar_file(
&self,
avatar: &mut File,
file_name: impl AsRef<str>,
new_user: &CreateUpdateUser,
) -> SzurubooruResult<UserResource> {
self.create_update_user(Method::POST, "/users", new_user, Some(avatar))
self.create_update_user(
Method::POST,
"/users",
new_user,
Some(avatar),
Some(file_name),
)
.await
}
@ -1242,8 +1236,15 @@ impl<'a> SzurubooruRequest<'a> {
avatar_path: impl AsRef<Path>,
new_user: &CreateUpdateUser,
) -> SzurubooruResult<UserResource> {
let mut file = File::open(avatar_path).map_err(SzurubooruClientError::IOError)?;
self.create_update_user(Method::POST, "/users", new_user, Some(&mut file))
let mut file = File::open(&avatar_path).map_err(SzurubooruClientError::IOError)?;
let filename = avatar_path.as_ref().file_name().unwrap().to_str().unwrap();
self.create_update_user(
Method::POST,
"/users",
new_user,
Some(&mut file),
Some(filename),
)
.await
}
@ -1262,8 +1263,8 @@ impl<'a> SzurubooruRequest<'a> {
where
T: AsRef<str> + Display,
{
let path = format!("/users/{name}");
self.do_request(Method::POST, path, None, Some(update_user))
let path = format!("/user/{name}");
self.do_request(Method::PUT, path, None, Some(update_user))
.await
}
@ -1274,13 +1275,20 @@ impl<'a> SzurubooruRequest<'a> {
&self,
name: T,
avatar: &mut File,
file_name: impl AsRef<str>,
update_user: &CreateUpdateUser,
) -> SzurubooruResult<UserResource>
where
T: AsRef<str> + Display,
{
let path = format!("/users/{name}");
self.create_update_user(Method::POST, &path, update_user, Some(avatar))
let path = format!("/user/{name}");
self.create_update_user(
Method::PUT,
&path,
update_user,
Some(avatar),
Some(file_name),
)
.await
}
@ -1296,9 +1304,16 @@ impl<'a> SzurubooruRequest<'a> {
where
T: AsRef<str> + Display,
{
let path = format!("/users/{name}");
let mut file = File::open(avatar_path).map_err(SzurubooruClientError::IOError)?;
self.create_update_user(Method::POST, &path, new_user, Some(&mut file))
let path = format!("/user/{name}");
let mut file = File::open(&avatar_path).map_err(SzurubooruClientError::IOError)?;
let filename = avatar_path.as_ref().file_name().unwrap().to_str().unwrap();
self.create_update_user(
Method::PUT,
&path,
new_user,
Some(&mut file),
Some(filename),
)
.await
}
@ -1307,7 +1322,7 @@ impl<'a> SzurubooruRequest<'a> {
where
T: AsRef<str> + Display,
{
let path = format!("/users/{name}");
let path = format!("/user/{name}");
self.do_request(Method::GET, &path, None, None::<&String>)
.await
}
@ -1317,10 +1332,11 @@ impl<'a> SzurubooruRequest<'a> {
where
T: AsRef<str> + Display,
{
let path = format!("/users/{name}");
let path = format!("/user/{name}");
let version_obj = ResourceVersion { version };
self.do_request(Method::DELETE, &path, None, Some(&version_obj))
self.do_request::<Value, _, _>(Method::DELETE, &path, None, Some(&version_obj))
.await
.map(|_| ())
}
/// Listing user tokens for the given user.
@ -1346,7 +1362,7 @@ impl<'a> SzurubooruRequest<'a> {
where
T: AsRef<str> + Display,
{
let path = format!("/user-tokens/{name}");
let path = format!("/user-token/{name}");
self.do_request(Method::POST, &path, None, Some(create_token))
.await
}
@ -1363,7 +1379,7 @@ impl<'a> SzurubooruRequest<'a> {
where
T: AsRef<str> + Display,
{
let path = format!("/user-tokens/{name}/{token}");
let path = format!("/user-token/{name}/{token}");
self.do_request(Method::PUT, &path, None, Some(update_token))
.await
}
@ -1380,10 +1396,11 @@ impl<'a> SzurubooruRequest<'a> {
where
T: AsRef<str> + Display,
{
let path = format!("/user-tokens/{name}/{token}");
let path = format!("/user-token/{name}/{token}");
let version_obj = ResourceVersion { version };
self.do_request(Method::DELETE, &path, None, Some(&version_obj))
self.do_request::<Value, _, _>(Method::DELETE, &path, None, Some(&version_obj))
.await
.map(|_| ())
}
/// Sends a confirmation email to given user. The email contains link containing a token. The

View file

@ -510,7 +510,7 @@ pub struct UserResource {
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, Builder)]
#[builder(setter(into, strip_option))]
#[builder(setter(strip_option))]
#[serde(rename_all = "camelCase")]
/// `struct` used to create or update a user resource. The version field is only used when
/// updating an existing resource

View file

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
chrono = "0.4.38"
szurubooru-client = { path = "../szurubooru-client" }
tokio = { version = "1.39.2", features = ["full", "test-util", "tracing"] }
tracing = "0.1.40"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,3 +1,4 @@
use chrono::Months;
use std::error::Error;
use std::fs::File;
use std::path::Path;
@ -29,8 +30,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
let anon_client = start_instance().await;
let create_user = CreateUpdateUserBuilder::default()
.name("integration_user")
.password("integration_password")
.name("integration_user".to_string())
.password("integration_password".to_string())
.rank(UserRank::Administrator)
.avatar_style(UserAvatarStyle::Gravatar)
.build()
@ -54,6 +55,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
test_pool_categories(&auth_client).await;
test_pools(&auth_client).await;
test_comments(&auth_client).await;
test_users(&auth_client).await;
Command::new("sh")
.current_dir(env!("CARGO_MANIFEST_DIR"))
@ -390,10 +392,18 @@ async fn test_creating_posts(client: &SzurubooruClient) {
let folly3_thumbnail = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly3_thumb.jpg");
let folly3_post = client
.request()
.create_post_from_file_path(folly3_path, Some(folly3_thumbnail), &folly3_obj)
.create_post_from_file_path(&folly3_path, Some(folly3_thumbnail), &folly3_obj)
.await
.expect("Could not create post with thumbnail");
info!("Searching for post by image");
let matching_posts = client
.request()
.posts_for_file_path(&folly3_path)
.await
.expect("Could not search for post by file path");
assert_eq!(matching_posts.results.first().unwrap(), &folly3_post);
info!("Testing temporary upload");
let folly4_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("folly4.jpg");
let folly4_temp_upload = client
@ -504,6 +514,7 @@ async fn test_creating_posts(client: &SzurubooruClient) {
.get_featured_post()
.await
.expect("Could not get featured post");
assert!(featured_post.is_some());
}
@ -725,10 +736,6 @@ async fn test_comments(client: &SzurubooruClient) {
.expect("Unable to fetch comment");
info!("Getting all comments for post");
let query_vec = vec![QueryToken::token(
CommentNamedToken::Post,
post_id.to_string(),
)];
let comment_list = client
.request()
//.list_comments(None)
@ -755,3 +762,102 @@ async fn test_comments(client: &SzurubooruClient) {
.await
.expect("Could not delete comment");
}
#[instrument(skip(client))]
async fn test_users(client: &SzurubooruClient) {
info!("Testing users");
info!("Listing users");
let user_list = client
.request()
.list_users(None)
.await
.expect("Could not list users");
assert_eq!(user_list.total, 1);
// Create user is already tested above
info!("Creating user with avatar");
let avatar_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("avatar.jpg");
let create_user = CreateUpdateUserBuilder::default()
.name("iu2".to_string())
.password("ipass2".to_string())
.rank(UserRank::Regular)
.avatar_style(UserAvatarStyle::Manual)
.build()
.expect("Could not create user creation object");
let user_obj = client
.request()
.create_user_with_avatar_path(avatar_path, &create_user)
.await
.expect("Could not create user");
info!("Updating user");
let update_user = CreateUpdateUserBuilder::default()
.version(user_obj.version.unwrap())
.rank(UserRank::Restricted)
.build()
.expect("Could not create user update object");
let user_obj = client
.request()
.update_user(user_obj.name.unwrap(), &update_user)
.await
.expect("Could not update user");
info!("Getting user");
let user_obj = client
.request()
.get_user(user_obj.name.unwrap())
.await
.expect("Could not get user");
info!("Deleting user");
client
.request()
.delete_user(user_obj.name.unwrap(), user_obj.version.unwrap())
.await
.expect("Could not delete user");
let username = "integration_user".to_string();
info!("Listing user tokens");
let tokens = client
.request()
.list_user_tokens(&username)
.await
.expect("Could not list user tokens");
assert!(tokens.results.is_empty());
info!("Creating user token");
let create_token = CreateUpdateUserAuthTokenBuilder::default()
.note("My token")
.enabled(true)
.build()
.expect("Could not create token creation object");
let token = client
.request()
.create_user_token(&username, &create_token)
.await
.expect("Could not create auth token");
info!("Updating user token");
let new_expiration = chrono::offset::Utc::now()
.checked_add_months(Months::new(1))
.unwrap();
let update_token = CreateUpdateUserAuthTokenBuilder::default()
.version(token.version.unwrap())
.expiration_time(new_expiration)
.build()
.expect("Could not create token update object");
let token = client
.request()
.update_user_token(&username, &token.token.unwrap(), &update_token)
.await
.expect("Could not update token");
info!("Deleting user token");
client
.request()
.delete_user_token(username, token.token.unwrap(), token.version.unwrap())
.await
.expect("Could not delete token");
}