diff --git a/.sqlx/query-073b7016109d65c3d7907ce2b32b47018ce25577d6d9267da38edb53828ada41.json b/.sqlx/query-073b7016109d65c3d7907ce2b32b47018ce25577d6d9267da38edb53828ada41.json deleted file mode 100644 index 2dcdec9a3..000000000 --- a/.sqlx/query-073b7016109d65c3d7907ce2b32b47018ce25577d6d9267da38edb53828ada41.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT login, avatar\n FROM owners", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "login", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "avatar", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false - ] - }, - "hash": "073b7016109d65c3d7907ce2b32b47018ce25577d6d9267da38edb53828ada41" -} diff --git a/.sqlx/query-16dfb0d87266568fb8a84585614387c8bfa4790b9e8d317159b37397e42b7ceb.json b/.sqlx/query-16dfb0d87266568fb8a84585614387c8bfa4790b9e8d317159b37397e42b7ceb.json new file mode 100644 index 000000000..a3ae9ba9b --- /dev/null +++ b/.sqlx/query-16dfb0d87266568fb8a84585614387c8bfa4790b9e8d317159b37397e42b7ceb.json @@ -0,0 +1,42 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT login, avatar, kind as \"kind: OwnerKind\"\n FROM owners", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "avatar", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "kind: OwnerKind", + "type_info": { + "Custom": { + "name": "owner_kind", + "kind": { + "Enum": [ + "user", + "team" + ] + } + } + } + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "16dfb0d87266568fb8a84585614387c8bfa4790b9e8d317159b37397e42b7ceb" +} diff --git a/.sqlx/query-1e48486ba272a4e2ea8793114e412c14ed13f652419b1467c21e1829f31cd573.json b/.sqlx/query-1e48486ba272a4e2ea8793114e412c14ed13f652419b1467c21e1829f31cd573.json new file mode 100644 index 000000000..90d8b6731 --- /dev/null +++ b/.sqlx/query-1e48486ba272a4e2ea8793114e412c14ed13f652419b1467c21e1829f31cd573.json @@ -0,0 +1,42 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT login, avatar, kind as \"kind: OwnerKind\" FROM owners", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "avatar", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "kind: OwnerKind", + "type_info": { + "Custom": { + "name": "owner_kind", + "kind": { + "Enum": [ + "user", + "team" + ] + } + } + } + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "1e48486ba272a4e2ea8793114e412c14ed13f652419b1467c21e1829f31cd573" +} diff --git a/.sqlx/query-5deb5bb52b993cc54f7b48714c77903829961a7b50ae4bfbdb9b34c38f374932.json b/.sqlx/query-5deb5bb52b993cc54f7b48714c77903829961a7b50ae4bfbdb9b34c38f374932.json deleted file mode 100644 index 6200d4eb0..000000000 --- a/.sqlx/query-5deb5bb52b993cc54f7b48714c77903829961a7b50ae4bfbdb9b34c38f374932.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO owners (login, avatar)\n VALUES ($1, $2)\n ON CONFLICT (login) DO UPDATE\n SET\n avatar = EXCLUDED.avatar\n RETURNING id", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Varchar", - "Varchar" - ] - }, - "nullable": [ - false - ] - }, - "hash": "5deb5bb52b993cc54f7b48714c77903829961a7b50ae4bfbdb9b34c38f374932" -} diff --git a/.sqlx/query-87952bd450ed2c13b99bd502a73a84edd7d17e6171523ebbd57f1d9dd7c9b46c.json b/.sqlx/query-87952bd450ed2c13b99bd502a73a84edd7d17e6171523ebbd57f1d9dd7c9b46c.json new file mode 100644 index 000000000..9b7cbc9f7 --- /dev/null +++ b/.sqlx/query-87952bd450ed2c13b99bd502a73a84edd7d17e6171523ebbd57f1d9dd7c9b46c.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO owners (login, avatar, kind)\n VALUES ($1, $2, $3)\n ON CONFLICT (login) DO UPDATE\n SET\n avatar = EXCLUDED.avatar,\n kind = EXCLUDED.kind\n RETURNING id", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + { + "Custom": { + "name": "owner_kind", + "kind": { + "Enum": [ + "user", + "team" + ] + } + } + } + ] + }, + "nullable": [ + false + ] + }, + "hash": "87952bd450ed2c13b99bd502a73a84edd7d17e6171523ebbd57f1d9dd7c9b46c" +} diff --git a/.sqlx/query-8e1cb8355b3586b849494ae1cbde9b034ca01090469a84df2d9af4446f9d9451.json b/.sqlx/query-8e1cb8355b3586b849494ae1cbde9b034ca01090469a84df2d9af4446f9d9451.json deleted file mode 100644 index 84aff1df2..000000000 --- a/.sqlx/query-8e1cb8355b3586b849494ae1cbde9b034ca01090469a84df2d9af4446f9d9451.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT login, avatar FROM owners", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "login", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "avatar", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - false, - false - ] - }, - "hash": "8e1cb8355b3586b849494ae1cbde9b034ca01090469a84df2d9af4446f9d9451" -} diff --git a/.sqlx/query-d87220d3f4503e99fa17815db0058ab7883bf28f216d5b5fd720c56fd8889eed.json b/.sqlx/query-d87220d3f4503e99fa17815db0058ab7883bf28f216d5b5fd720c56fd8889eed.json new file mode 100644 index 000000000..7feaf3ddd --- /dev/null +++ b/.sqlx/query-d87220d3f4503e99fa17815db0058ab7883bf28f216d5b5fd720c56fd8889eed.json @@ -0,0 +1,44 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT login, avatar, kind as \"kind: OwnerKind\"\n FROM owners\n INNER JOIN owner_rels ON owner_rels.oid = owners.id\n WHERE cid = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "avatar", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "kind: OwnerKind", + "type_info": { + "Custom": { + "name": "owner_kind", + "kind": { + "Enum": [ + "user", + "team" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Int4" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "d87220d3f4503e99fa17815db0058ab7883bf28f216d5b5fd720c56fd8889eed" +} diff --git a/.sqlx/query-d9fdce61d807d32b2c700c29e0b8100b5abf2d283016f48f468d823bd85da551.json b/.sqlx/query-d9fdce61d807d32b2c700c29e0b8100b5abf2d283016f48f468d823bd85da551.json deleted file mode 100644 index 709cac8c7..000000000 --- a/.sqlx/query-d9fdce61d807d32b2c700c29e0b8100b5abf2d283016f48f468d823bd85da551.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT login, avatar\n FROM owners\n INNER JOIN owner_rels ON owner_rels.oid = owners.id\n WHERE cid = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "login", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "avatar", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Int4" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "d9fdce61d807d32b2c700c29e0b8100b5abf2d283016f48f468d823bd85da551" -} diff --git a/migrations/20240227040753_add_owner_kind.down.sql b/migrations/20240227040753_add_owner_kind.down.sql new file mode 100644 index 000000000..859b40c10 --- /dev/null +++ b/migrations/20240227040753_add_owner_kind.down.sql @@ -0,0 +1,4 @@ +ALTER TABLE owners +DROP COLUMN IF EXISTS kind; + +DROP TYPE IF EXISTS owner_kind; diff --git a/migrations/20240227040753_add_owner_kind.up.sql b/migrations/20240227040753_add_owner_kind.up.sql new file mode 100644 index 000000000..64bbaef7e --- /dev/null +++ b/migrations/20240227040753_add_owner_kind.up.sql @@ -0,0 +1,14 @@ +CREATE TYPE owner_kind AS ENUM ( + 'user', + 'team' +); + +ALTER TABLE owners +ADD COLUMN IF NOT EXISTS kind owner_kind NOT NULL DEFAULT 'user'; + +UPDATE owners +SET + kind = CASE + WHEN login LIKE 'github:%' THEN 'team'::owner_kind + ELSE 'user'::owner_kind + END; diff --git a/src/db/add_package.rs b/src/db/add_package.rs index 5cc5a87f3..3bbcff263 100644 --- a/src/db/add_package.rs +++ b/src/db/add_package.rs @@ -454,14 +454,16 @@ async fn update_owners_in_database( for owner in owners { oids.push( sqlx::query_scalar!( - "INSERT INTO owners (login, avatar) - VALUES ($1, $2) + "INSERT INTO owners (login, avatar, kind) + VALUES ($1, $2, $3) ON CONFLICT (login) DO UPDATE SET - avatar = EXCLUDED.avatar + avatar = EXCLUDED.avatar, + kind = EXCLUDED.kind RETURNING id", owner.login, - owner.avatar + owner.avatar, + owner.kind as _, ) .fetch_one(&mut *conn) .await?, @@ -520,6 +522,7 @@ where #[cfg(test)] mod test { use super::*; + use crate::registry_api::OwnerKind; use crate::test::*; use crate::utils::CargoMetadata; use test_case::test_case; @@ -647,18 +650,20 @@ mod test { let owner1 = CrateOwner { avatar: "avatar".into(), login: "login".into(), + kind: OwnerKind::User, }; update_owners_in_database(&mut conn, &[owner1.clone()], crate_id).await?; let owner_def = sqlx::query!( - "SELECT login, avatar - FROM owners" + r#"SELECT login, avatar, kind as "kind: OwnerKind" + FROM owners"# ) .fetch_one(&mut *conn) .await?; assert_eq!(owner_def.login, owner1.login); assert_eq!(owner_def.avatar, owner1.avatar); + assert_eq!(owner_def.kind, owner1.kind); let owner_rel = sqlx::query!( "SELECT o.login @@ -688,6 +693,7 @@ mod test { &[CrateOwner { login: "login".into(), avatar: "avatar".into(), + kind: OwnerKind::User, }], crate_id, ) @@ -696,14 +702,17 @@ mod test { let updated_owner = CrateOwner { login: "login".into(), avatar: "avatar2".into(), + kind: OwnerKind::Team, }; update_owners_in_database(&mut conn, &[updated_owner.clone()], crate_id).await?; - let owner_def = sqlx::query!("SELECT login, avatar FROM owners") - .fetch_one(&mut *conn) - .await?; + let owner_def = + sqlx::query!(r#"SELECT login, avatar, kind as "kind: OwnerKind" FROM owners"#) + .fetch_one(&mut *conn) + .await?; assert_eq!(owner_def.login, updated_owner.login); assert_eq!(owner_def.avatar, updated_owner.avatar); + assert_eq!(owner_def.kind, updated_owner.kind); let owner_rel = sqlx::query!( "SELECT o.login @@ -733,6 +742,7 @@ mod test { &[CrateOwner { login: "login".into(), avatar: "avatar".into(), + kind: OwnerKind::User, }], crate_id, ) @@ -742,6 +752,7 @@ mod test { .map(|i| CrateOwner { login: format!("login{i}"), avatar: format!("avatar{i}"), + kind: OwnerKind::User, }) .collect(); diff --git a/src/db/delete.rs b/src/db/delete.rs index 7f9c778f6..8f96c4bd4 100644 --- a/src/db/delete.rs +++ b/src/db/delete.rs @@ -196,7 +196,7 @@ fn delete_crate_from_database(conn: &mut Client, name: &str, crate_id: i32) -> R #[cfg(test)] mod tests { use super::*; - use crate::registry_api::CrateOwner; + use crate::registry_api::{CrateOwner, OwnerKind}; use crate::test::{assert_success, wrapper}; use test_case::test_case; @@ -328,6 +328,7 @@ mod tests { .add_owner(CrateOwner { login: "malicious actor".into(), avatar: "https://example.org/malicious".into(), + kind: OwnerKind::User, }) .create()?; assert!(release_exists(&mut db.conn(), v1)?); @@ -358,6 +359,7 @@ mod tests { .add_owner(CrateOwner { login: "Peter Rabbit".into(), avatar: "https://example.org/peter".into(), + kind: OwnerKind::User, }) .create()?; assert!(release_exists(&mut db.conn(), v2)?); diff --git a/src/registry_api.rs b/src/registry_api.rs index fb0e1ca5d..47d403179 100644 --- a/src/registry_api.rs +++ b/src/registry_api.rs @@ -3,7 +3,7 @@ use anyhow::{anyhow, Context}; use chrono::{DateTime, Utc}; use reqwest::header::{HeaderValue, ACCEPT, USER_AGENT}; use semver::Version; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use tracing::instrument; use url::Url; @@ -46,6 +46,17 @@ impl Default for ReleaseData { pub struct CrateOwner { pub(crate) avatar: String, pub(crate) login: String, + pub(crate) kind: OwnerKind, +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, sqlx::Type, +)] +#[sqlx(type_name = "owner_kind", rename_all = "lowercase")] +#[serde(rename_all = "lowercase")] +pub enum OwnerKind { + User, + Team, } impl RegistryApi { @@ -168,6 +179,8 @@ impl RegistryApi { avatar: Option, #[serde(default)] login: Option, + #[serde(default)] + kind: Option, } let response: Response = retry_async( @@ -198,6 +211,7 @@ impl RegistryApi { .map(|data| CrateOwner { avatar: data.avatar.unwrap_or_default(), login: data.login.unwrap_or_default(), + kind: data.kind.unwrap_or(OwnerKind::User), }) .collect(); diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index be079ff41..fb562c60d 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -1,4 +1,5 @@ use super::{markdown, match_version, MetaData}; +use crate::registry_api::OwnerKind; use crate::utils::{get_correct_docsrs_style_file, report_error}; use crate::web::rustdoc::RustdocHtmlParams; use crate::{ @@ -35,7 +36,7 @@ pub(crate) struct CrateDetails { name: String, pub version: Version, description: Option, - owners: Vec<(String, String)>, + owners: Vec<(String, String, OwnerKind)>, dependencies: Option, #[serde(serialize_with = "optional_markdown")] readme: Option, @@ -264,14 +265,14 @@ impl CrateDetails { // get owners crate_details.owners = sqlx::query!( - "SELECT login, avatar + r#"SELECT login, avatar, kind as "kind: OwnerKind" FROM owners INNER JOIN owner_rels ON owner_rels.oid = owners.id - WHERE cid = $1", + WHERE cid = $1"#, krate.crate_id, ) .fetch(&mut *conn) - .map_ok(|row| (row.login, row.avatar)) + .map_ok(|row| (row.login, row.avatar, row.kind)) .try_collect() .await?; @@ -1253,6 +1254,7 @@ mod tests { .add_owner(CrateOwner { login: "foobar".into(), avatar: "https://example.org/foobar".into(), + kind: OwnerKind::User, }) .create()?; @@ -1262,7 +1264,11 @@ mod tests { }); assert_eq!( details.owners, - vec![("foobar".into(), "https://example.org/foobar".into())] + vec![( + "foobar".into(), + "https://example.org/foobar".into(), + OwnerKind::User + )] ); // Adding a new owner, and changing details on an existing owner @@ -1272,10 +1278,12 @@ mod tests { .add_owner(CrateOwner { login: "foobar".into(), avatar: "https://example.org/foobarv2".into(), + kind: OwnerKind::User, }) .add_owner(CrateOwner { login: "barfoo".into(), avatar: "https://example.org/barfoo".into(), + kind: OwnerKind::User, }) .create()?; @@ -1288,8 +1296,16 @@ mod tests { assert_eq!( owners, vec![ - ("barfoo".into(), "https://example.org/barfoo".into()), - ("foobar".into(), "https://example.org/foobarv2".into()) + ( + "barfoo".into(), + "https://example.org/barfoo".into(), + OwnerKind::User + ), + ( + "foobar".into(), + "https://example.org/foobarv2".into(), + OwnerKind::User + ) ] ); @@ -1300,6 +1316,7 @@ mod tests { .add_owner(CrateOwner { login: "barfoo".into(), avatar: "https://example.org/barfoo".into(), + kind: OwnerKind::User, }) .create()?; @@ -1309,7 +1326,11 @@ mod tests { }); assert_eq!( details.owners, - vec![("barfoo".into(), "https://example.org/barfoo".into())] + vec![( + "barfoo".into(), + "https://example.org/barfoo".into(), + OwnerKind::User + )] ); // Changing owner details on another of their crates applies the change to both @@ -1319,6 +1340,7 @@ mod tests { .add_owner(CrateOwner { login: "barfoo".into(), avatar: "https://example.org/barfoov2".into(), + kind: OwnerKind::User, }) .create()?; @@ -1328,7 +1350,11 @@ mod tests { }); assert_eq!( details.owners, - vec![("barfoo".into(), "https://example.org/barfoov2".into())] + vec![( + "barfoo".into(), + "https://example.org/barfoov2".into(), + OwnerKind::User + )] ); Ok(()) diff --git a/src/web/releases.rs b/src/web/releases.rs index e7486813a..f6fe8ea00 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -778,7 +778,7 @@ pub(crate) async fn build_queue_handler( #[cfg(test)] mod tests { use super::*; - use crate::registry_api::CrateOwner; + use crate::registry_api::{CrateOwner, OwnerKind}; use crate::test::{ assert_cache_control, assert_redirect, assert_redirect_unchecked, assert_success, wrapper, TestFrontend, @@ -1642,6 +1642,7 @@ mod tests { .add_owner(CrateOwner { login: "foobar".into(), avatar: "https://example.org/foobar".into(), + kind: OwnerKind::User, }) .create()?; diff --git a/templates/rustdoc/topbar.html b/templates/rustdoc/topbar.html index a5b6e1bfd..26dc7138c 100644 --- a/templates/rustdoc/topbar.html +++ b/templates/rustdoc/topbar.html @@ -112,7 +112,7 @@ {%- for owner in krate.owners -%}
  • - + {{ "user" | fas }} {{ owner[0] }}