From d9b360ac66ba60070a4acbab1f1f28c4b5912f10 Mon Sep 17 00:00:00 2001 From: Cyborus Date: Wed, 29 Nov 2023 14:20:19 -0500 Subject: [PATCH 01/13] add `admin` methods --- src/admin.rs | 443 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + 2 files changed, 445 insertions(+) create mode 100644 src/admin.rs diff --git a/src/admin.rs b/src/admin.rs new file mode 100644 index 0000000..6c08138 --- /dev/null +++ b/src/admin.rs @@ -0,0 +1,443 @@ +use super::*; + +use std::fmt::Write; +use std::collections::BTreeMap; + +impl Forgejo { + pub async fn admin_get_crons(&self, query: CronQuery) -> Result, ForgejoError> { + self.get(&query.path()).await + } + + pub async fn admin_run_cron(&self, name: &str) -> Result<(), ForgejoError> { + self.post(&format!("admin/cron/{name}"), &()).await + } + + pub async fn admin_get_emails(&self, query: EmailListQuery) -> Result, ForgejoError> { + self.get(&query.path()).await + } + + pub async fn admin_search_emails(&self, query: EmailSearchQuery) -> Result, ForgejoError> { + self.get(&query.path()).await + } + + pub async fn admin_get_hooks(&self, query: HookQuery) -> Result, ForgejoError> { + self.get(&query.path()).await + } + + pub async fn admin_create_hook(&self, opt: CreateHookOption) -> Result { + self.post("admin/hooks", &opt).await + } + + pub async fn admin_get_hook(&self, id: u64) -> Result, ForgejoError> { + self.get_opt(&format!("admin/hooks/{id}")).await + } + + pub async fn admin_delete_hook(&self, id: u64) -> Result<(), ForgejoError> { + self.delete(&format!("admin/hooks/{id}")).await + } + + pub async fn admin_edit_hook(&self, id: u64, opt: EditHookOption) -> Result { + self.patch(&format!("admin/hooks/{id}"), &opt).await + } + + pub async fn admin_get_orgs(&self, query: AdminOrganizationQuery) -> Result, ForgejoError> { + self.get(&query.path()).await + } + + pub async fn admin_unadopted_repos(&self, query: UnadoptedRepoQuery) -> Result, ForgejoError> { + self.get(&query.path()).await + } + + pub async fn admin_adopt(&self, owner: &str, repo: &str) -> Result<(), ForgejoError> { + self.post(&format!("admin/unadopted/{owner}/{repo}"), &()).await + } + + pub async fn admin_delete_unadopted(&self, owner: &str, repo: &str) -> Result<(), ForgejoError> { + self.delete(&format!("admin/unadopted/{owner}/{repo}")).await + } + + pub async fn admin_users(&self, query: AdminUserQuery) -> Result, ForgejoError> { + self.get(&query.path()).await + } + + pub async fn admin_create_user(&self, opt: CreateUserOption) -> Result { + self.post("admin/users", &opt).await + } + + pub async fn admin_delete_user(&self, user: &str, purge: bool) -> Result<(), ForgejoError> { + self.delete(&format!("admin/users/{user}?purge={purge}")).await + } + + pub async fn admin_edit_user(&self, user: &str, opt: CreateUserOption) -> Result { + self.patch(&format!("admin/users/{user}"), &opt).await + } + + pub async fn admin_add_key(&self, user: &str, opt: CreateKeyOption) -> Result { + self.patch(&format!("admin/users/{user}/keys"), &opt).await + } + + pub async fn admin_delete_key(&self, user: &str, id: u64) -> Result<(), ForgejoError> { + self.delete(&format!("admin/users/{user}/keys/{id}")).await + } + + pub async fn admin_create_org(&self, owner: &str, opt: CreateOrgOption) -> Result { + self.post(&format!("admin/users/{owner}/orgs"), &opt).await + } + + pub async fn admin_rename_user(&self, user: &str, opt: RenameUserOption) -> Result<(), ForgejoError> { + self.post(&format!("admin/users/{user}/rename"), &opt).await + } + + pub async fn admin_create_repo(&self, owner: &str, opt: CreateRepoOption) -> Result { + self.post(&format!("admin/users/{owner}/repos"), &opt).await + } +} + +#[derive(serde::Deserialize, Debug, PartialEq)] +pub struct Cron { + pub exec_times: u64, + pub name: String, + #[serde(with = "time::serde::rfc3339")] + pub next: time::OffsetDateTime, + #[serde(with = "time::serde::rfc3339")] + pub prev: time::OffsetDateTime, + pub schedule: String, +} + +#[derive(Default, Debug)] +pub struct CronQuery { + pub page: Option, + pub limit: Option, +} + +impl CronQuery { + fn path(&self) -> String { + let mut s = String::from("admin/cron?"); + if let Some(page) = self.page { + s.push_str("page="); + s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.push('&'); + } + if let Some(limit) = self.limit { + s.push_str("limit="); + s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.push('&'); + } + s + } +} + +#[derive(serde::Deserialize, Debug, PartialEq)] +pub struct Email { + pub email: String, + pub primary: bool, + pub user_id: u64, + pub username: String, + pub verified: bool, +} + +#[derive(Default, Debug)] +pub struct EmailListQuery { + pub page: Option, + pub limit: Option, +} + +impl EmailListQuery { + fn path(&self) -> String { + let mut s = String::from("admin/emails?"); + if let Some(page) = self.page { + s.push_str("page="); + s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.push('&'); + } + if let Some(limit) = self.limit { + s.push_str("limit="); + s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.push('&'); + } + s + } +} + +#[derive(Default, Debug)] +pub struct EmailSearchQuery { + pub query: String, + pub page: Option, + pub limit: Option, +} + +impl EmailSearchQuery { + fn path(&self) -> String { + let mut s = String::from("admin/emails/search?"); + if !self.query.is_empty() { + s.push_str("q="); + s.push_str(&self.query); + s.push('&'); + } + if let Some(page) = self.page { + s.push_str("page="); + s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.push('&'); + } + if let Some(limit) = self.limit { + s.push_str("limit="); + s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.push('&'); + } + s + } +} + +#[derive(serde::Deserialize, Debug, PartialEq)] +pub struct Hook { + pub active: bool, + pub authorization_header: String, + pub branch_filter: String, + pub config: std::collections::BTreeMap, + #[serde(with = "time::serde::rfc3339")] + pub created_at: time::OffsetDateTime, + pub events: Vec, + pub id: u64, + #[serde(rename = "type")] + pub _type: HookType, + #[serde(with = "time::serde::rfc3339")] + pub updated_at: time::OffsetDateTime, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)] +#[non_exhaustive] +#[serde(rename_all = "lowercase")] +pub enum HookType { + Forgejo, + Dingtalk, + Discord, + Gitea, + Gogs, + Msteams, + Slack, + Telegram, + Feishu, + Wechatwork, + Packagist +} + +#[derive(Default, Debug)] +pub struct HookQuery { + pub page: Option, + pub limit: Option, +} + +impl HookQuery { + fn path(&self) -> String { + let mut s = String::from("admin/hooks?"); + if let Some(page) = self.page { + s.push_str("page="); + s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.push('&'); + } + if let Some(limit) = self.limit { + s.push_str("limit="); + s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.push('&'); + } + s + } +} + +#[derive(serde::Serialize, Debug, PartialEq)] +pub struct CreateHookOption { + pub active: Option, + pub authorization_header: Option, + pub branch_filter: Option, + pub config: CreateHookOptionConfig, + pub events: Vec, + #[serde(rename = "type")] + pub _type: HookType, +} + +#[derive(serde::Serialize, Debug, PartialEq)] +pub struct CreateHookOptionConfig { + pub content_type: String, + pub url: Url, + #[serde(flatten)] + pub other: BTreeMap, +} + +#[derive(serde::Serialize, Debug, PartialEq, Default)] +pub struct EditHookOption { + pub active: Option, + pub authorization_header: Option, + pub branch_filter: Option, + pub config: Option>, + pub events: Option>, +} + +#[derive(Default, Debug)] +pub struct AdminOrganizationQuery { + pub page: Option, + pub limit: Option, +} + +impl AdminOrganizationQuery { + fn path(&self) -> String { + let mut s = String::from("admin/orgs?"); + if let Some(page) = self.page { + s.push_str("page="); + s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.push('&'); + } + if let Some(limit) = self.limit { + s.push_str("limit="); + s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.push('&'); + } + s + } +} + +#[derive(Default, Debug)] +pub struct UnadoptedRepoQuery { + pub page: Option, + pub limit: Option, + pub pattern: String, +} + +impl UnadoptedRepoQuery { + fn path(&self) -> String { + let mut s = String::from("admin/unadopted?"); + if let Some(page) = self.page { + s.push_str("page="); + s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.push('&'); + } + if let Some(limit) = self.limit { + s.push_str("limit="); + s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.push('&'); + } + if !self.pattern.is_empty() { + s.push_str("pattern="); + s.push_str(&self.pattern); + s.push('&'); + } + s + } +} + +#[derive(Default, Debug)] +pub struct AdminUserQuery { + pub source_id: Option, + pub login_name: String, + pub page: Option, + pub limit: Option, +} + +impl AdminUserQuery { + fn path(&self) -> String { + let mut s = String::from("admin/users?"); + if let Some(source_id) = self.source_id { + s.push_str("source_id="); + s.write_fmt(format_args!("{source_id}")).expect("writing to string can't fail"); + s.push('&'); + } + if !self.login_name.is_empty() { + s.push_str("login_name="); + s.push_str(&self.login_name); + s.push('&'); + } + if let Some(page) = self.page { + s.push_str("page="); + s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.push('&'); + } + if let Some(limit) = self.limit { + s.push_str("limit="); + s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.push('&'); + } + s + } +} + +#[derive(serde::Serialize, Debug, PartialEq)] +pub struct CreateUserOption { + #[serde(with = "time::serde::rfc3339::option")] + pub created_at: Option, + pub email: String, + pub full_name: Option, + pub login_name: Option, + pub must_change_password: bool, + pub password: String, + pub restricted: bool, + pub send_notify: bool, + pub source_id: Option, + pub username: String, + pub visibility: String, +} + +#[derive(serde::Serialize, Debug, PartialEq, Default)] +pub struct EditUserOption { + pub active: Option, + pub admin: Option, + pub allow_create_organization: Option, + pub allow_git_hook: Option, + pub allow_import_local: Option, + pub description: Option, + pub email: Option, + pub full_name: Option, + pub location: Option, + pub login_name: Option, + pub max_repo_creation: Option, + pub must_change_password: Option, + pub password: Option, + pub prohibit_login: Option, + pub restricted: Option, + pub source_id: Option, + pub visibility: Option, + pub website: Option, +} + +#[derive(serde::Serialize, Debug, PartialEq)] +pub struct CreateKeyOption { + pub key: String, + pub read_only: Option, + pub title: String, +} + +#[derive(serde::Deserialize, Debug, PartialEq)] +pub struct PublicKey { + #[serde(with = "time::serde::rfc3339")] + pub created_at: time::OffsetDateTime, + pub fingerprint: String, + pub id: u64, + pub key: String, + pub key_type: String, + pub read_only: bool, + pub title: String, + pub url: Option, + pub user: User, +} + +#[derive(serde::Serialize, Debug, PartialEq)] +pub struct CreateOrgOption { + pub description: Option, + pub full_name: Option, + pub location: Option, + pub repo_admin_change_team_access: Option, + pub username: String, + pub visibility: OrgVisibility, + pub website: Option, +} + +#[derive(serde::Serialize, Debug, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum OrgVisibility { + Public, + Limited, + Private, +} + +#[derive(serde::Serialize, Debug, PartialEq)] +pub struct RenameUserOption { + pub new_username: String, +} diff --git a/src/lib.rs b/src/lib.rs index d9e8179..6a86d3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub struct Forgejo { } mod issue; +mod admin; mod misc; mod notification; mod organization; @@ -17,6 +18,7 @@ mod repository; mod user; pub use issue::*; +pub use admin::*; pub use misc::*; pub use notification::*; pub use organization::*; From ab6ac96464feb69271a6b85f68bb9cd3574ef8cd Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 13:08:11 -0500 Subject: [PATCH 02/13] add admin tests --- tests/ci_test.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/ci_test.rs b/tests/ci_test.rs index b949b7f..91b42eb 100644 --- a/tests/ci_test.rs +++ b/tests/ci_test.rs @@ -11,6 +11,7 @@ async fn ci() -> eyre::Result<()> { results.push(user(&api).await.wrap_err("user error")); results.push(repo(&api).await.wrap_err("repo error")); + results.push(admin(&api).await.wrap_err("repo error")); let mut errors = 0; for report in results.into_iter().filter_map(Result::err) { @@ -233,3 +234,79 @@ async fn repo(api: &forgejo_api::Forgejo) -> eyre::Result<()> { Ok(()) } + +async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { + let user_opt = forgejo_api::CreateUserOption { + created_at: None, + email: "user@noreply.example.org".into(), + full_name: None, + login_name: None, + must_change_password: false, + password: "userpass".into(), + restricted: false, + send_notify: true, + source_id: None, + username: "Pipis".into(), + visibility: "public".into(), + }; + let _ = api.admin_create_user(user_opt).await.wrap_err("failed to create user")?; + + let users = api.admin_users(forgejo_api::AdminUserQuery::default()).await.wrap_err("failed to search users")?; + ensure!(users.iter().find(|u| u.login == "Pipis").is_some(), "could not find new user"); + let users = api.admin_get_emails(forgejo_api::EmailListQuery::default()).await.wrap_err("failed to search emails")?; + ensure!(users.iter().find(|u| u.email == "user@noreply.example.org").is_some(), "could not find new user"); + + let org_opt = forgejo_api::CreateOrgOption { + description: None, + full_name: None, + location: None, + repo_admin_change_team_access: None, + username: "test-org".into(), + visibility: forgejo_api::OrgVisibility::Public, + website: None, + }; + let _ = api.admin_create_org("Pipis", org_opt).await.wrap_err("failed to create org")?; + ensure!(!api.admin_get_orgs(forgejo_api::AdminOrganizationQuery::default()).await?.is_empty(), "org list empty"); + + let key_opt = forgejo_api::CreateKeyOption { + key: "ssh-ed25519 00000000000000000000000000000000000000000000000000000000000000000000 user@noreply.example.org".into(), + read_only: None, + title: "Example Key".into(), + }; + let key = api.admin_add_key("Pipis", key_opt).await?; + api.admin_delete_key("Pipis", key.id).await?; + + let rename_opt = forgejo_api::RenameUserOption { + new_username: "Bepis".into(), + }; + api.admin_rename_user("Pipis", rename_opt).await.wrap_err("failed to rename user")?; + api.admin_delete_user("Bepis", true).await.wrap_err("failed to delete user")?; + ensure!(api.admin_delete_user("Ghost", true).await.is_err(), "deleting fake user should fail"); + + let crons = api.admin_get_crons(forgejo_api::CronQuery::default()).await.wrap_err("failed to get crons list")?; + api.admin_run_cron(&crons.get(0).ok_or_else(|| eyre!("no crons"))?.name).await.wrap_err("failed to run cron")?; + + let hook_opt = forgejo_api::CreateHookOption { + active: None, + authorization_header: None, + branch_filter: None, + config: forgejo_api::CreateHookOptionConfig { + content_type: "text/plain".into(), + url: url::Url::parse("http://test.local/").unwrap(), + other: Default::default(), + }, + events: Vec::new(), + _type: forgejo_api::HookType::Forgejo, + }; + // yarr har har me matey this is me hook + let hook = api.admin_create_hook(hook_opt).await.wrap_err("failed to create hook")?; + let edit_hook = forgejo_api::EditHookOption { + active: Some(true), + ..Default::default() + }; + api.admin_edit_hook(hook.id, edit_hook).await.wrap_err("failed to edit hook")?; + api.admin_delete_hook(hook.id).await.wrap_err("failed to delete hook")?; + + Ok(()) +} + From bd6d92f6b6b00d4e570e89098fe4d22b5e59d439 Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 13:08:32 -0500 Subject: [PATCH 03/13] format --- src/admin.rs | 146 +++++++++++++++++++++++++++++++++-------------- src/lib.rs | 4 +- tests/ci_test.rs | 76 ++++++++++++++++++------ 3 files changed, 164 insertions(+), 62 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 6c08138..f824d68 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -1,7 +1,7 @@ use super::*; -use std::fmt::Write; use std::collections::BTreeMap; +use std::fmt::Write; impl Forgejo { pub async fn admin_get_crons(&self, query: CronQuery) -> Result, ForgejoError> { @@ -12,11 +12,17 @@ impl Forgejo { self.post(&format!("admin/cron/{name}"), &()).await } - pub async fn admin_get_emails(&self, query: EmailListQuery) -> Result, ForgejoError> { + pub async fn admin_get_emails( + &self, + query: EmailListQuery, + ) -> Result, ForgejoError> { self.get(&query.path()).await } - pub async fn admin_search_emails(&self, query: EmailSearchQuery) -> Result, ForgejoError> { + pub async fn admin_search_emails( + &self, + query: EmailSearchQuery, + ) -> Result, ForgejoError> { self.get(&query.path()).await } @@ -36,24 +42,40 @@ impl Forgejo { self.delete(&format!("admin/hooks/{id}")).await } - pub async fn admin_edit_hook(&self, id: u64, opt: EditHookOption) -> Result { + pub async fn admin_edit_hook( + &self, + id: u64, + opt: EditHookOption, + ) -> Result { self.patch(&format!("admin/hooks/{id}"), &opt).await } - pub async fn admin_get_orgs(&self, query: AdminOrganizationQuery) -> Result, ForgejoError> { + pub async fn admin_get_orgs( + &self, + query: AdminOrganizationQuery, + ) -> Result, ForgejoError> { self.get(&query.path()).await } - pub async fn admin_unadopted_repos(&self, query: UnadoptedRepoQuery) -> Result, ForgejoError> { + pub async fn admin_unadopted_repos( + &self, + query: UnadoptedRepoQuery, + ) -> Result, ForgejoError> { self.get(&query.path()).await } - + pub async fn admin_adopt(&self, owner: &str, repo: &str) -> Result<(), ForgejoError> { - self.post(&format!("admin/unadopted/{owner}/{repo}"), &()).await + self.post(&format!("admin/unadopted/{owner}/{repo}"), &()) + .await } - pub async fn admin_delete_unadopted(&self, owner: &str, repo: &str) -> Result<(), ForgejoError> { - self.delete(&format!("admin/unadopted/{owner}/{repo}")).await + pub async fn admin_delete_unadopted( + &self, + owner: &str, + repo: &str, + ) -> Result<(), ForgejoError> { + self.delete(&format!("admin/unadopted/{owner}/{repo}")) + .await } pub async fn admin_users(&self, query: AdminUserQuery) -> Result, ForgejoError> { @@ -65,14 +87,23 @@ impl Forgejo { } pub async fn admin_delete_user(&self, user: &str, purge: bool) -> Result<(), ForgejoError> { - self.delete(&format!("admin/users/{user}?purge={purge}")).await + self.delete(&format!("admin/users/{user}?purge={purge}")) + .await } - pub async fn admin_edit_user(&self, user: &str, opt: CreateUserOption) -> Result { + pub async fn admin_edit_user( + &self, + user: &str, + opt: CreateUserOption, + ) -> Result { self.patch(&format!("admin/users/{user}"), &opt).await } - pub async fn admin_add_key(&self, user: &str, opt: CreateKeyOption) -> Result { + pub async fn admin_add_key( + &self, + user: &str, + opt: CreateKeyOption, + ) -> Result { self.patch(&format!("admin/users/{user}/keys"), &opt).await } @@ -80,15 +111,27 @@ impl Forgejo { self.delete(&format!("admin/users/{user}/keys/{id}")).await } - pub async fn admin_create_org(&self, owner: &str, opt: CreateOrgOption) -> Result { + pub async fn admin_create_org( + &self, + owner: &str, + opt: CreateOrgOption, + ) -> Result { self.post(&format!("admin/users/{owner}/orgs"), &opt).await } - - pub async fn admin_rename_user(&self, user: &str, opt: RenameUserOption) -> Result<(), ForgejoError> { + + pub async fn admin_rename_user( + &self, + user: &str, + opt: RenameUserOption, + ) -> Result<(), ForgejoError> { self.post(&format!("admin/users/{user}/rename"), &opt).await } - - pub async fn admin_create_repo(&self, owner: &str, opt: CreateRepoOption) -> Result { + + pub async fn admin_create_repo( + &self, + owner: &str, + opt: CreateRepoOption, + ) -> Result { self.post(&format!("admin/users/{owner}/repos"), &opt).await } } @@ -115,12 +158,14 @@ impl CronQuery { let mut s = String::from("admin/cron?"); if let Some(page) = self.page { s.push_str("page="); - s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{page}")) + .expect("writing to string can't fail"); s.push('&'); } if let Some(limit) = self.limit { s.push_str("limit="); - s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{limit}")) + .expect("writing to string can't fail"); s.push('&'); } s @@ -147,12 +192,14 @@ impl EmailListQuery { let mut s = String::from("admin/emails?"); if let Some(page) = self.page { s.push_str("page="); - s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{page}")) + .expect("writing to string can't fail"); s.push('&'); } if let Some(limit) = self.limit { s.push_str("limit="); - s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{limit}")) + .expect("writing to string can't fail"); s.push('&'); } s @@ -176,12 +223,14 @@ impl EmailSearchQuery { } if let Some(page) = self.page { s.push_str("page="); - s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{page}")) + .expect("writing to string can't fail"); s.push('&'); } if let Some(limit) = self.limit { s.push_str("limit="); - s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{limit}")) + .expect("writing to string can't fail"); s.push('&'); } s @@ -208,17 +257,17 @@ pub struct Hook { #[non_exhaustive] #[serde(rename_all = "lowercase")] pub enum HookType { - Forgejo, - Dingtalk, - Discord, - Gitea, - Gogs, - Msteams, - Slack, - Telegram, - Feishu, - Wechatwork, - Packagist + Forgejo, + Dingtalk, + Discord, + Gitea, + Gogs, + Msteams, + Slack, + Telegram, + Feishu, + Wechatwork, + Packagist, } #[derive(Default, Debug)] @@ -232,12 +281,14 @@ impl HookQuery { let mut s = String::from("admin/hooks?"); if let Some(page) = self.page { s.push_str("page="); - s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{page}")) + .expect("writing to string can't fail"); s.push('&'); } if let Some(limit) = self.limit { s.push_str("limit="); - s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{limit}")) + .expect("writing to string can't fail"); s.push('&'); } s @@ -283,12 +334,14 @@ impl AdminOrganizationQuery { let mut s = String::from("admin/orgs?"); if let Some(page) = self.page { s.push_str("page="); - s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{page}")) + .expect("writing to string can't fail"); s.push('&'); } if let Some(limit) = self.limit { s.push_str("limit="); - s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{limit}")) + .expect("writing to string can't fail"); s.push('&'); } s @@ -307,12 +360,14 @@ impl UnadoptedRepoQuery { let mut s = String::from("admin/unadopted?"); if let Some(page) = self.page { s.push_str("page="); - s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{page}")) + .expect("writing to string can't fail"); s.push('&'); } if let Some(limit) = self.limit { s.push_str("limit="); - s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{limit}")) + .expect("writing to string can't fail"); s.push('&'); } if !self.pattern.is_empty() { @@ -337,7 +392,8 @@ impl AdminUserQuery { let mut s = String::from("admin/users?"); if let Some(source_id) = self.source_id { s.push_str("source_id="); - s.write_fmt(format_args!("{source_id}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{source_id}")) + .expect("writing to string can't fail"); s.push('&'); } if !self.login_name.is_empty() { @@ -347,12 +403,14 @@ impl AdminUserQuery { } if let Some(page) = self.page { s.push_str("page="); - s.write_fmt(format_args!("{page}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{page}")) + .expect("writing to string can't fail"); s.push('&'); } if let Some(limit) = self.limit { s.push_str("limit="); - s.write_fmt(format_args!("{limit}")).expect("writing to string can't fail"); + s.write_fmt(format_args!("{limit}")) + .expect("writing to string can't fail"); s.push('&'); } s diff --git a/src/lib.rs b/src/lib.rs index 6a86d3e..21b0a45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,8 +8,8 @@ pub struct Forgejo { client: Client, } -mod issue; mod admin; +mod issue; mod misc; mod notification; mod organization; @@ -17,8 +17,8 @@ mod package; mod repository; mod user; -pub use issue::*; pub use admin::*; +pub use issue::*; pub use misc::*; pub use notification::*; pub use organization::*; diff --git a/tests/ci_test.rs b/tests/ci_test.rs index 91b42eb..e059799 100644 --- a/tests/ci_test.rs +++ b/tests/ci_test.rs @@ -249,12 +249,30 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { username: "Pipis".into(), visibility: "public".into(), }; - let _ = api.admin_create_user(user_opt).await.wrap_err("failed to create user")?; + let _ = api + .admin_create_user(user_opt) + .await + .wrap_err("failed to create user")?; - let users = api.admin_users(forgejo_api::AdminUserQuery::default()).await.wrap_err("failed to search users")?; - ensure!(users.iter().find(|u| u.login == "Pipis").is_some(), "could not find new user"); - let users = api.admin_get_emails(forgejo_api::EmailListQuery::default()).await.wrap_err("failed to search emails")?; - ensure!(users.iter().find(|u| u.email == "user@noreply.example.org").is_some(), "could not find new user"); + let users = api + .admin_users(forgejo_api::AdminUserQuery::default()) + .await + .wrap_err("failed to search users")?; + ensure!( + users.iter().find(|u| u.login == "Pipis").is_some(), + "could not find new user" + ); + let users = api + .admin_get_emails(forgejo_api::EmailListQuery::default()) + .await + .wrap_err("failed to search emails")?; + ensure!( + users + .iter() + .find(|u| u.email == "user@noreply.example.org") + .is_some(), + "could not find new user" + ); let org_opt = forgejo_api::CreateOrgOption { description: None, @@ -265,8 +283,16 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { visibility: forgejo_api::OrgVisibility::Public, website: None, }; - let _ = api.admin_create_org("Pipis", org_opt).await.wrap_err("failed to create org")?; - ensure!(!api.admin_get_orgs(forgejo_api::AdminOrganizationQuery::default()).await?.is_empty(), "org list empty"); + let _ = api + .admin_create_org("Pipis", org_opt) + .await + .wrap_err("failed to create org")?; + ensure!( + !api.admin_get_orgs(forgejo_api::AdminOrganizationQuery::default()) + .await? + .is_empty(), + "org list empty" + ); let key_opt = forgejo_api::CreateKeyOption { key: "ssh-ed25519 00000000000000000000000000000000000000000000000000000000000000000000 user@noreply.example.org".into(), @@ -279,12 +305,24 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { let rename_opt = forgejo_api::RenameUserOption { new_username: "Bepis".into(), }; - api.admin_rename_user("Pipis", rename_opt).await.wrap_err("failed to rename user")?; - api.admin_delete_user("Bepis", true).await.wrap_err("failed to delete user")?; - ensure!(api.admin_delete_user("Ghost", true).await.is_err(), "deleting fake user should fail"); + api.admin_rename_user("Pipis", rename_opt) + .await + .wrap_err("failed to rename user")?; + api.admin_delete_user("Bepis", true) + .await + .wrap_err("failed to delete user")?; + ensure!( + api.admin_delete_user("Ghost", true).await.is_err(), + "deleting fake user should fail" + ); - let crons = api.admin_get_crons(forgejo_api::CronQuery::default()).await.wrap_err("failed to get crons list")?; - api.admin_run_cron(&crons.get(0).ok_or_else(|| eyre!("no crons"))?.name).await.wrap_err("failed to run cron")?; + let crons = api + .admin_get_crons(forgejo_api::CronQuery::default()) + .await + .wrap_err("failed to get crons list")?; + api.admin_run_cron(&crons.get(0).ok_or_else(|| eyre!("no crons"))?.name) + .await + .wrap_err("failed to run cron")?; let hook_opt = forgejo_api::CreateHookOption { active: None, @@ -299,14 +337,20 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { _type: forgejo_api::HookType::Forgejo, }; // yarr har har me matey this is me hook - let hook = api.admin_create_hook(hook_opt).await.wrap_err("failed to create hook")?; + let hook = api + .admin_create_hook(hook_opt) + .await + .wrap_err("failed to create hook")?; let edit_hook = forgejo_api::EditHookOption { active: Some(true), ..Default::default() }; - api.admin_edit_hook(hook.id, edit_hook).await.wrap_err("failed to edit hook")?; - api.admin_delete_hook(hook.id).await.wrap_err("failed to delete hook")?; + api.admin_edit_hook(hook.id, edit_hook) + .await + .wrap_err("failed to edit hook")?; + api.admin_delete_hook(hook.id) + .await + .wrap_err("failed to delete hook")?; Ok(()) } - From a9f816cfbce5e2c798aa4ef4cf79a800d1e740a9 Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 13:12:09 -0500 Subject: [PATCH 04/13] label admin test correctly --- tests/ci_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci_test.rs b/tests/ci_test.rs index e059799..e49c72e 100644 --- a/tests/ci_test.rs +++ b/tests/ci_test.rs @@ -11,7 +11,7 @@ async fn ci() -> eyre::Result<()> { results.push(user(&api).await.wrap_err("user error")); results.push(repo(&api).await.wrap_err("repo error")); - results.push(admin(&api).await.wrap_err("repo error")); + results.push(admin(&api).await.wrap_err("admin error")); let mut errors = 0; for report in results.into_iter().filter_map(Result::err) { From 078aa2ef5c2bb4237a20d4b7c88b0d237cbbe5e8 Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 13:17:00 -0500 Subject: [PATCH 05/13] wrap key errors --- tests/ci_test.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ci_test.rs b/tests/ci_test.rs index e49c72e..b65ba03 100644 --- a/tests/ci_test.rs +++ b/tests/ci_test.rs @@ -299,8 +299,8 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { read_only: None, title: "Example Key".into(), }; - let key = api.admin_add_key("Pipis", key_opt).await?; - api.admin_delete_key("Pipis", key.id).await?; + let key = api.admin_add_key("Pipis", key_opt).await.wrap_err("failed to create key")?; + api.admin_delete_key("Pipis", key.id).await.wrap_err("failed to delete key")?; let rename_opt = forgejo_api::RenameUserOption { new_username: "Bepis".into(), From 75d19e77db5c51d2f30f0c88b7ac920965580f5a Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 13:40:08 -0500 Subject: [PATCH 06/13] `admin_add_key` should use post --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index f824d68..243a183 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -104,7 +104,7 @@ impl Forgejo { user: &str, opt: CreateKeyOption, ) -> Result { - self.patch(&format!("admin/users/{user}/keys"), &opt).await + self.post(&format!("admin/users/{user}/keys"), &opt).await } pub async fn admin_delete_key(&self, user: &str, id: u64) -> Result<(), ForgejoError> { From 0912ffdcee4246abe9667fc5faf12a3dedb1fab5 Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 13:45:38 -0500 Subject: [PATCH 07/13] use real dummy key --- tests/ci_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci_test.rs b/tests/ci_test.rs index b65ba03..d57f5a0 100644 --- a/tests/ci_test.rs +++ b/tests/ci_test.rs @@ -295,7 +295,7 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { ); let key_opt = forgejo_api::CreateKeyOption { - key: "ssh-ed25519 00000000000000000000000000000000000000000000000000000000000000000000 user@noreply.example.org".into(), + key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN68ehQAsbGEwlXPa2AxbAh1QxFQrtRel2jeC0hRlPc1 user@noreply.example.org".into(), read_only: None, title: "Example Key".into(), }; From 1bb146d8f60cef51ad33aa33c6950bb15a455cec Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 13:58:23 -0500 Subject: [PATCH 08/13] `PublicKey.read_only` is optional --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index 243a183..f6c5977 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -470,7 +470,7 @@ pub struct PublicKey { pub id: u64, pub key: String, pub key_type: String, - pub read_only: bool, + pub read_only: Option, pub title: String, pub url: Option, pub user: User, From d3f75f67eed0fb6724edcc3ea67dce24138afb97 Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 14:02:44 -0500 Subject: [PATCH 09/13] renaming user should call `post_unit` --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index f6c5977..d52cf11 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -124,7 +124,7 @@ impl Forgejo { user: &str, opt: RenameUserOption, ) -> Result<(), ForgejoError> { - self.post(&format!("admin/users/{user}/rename"), &opt).await + self.post_unit(&format!("admin/users/{user}/rename"), &opt).await } pub async fn admin_create_repo( From 6ce8aa731af30ee7f872d2bd392f7e72c8c618fa Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 14:05:23 -0500 Subject: [PATCH 10/13] running cron should call `post_unit` --- src/admin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin.rs b/src/admin.rs index d52cf11..389be75 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -9,7 +9,7 @@ impl Forgejo { } pub async fn admin_run_cron(&self, name: &str) -> Result<(), ForgejoError> { - self.post(&format!("admin/cron/{name}"), &()).await + self.post_unit(&format!("admin/cron/{name}"), &()).await } pub async fn admin_get_emails( From 2978d34a872bef4db76e7138d3bedda8f4d8759c Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 14:15:33 -0500 Subject: [PATCH 11/13] hook content type json --- tests/ci_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci_test.rs b/tests/ci_test.rs index d57f5a0..8fee5d6 100644 --- a/tests/ci_test.rs +++ b/tests/ci_test.rs @@ -329,7 +329,7 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { authorization_header: None, branch_filter: None, config: forgejo_api::CreateHookOptionConfig { - content_type: "text/plain".into(), + content_type: "application/json".into(), url: url::Url::parse("http://test.local/").unwrap(), other: Default::default(), }, From 76b8093decbcb0a30c3e6641d36f42a1a8a4b976 Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 16:37:33 -0500 Subject: [PATCH 12/13] apparently content type != mime type --- tests/ci_test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ci_test.rs b/tests/ci_test.rs index 8fee5d6..79447c7 100644 --- a/tests/ci_test.rs +++ b/tests/ci_test.rs @@ -329,7 +329,7 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { authorization_header: None, branch_filter: None, config: forgejo_api::CreateHookOptionConfig { - content_type: "application/json".into(), + content_type: "json".into(), url: url::Url::parse("http://test.local/").unwrap(), other: Default::default(), }, From c6b075727228959564ab00e989a1da3b9550690d Mon Sep 17 00:00:00 2001 From: Cyborus Date: Tue, 12 Dec 2023 16:43:51 -0500 Subject: [PATCH 13/13] format --- src/admin.rs | 3 ++- tests/ci_test.rs | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 389be75..560d6c6 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -124,7 +124,8 @@ impl Forgejo { user: &str, opt: RenameUserOption, ) -> Result<(), ForgejoError> { - self.post_unit(&format!("admin/users/{user}/rename"), &opt).await + self.post_unit(&format!("admin/users/{user}/rename"), &opt) + .await } pub async fn admin_create_repo( diff --git a/tests/ci_test.rs b/tests/ci_test.rs index 79447c7..39acbd4 100644 --- a/tests/ci_test.rs +++ b/tests/ci_test.rs @@ -299,8 +299,13 @@ async fn admin(api: &forgejo_api::Forgejo) -> eyre::Result<()> { read_only: None, title: "Example Key".into(), }; - let key = api.admin_add_key("Pipis", key_opt).await.wrap_err("failed to create key")?; - api.admin_delete_key("Pipis", key.id).await.wrap_err("failed to delete key")?; + let key = api + .admin_add_key("Pipis", key_opt) + .await + .wrap_err("failed to create key")?; + api.admin_delete_key("Pipis", key.id) + .await + .wrap_err("failed to delete key")?; let rename_opt = forgejo_api::RenameUserOption { new_username: "Bepis".into(),