From f016821a11f0e3805884da4b21a0c98028a34768 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Sun, 25 Jan 2026 15:37:47 +0100 Subject: [PATCH 1/3] feat: private packages --- ...1897f68b0462ad93a29ce20bb652bbbb83420.json | 99 ---------------- ...ffa51b28dfa6f769f8770a01eafe0201ab46.json} | 28 +++-- ...3e39551d1c976d53eba93a4b58eeb97960ee.json} | 16 ++- ...b8dfe51e9edc309c346baaea94470cace57af.json | 76 ------------- ...d6c41dc7080e695249ff4a5a415df05139ece.json | 94 --------------- ...199464a66b69f4b7c31a90957c9aa4f9d58b.json} | 28 +++-- ...573898e37b485055291aac01ff65591ca5c0.json} | 16 ++- ...cb90a993091dcd8feb45f26dfaa86a441e6c.json} | 18 ++- ...b185da1afcd31f3e2762329dd2cbf0fd3c610.json | 107 ------------------ ...077b8dd5628fadebd5ab936a55ef038786ca.json} | 18 ++- ...8b93a12da06bb610173c1e39f9997f97e9610.json | 48 -------- ...e9761bc495a34441ddf2a128fec6364be6a62.json | 48 -------- ...2a35c41672736f94acea3afe9ad369b9f53b5.json | 69 ----------- ...d59d2a1acd00d8086ef42599cdd12e3196a1.json} | 28 +++-- ...709cfe002530b6e0cfca2cdf1724da907d051.json | 106 +++++++++++++++++ ...afabc2574dae3f240befb41e4fb4e74280528.json | 14 --- ...f19538e11c45b33414c29609729ed417bdb6.json} | 16 ++- ...e7cf6d7581ad92acced7fcbc7aad0ca28d51.json} | 16 ++- ...df9094a39a8ab3b5a751f437668c47c3a56f.json} | 16 ++- ...8ba365f1efc2f2ecc9a98e272c25def1def1.json} | 28 +++-- ...10b1c00bf0bb5093be0707abb71431716b58.json} | 16 ++- .../20260125100000_private_packages.sql | 2 + api/src/api/package.rs | 48 ++++++++ api/src/api/self_user.rs | 15 ++- api/src/api/types.rs | 3 + api/src/buckets.rs | 2 + api/src/config.rs | 16 +++ api/src/db/database.rs | 82 +++++++++++--- api/src/db/models.rs | 16 +++ api/src/iam.rs | 67 +++++++++++ api/src/main.rs | 12 ++ api/src/orama.rs | 2 +- api/src/publish.rs | 70 ++++++++++-- api/src/tarball.rs | 27 ++++- api/src/util.rs | 4 + .../package/(_components)/PackageHeader.tsx | 6 + frontend/routes/package/settings.tsx | 72 ++++++++++++ frontend/utils/api_types.ts | 1 + lb/local.ts | 4 + lb/main.ts | 80 +++++++++++-- lb/types.ts | 9 ++ terraform/buckets.tf | 12 ++ terraform/lb.tf | 8 ++ 43 files changed, 779 insertions(+), 684 deletions(-) delete mode 100644 api/.sqlx/query-050b47e8c4f5300bdb04ae9f42a1897f68b0462ad93a29ce20bb652bbbb83420.json rename api/.sqlx/{query-ef0155c2926ea69baf0738c6317064adee44c3627443478d3a94a45b1748db25.json => query-09064d0df28c61389f2ccff0d4f1ffa51b28dfa6f769f8770a01eafe0201ab46.json} (67%) rename api/.sqlx/{query-42bc09915e14ee2d075d95dff11eb31d3b1392ea9ad74e4be0865cb8fce402be.json => query-0a80e3888c3dce8a884e9d6cdf113e39551d1c976d53eba93a4b58eeb97960ee.json} (81%) delete mode 100644 api/.sqlx/query-0ddb2cf66ee7e7d2c7341713b39b8dfe51e9edc309c346baaea94470cace57af.json delete mode 100644 api/.sqlx/query-0e8e4c876ea49c287f77dc33a1dd6c41dc7080e695249ff4a5a415df05139ece.json rename api/.sqlx/{query-ce694bc69d1154fa4bc5cc33f2b067f1c06831da03a15512f446c8f146bf120b.json => query-1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b.json} (67%) rename api/.sqlx/{query-b43a8cac5abc471e9e2781341c713f8b4c8c7c1b32d28b911b7449ab69ba43fc.json => query-22c8d5698ea739ca61c701a75407573898e37b485055291aac01ff65591ca5c0.json} (82%) rename api/.sqlx/{query-785dbd6f774c40c14f8a8b748e7a5287e7f555ee1d1feae9dabab8642daffa9b.json => query-2b46ea4eb5a53a21fbbbbc0da441cb90a993091dcd8feb45f26dfaa86a441e6c.json} (75%) delete mode 100644 api/.sqlx/query-30441825bf3627e9fd7b99368f0b185da1afcd31f3e2762329dd2cbf0fd3c610.json rename api/.sqlx/{query-7565c8ea2ffa802ba5b6d17816887f4f65868ed519aa8ad006dd7cf2124a63bd.json => query-3302ed990a043926109579e49b19077b8dd5628fadebd5ab936a55ef038786ca.json} (73%) delete mode 100644 api/.sqlx/query-35632bc48d97695339900965cf98b93a12da06bb610173c1e39f9997f97e9610.json delete mode 100644 api/.sqlx/query-3f118b22cfc900a7f6e3e483f97e9761bc495a34441ddf2a128fec6364be6a62.json delete mode 100644 api/.sqlx/query-51fd48d86ae305c6fc527fd5b062a35c41672736f94acea3afe9ad369b9f53b5.json rename api/.sqlx/{query-ad4dad0f6bf1d42a787cbb3fbc0854131f1d2d293cbb4faa8f69ae7f91c8ecb9.json => query-9a6acbec173320e7732884e30fc9d59d2a1acd00d8086ef42599cdd12e3196a1.json} (65%) create mode 100644 api/.sqlx/query-a8050aebace5099be9493ab562f709cfe002530b6e0cfca2cdf1724da907d051.json delete mode 100644 api/.sqlx/query-af37a947380c91b98ef36a7f2bcafabc2574dae3f240befb41e4fb4e74280528.json rename api/.sqlx/{query-246465b7114a74de5f74426e402ff9f2d708119dd91b92100d441d338e3e7383.json => query-b3a4a3c8d04a6a26c21054b9e104f19538e11c45b33414c29609729ed417bdb6.json} (81%) rename api/.sqlx/{query-1fa16ba98cff2cac39f2226eed339e74192378e1f664ab7fa32958fce062aa01.json => query-d2957473c284f6b790cd3e21c1cfe7cf6d7581ad92acced7fcbc7aad0ca28d51.json} (80%) rename api/.sqlx/{query-9ee1b77a2fbb5b60b0688552a386dc3c4461f3389285880d028c7e3e10b68acc.json => query-ecc554906f315d9f5f152b9851c0df9094a39a8ab3b5a751f437668c47c3a56f.json} (81%) rename api/.sqlx/{query-532e3e4dd5ca26105b9cc92f3937ea2c7b486726f9e00245805933a76a0035e7.json => query-f1a90c113e428cb8dfb77979bdef8ba365f1efc2f2ecc9a98e272c25def1def1.json} (69%) rename api/.sqlx/{query-0ae764ade6ef1d0d5e29732aa68f0c9cf47f1ae339303eb5be8864b5df07e018.json => query-f53e7cd042933a6427086472f9a710b1c00bf0bb5093be0707abb71431716b58.json} (78%) create mode 100644 api/migrations/20260125100000_private_packages.sql diff --git a/api/.sqlx/query-050b47e8c4f5300bdb04ae9f42a1897f68b0462ad93a29ce20bb652bbbb83420.json b/api/.sqlx/query-050b47e8c4f5300bdb04ae9f42a1897f68b0462ad93a29ce20bb652bbbb83420.json deleted file mode 100644 index b9c4edadc..000000000 --- a/api/.sqlx/query-050b47e8c4f5300bdb04ae9f42a1897f68b0462ad93a29ce20bb652bbbb83420.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO users (name, email, avatar_url, github_id, is_blocked, is_staff)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING id, name, email, avatar_url, updated_at, created_at, github_id, is_blocked, is_staff, scope_limit,\n (SELECT COUNT(created_at) FROM scope_invites WHERE target_user_id = id) as \"invite_count!\",\n (SELECT COUNT(created_at) FROM scopes WHERE creator = id) as \"scope_usage!\",\n (CASE WHEN users.is_staff THEN (\n SELECT count(tickets.created_at) FROM tickets WHERE closed = false AND EXISTS (\n SELECT 1 FROM ticket_messages as tm WHERE tm.ticket_id = tickets.id AND tm.author = tickets.creator AND tm.created_at = (\n SELECT MAX(ticket_messages.created_at) FROM ticket_messages WHERE ticket_messages.ticket_id = tickets.id\n )\n )\n ) ELSE (\n SELECT COUNT(created_at) FROM tickets WHERE closed = false AND tickets.creator = users.id AND EXISTS (\n SELECT 1 FROM ticket_messages as tm WHERE tm.ticket_id = tickets.id AND tm.author != users.id AND tm.created_at > (\n SELECT MAX(tm2.created_at) FROM ticket_messages as tm2 WHERE tm2.ticket_id = tm.ticket_id AND tm2.author = users.id\n )\n )\n ) END) as \"newer_ticket_messages_count!\"\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "avatar_url", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 6, - "name": "github_id", - "type_info": "Int8" - }, - { - "ordinal": 7, - "name": "is_blocked", - "type_info": "Bool" - }, - { - "ordinal": 8, - "name": "is_staff", - "type_info": "Bool" - }, - { - "ordinal": 9, - "name": "scope_limit", - "type_info": "Int4" - }, - { - "ordinal": 10, - "name": "invite_count!", - "type_info": "Int8" - }, - { - "ordinal": 11, - "name": "scope_usage!", - "type_info": "Int8" - }, - { - "ordinal": 12, - "name": "newer_ticket_messages_count!", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text", - "Varchar", - "Text", - "Int8", - "Bool", - "Bool" - ] - }, - "nullable": [ - false, - false, - true, - false, - false, - false, - true, - false, - false, - false, - null, - null, - null - ] - }, - "hash": "050b47e8c4f5300bdb04ae9f42a1897f68b0462ad93a29ce20bb652bbbb83420" -} diff --git a/api/.sqlx/query-ef0155c2926ea69baf0738c6317064adee44c3627443478d3a94a45b1748db25.json b/api/.sqlx/query-09064d0df28c61389f2ccff0d4f1ffa51b28dfa6f769f8770a01eafe0201ab46.json similarity index 67% rename from api/.sqlx/query-ef0155c2926ea69baf0738c6317064adee44c3627443478d3a94a45b1748db25.json rename to api/.sqlx/query-09064d0df28c61389f2ccff0d4f1ffa51b28dfa6f769f8770a01eafe0201ab46.json index 8a3f0cab6..da7b0bab1 100644 --- a/api/.sqlx/query-ef0155c2926ea69baf0738c6317064adee44c3627443478d3a94a45b1748db25.json +++ b/api/.sqlx/query-09064d0df28c61389f2ccff0d4f1ffa51b28dfa6f769f8770a01eafe0201ab46.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat as \"package_runtime_compat: RuntimeCompat\", packages.readme_source as \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND is_yanked = false AND version IS NOT NULL ORDER BY version DESC LIMIT 1) IS NOT NULL AND NOT packages.is_archived\n ORDER BY packages.created_at DESC\n LIMIT 10", + "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat as \"package_runtime_compat: RuntimeCompat\", packages.readme_source as \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.is_private \"package_is_private\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE packages.when_featured IS NOT NULL AND NOT packages.is_archived AND NOT packages.is_private\n ORDER BY packages.when_featured DESC\n LIMIT 10", "describe": { "columns": [ { @@ -55,51 +55,56 @@ }, { "ordinal": 8, + "name": "package_is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "package_updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "package_created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "package_version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "package_latest_version", "type_info": "Text" }, { - "ordinal": 12, + "ordinal": 13, "name": "package_version_meta: PackageVersionMeta", "type_info": "Jsonb" }, { - "ordinal": 13, + "ordinal": 14, "name": "github_repository_id?", "type_info": "Int8" }, { - "ordinal": 14, + "ordinal": 15, "name": "github_repository_owner?", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 16, "name": "github_repository_name?", "type_info": "Text" }, { - "ordinal": 16, + "ordinal": 17, "name": "github_repository_updated_at?", "type_info": "Timestamptz" }, { - "ordinal": 17, + "ordinal": 18, "name": "github_repository_created_at?", "type_info": "Timestamptz" } @@ -118,6 +123,7 @@ false, false, false, + false, null, null, null, @@ -128,5 +134,5 @@ false ] }, - "hash": "ef0155c2926ea69baf0738c6317064adee44c3627443478d3a94a45b1748db25" + "hash": "09064d0df28c61389f2ccff0d4f1ffa51b28dfa6f769f8770a01eafe0201ab46" } diff --git a/api/.sqlx/query-42bc09915e14ee2d075d95dff11eb31d3b1392ea9ad74e4be0865cb8fce402be.json b/api/.sqlx/query-0a80e3888c3dce8a884e9d6cdf113e39551d1c976d53eba93a4b58eeb97960ee.json similarity index 81% rename from api/.sqlx/query-42bc09915e14ee2d075d95dff11eb31d3b1392ea9ad74e4be0865cb8fce402be.json rename to api/.sqlx/query-0a80e3888c3dce8a884e9d6cdf113e39551d1c976d53eba93a4b58eeb97960ee.json index 16788c860..6d4cd7a09 100644 --- a/api/.sqlx/query-42bc09915e14ee2d075d95dff11eb31d3b1392ea9ad74e4be0865cb8fce402be.json +++ b/api/.sqlx/query-0a80e3888c3dce8a884e9d6cdf113e39551d1c976d53eba93a4b58eeb97960ee.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE packages\n SET is_archived = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", + "query": "UPDATE packages\n SET is_archived = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", "describe": { "columns": [ { @@ -55,21 +55,26 @@ }, { "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "latest_version", "type_info": "Text" } @@ -92,9 +97,10 @@ false, false, false, + false, null, null ] }, - "hash": "42bc09915e14ee2d075d95dff11eb31d3b1392ea9ad74e4be0865cb8fce402be" + "hash": "0a80e3888c3dce8a884e9d6cdf113e39551d1c976d53eba93a4b58eeb97960ee" } diff --git a/api/.sqlx/query-0ddb2cf66ee7e7d2c7341713b39b8dfe51e9edc309c346baaea94470cace57af.json b/api/.sqlx/query-0ddb2cf66ee7e7d2c7341713b39b8dfe51e9edc309c346baaea94470cace57af.json deleted file mode 100644 index 902544274..000000000 --- a/api/.sqlx/query-0ddb2cf66ee7e7d2c7341713b39b8dfe51e9edc309c346baaea94470cace57af.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n scope as \"scope: ScopeName\",\n description as \"description: ScopeDescription\",\n creator,\n package_limit,\n new_package_per_week_limit,\n publish_attempts_per_week_limit,\n verify_oidc_actor,\n require_publishing_from_ci,\n updated_at,\n created_at\n FROM scopes WHERE creator = $1\n ORDER BY scope ASC", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "scope: ScopeName", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "description: ScopeDescription", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "creator", - "type_info": "Uuid" - }, - { - "ordinal": 3, - "name": "package_limit", - "type_info": "Int4" - }, - { - "ordinal": 4, - "name": "new_package_per_week_limit", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "publish_attempts_per_week_limit", - "type_info": "Int4" - }, - { - "ordinal": 6, - "name": "verify_oidc_actor", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "require_publishing_from_ci", - "type_info": "Bool" - }, - { - "ordinal": 8, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 9, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - false, - false, - false, - false, - false - ] - }, - "hash": "0ddb2cf66ee7e7d2c7341713b39b8dfe51e9edc309c346baaea94470cace57af" -} diff --git a/api/.sqlx/query-0e8e4c876ea49c287f77dc33a1dd6c41dc7080e695249ff4a5a415df05139ece.json b/api/.sqlx/query-0e8e4c876ea49c287f77dc33a1dd6c41dc7080e695249ff4a5a415df05139ece.json deleted file mode 100644 index de37c9f85..000000000 --- a/api/.sqlx/query-0e8e4c876ea49c287f77dc33a1dd6c41dc7080e695249ff4a5a415df05139ece.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "DELETE FROM users\n WHERE id = $1\n RETURNING id, name, email, avatar_url, updated_at, created_at, github_id, is_blocked, is_staff, scope_limit,\n (SELECT COUNT(created_at) FROM scope_invites WHERE target_user_id = id) as \"invite_count!\",\n (SELECT COUNT(created_at) FROM scopes WHERE creator = id) as \"scope_usage!\",\n (CASE WHEN users.is_staff THEN (\n SELECT count(tickets.created_at) FROM tickets WHERE closed = false AND EXISTS (\n SELECT 1 FROM ticket_messages as tm WHERE tm.ticket_id = tickets.id AND tm.author = tickets.creator AND tm.created_at = (\n SELECT MAX(ticket_messages.created_at) FROM ticket_messages WHERE ticket_messages.ticket_id = tickets.id\n )\n )\n ) ELSE (\n SELECT COUNT(created_at) FROM tickets WHERE closed = false AND tickets.creator = users.id AND EXISTS (\n SELECT 1 FROM ticket_messages as tm WHERE tm.ticket_id = tickets.id AND tm.author != users.id AND tm.created_at > (\n SELECT MAX(tm2.created_at) FROM ticket_messages as tm2 WHERE tm2.ticket_id = tm.ticket_id AND tm2.author = users.id\n )\n )\n ) END) as \"newer_ticket_messages_count!\"\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Uuid" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "avatar_url", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 5, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 6, - "name": "github_id", - "type_info": "Int8" - }, - { - "ordinal": 7, - "name": "is_blocked", - "type_info": "Bool" - }, - { - "ordinal": 8, - "name": "is_staff", - "type_info": "Bool" - }, - { - "ordinal": 9, - "name": "scope_limit", - "type_info": "Int4" - }, - { - "ordinal": 10, - "name": "invite_count!", - "type_info": "Int8" - }, - { - "ordinal": 11, - "name": "scope_usage!", - "type_info": "Int8" - }, - { - "ordinal": 12, - "name": "newer_ticket_messages_count!", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Uuid" - ] - }, - "nullable": [ - false, - false, - true, - false, - false, - false, - true, - false, - false, - false, - null, - null, - null - ] - }, - "hash": "0e8e4c876ea49c287f77dc33a1dd6c41dc7080e695249ff4a5a415df05139ece" -} diff --git a/api/.sqlx/query-ce694bc69d1154fa4bc5cc33f2b067f1c06831da03a15512f446c8f146bf120b.json b/api/.sqlx/query-1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b.json similarity index 67% rename from api/.sqlx/query-ce694bc69d1154fa4bc5cc33f2b067f1c06831da03a15512f446c8f146bf120b.json rename to api/.sqlx/query-1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b.json index d78d425ec..52cdfb1cc 100644 --- a/api/.sqlx/query-ce694bc69d1154fa4bc5cc33f2b067f1c06831da03a15512f446c8f146bf120b.json +++ b/api/.sqlx/query-1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat as \"package_runtime_compat: RuntimeCompat\", packages.readme_source as \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE packages.scope = $1 AND ($2 = true OR packages.is_archived = false)\n ORDER BY packages.is_archived ASC, packages.name\n OFFSET $3 LIMIT $4", + "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat as \"package_runtime_compat: RuntimeCompat\", packages.readme_source as \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.is_private \"package_is_private\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE packages.scope = $1 AND ($2 = true OR packages.is_archived = false)\n ORDER BY packages.is_archived ASC, packages.name\n OFFSET $3 LIMIT $4", "describe": { "columns": [ { @@ -55,51 +55,56 @@ }, { "ordinal": 8, + "name": "package_is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "package_updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "package_created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "package_version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "package_latest_version", "type_info": "Text" }, { - "ordinal": 12, + "ordinal": 13, "name": "package_version_meta: PackageVersionMeta", "type_info": "Jsonb" }, { - "ordinal": 13, + "ordinal": 14, "name": "github_repository_id?", "type_info": "Int8" }, { - "ordinal": 14, + "ordinal": 15, "name": "github_repository_owner?", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 16, "name": "github_repository_name?", "type_info": "Text" }, { - "ordinal": 16, + "ordinal": 17, "name": "github_repository_updated_at?", "type_info": "Timestamptz" }, { - "ordinal": 17, + "ordinal": 18, "name": "github_repository_created_at?", "type_info": "Timestamptz" } @@ -123,6 +128,7 @@ false, false, false, + false, null, null, null, @@ -133,5 +139,5 @@ false ] }, - "hash": "ce694bc69d1154fa4bc5cc33f2b067f1c06831da03a15512f446c8f146bf120b" + "hash": "1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b" } diff --git a/api/.sqlx/query-b43a8cac5abc471e9e2781341c713f8b4c8c7c1b32d28b911b7449ab69ba43fc.json b/api/.sqlx/query-22c8d5698ea739ca61c701a75407573898e37b485055291aac01ff65591ca5c0.json similarity index 82% rename from api/.sqlx/query-b43a8cac5abc471e9e2781341c713f8b4c8c7c1b32d28b911b7449ab69ba43fc.json rename to api/.sqlx/query-22c8d5698ea739ca61c701a75407573898e37b485055291aac01ff65591ca5c0.json index 9400e2fb9..926bd3e98 100644 --- a/api/.sqlx/query-b43a8cac5abc471e9e2781341c713f8b4c8c7c1b32d28b911b7449ab69ba43fc.json +++ b/api/.sqlx/query-22c8d5698ea739ca61c701a75407573898e37b485055291aac01ff65591ca5c0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE packages\n SET readme_source = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", + "query": "UPDATE packages\n SET readme_source = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", "describe": { "columns": [ { @@ -55,21 +55,26 @@ }, { "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "latest_version", "type_info": "Text" } @@ -102,9 +107,10 @@ false, false, false, + false, null, null ] }, - "hash": "b43a8cac5abc471e9e2781341c713f8b4c8c7c1b32d28b911b7449ab69ba43fc" + "hash": "22c8d5698ea739ca61c701a75407573898e37b485055291aac01ff65591ca5c0" } diff --git a/api/.sqlx/query-785dbd6f774c40c14f8a8b748e7a5287e7f555ee1d1feae9dabab8642daffa9b.json b/api/.sqlx/query-2b46ea4eb5a53a21fbbbbc0da441cb90a993091dcd8feb45f26dfaa86a441e6c.json similarity index 75% rename from api/.sqlx/query-785dbd6f774c40c14f8a8b748e7a5287e7f555ee1d1feae9dabab8642daffa9b.json rename to api/.sqlx/query-2b46ea4eb5a53a21fbbbbc0da441cb90a993091dcd8feb45f26dfaa86a441e6c.json index 022ccb4c4..e902bb421 100644 --- a/api/.sqlx/query-785dbd6f774c40c14f8a8b748e7a5287e7f555ee1d1feae9dabab8642daffa9b.json +++ b/api/.sqlx/query-2b46ea4eb5a53a21fbbbbc0da441cb90a993091dcd8feb45f26dfaa86a441e6c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE packages\n SET description = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\"", + "query": "UPDATE packages\n SET description = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\"", "describe": { "columns": [ { @@ -55,26 +55,31 @@ }, { "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "latest_version", "type_info": "Text" }, { - "ordinal": 12, + "ordinal": 13, "name": "package_version_meta: PackageVersionMeta", "type_info": "Jsonb" } @@ -97,10 +102,11 @@ false, false, false, + false, null, null, null ] }, - "hash": "785dbd6f774c40c14f8a8b748e7a5287e7f555ee1d1feae9dabab8642daffa9b" + "hash": "2b46ea4eb5a53a21fbbbbc0da441cb90a993091dcd8feb45f26dfaa86a441e6c" } diff --git a/api/.sqlx/query-30441825bf3627e9fd7b99368f0b185da1afcd31f3e2762329dd2cbf0fd3c610.json b/api/.sqlx/query-30441825bf3627e9fd7b99368f0b185da1afcd31f3e2762329dd2cbf0fd3c610.json deleted file mode 100644 index b050db161..000000000 --- a/api/.sqlx/query-30441825bf3627e9fd7b99368f0b185da1afcd31f3e2762329dd2cbf0fd3c610.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO package_versions (scope, name, version, user_id, readme_path, exports, uses_npm, meta)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", version as \"version: Version\", user_id, readme_path as \"readme_path: PackagePath\", exports as \"exports: ExportsMap\", is_yanked, uses_npm, meta as \"meta: PackageVersionMeta\", updated_at, created_at, rekor_log_id,\n (SELECT COUNT(*)\n FROM package_versions AS pv\n WHERE pv.scope = package_versions.scope\n AND pv.name = package_versions.name\n AND pv.version > package_versions.version\n AND pv.version NOT LIKE '%-%'\n AND pv.is_yanked = false) as \"newer_versions_count!\",\n (SELECT COALESCE(SUM(dl.count), 0)\n FROM version_download_counts_24h as dl\n WHERE dl.scope = package_versions.scope\n AND dl.package = package_versions.name\n AND dl.version = package_versions.version) as \"lifetime_download_count!\"", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "scope: ScopeName", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "name: PackageName", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "version: Version", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "user_id", - "type_info": "Uuid" - }, - { - "ordinal": 4, - "name": "readme_path: PackagePath", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "exports: ExportsMap", - "type_info": "Jsonb" - }, - { - "ordinal": 6, - "name": "is_yanked", - "type_info": "Bool" - }, - { - "ordinal": 7, - "name": "uses_npm", - "type_info": "Bool" - }, - { - "ordinal": 8, - "name": "meta: PackageVersionMeta", - "type_info": "Jsonb" - }, - { - "ordinal": 9, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "created_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "rekor_log_id", - "type_info": "Text" - }, - { - "ordinal": 12, - "name": "newer_versions_count!", - "type_info": "Int8" - }, - { - "ordinal": 13, - "name": "lifetime_download_count!", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text", - "Uuid", - "Text", - "Jsonb", - "Bool", - "Jsonb" - ] - }, - "nullable": [ - false, - false, - false, - true, - true, - false, - false, - false, - false, - false, - false, - true, - null, - null - ] - }, - "hash": "30441825bf3627e9fd7b99368f0b185da1afcd31f3e2762329dd2cbf0fd3c610" -} diff --git a/api/.sqlx/query-7565c8ea2ffa802ba5b6d17816887f4f65868ed519aa8ad006dd7cf2124a63bd.json b/api/.sqlx/query-3302ed990a043926109579e49b19077b8dd5628fadebd5ab936a55ef038786ca.json similarity index 73% rename from api/.sqlx/query-7565c8ea2ffa802ba5b6d17816887f4f65868ed519aa8ad006dd7cf2124a63bd.json rename to api/.sqlx/query-3302ed990a043926109579e49b19077b8dd5628fadebd5ab936a55ef038786ca.json index 4c2e72d41..f60a77630 100644 --- a/api/.sqlx/query-7565c8ea2ffa802ba5b6d17816887f4f65868ed519aa8ad006dd7cf2124a63bd.json +++ b/api/.sqlx/query-3302ed990a043926109579e49b19077b8dd5628fadebd5ab936a55ef038786ca.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE packages\n SET github_repository_id = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\"", + "query": "UPDATE packages\n SET github_repository_id = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\"", "describe": { "columns": [ { @@ -55,26 +55,31 @@ }, { "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "latest_version", "type_info": "Text" }, { - "ordinal": 12, + "ordinal": 13, "name": "package_version_meta: PackageVersionMeta", "type_info": "Jsonb" } @@ -97,10 +102,11 @@ false, false, false, + false, null, null, null ] }, - "hash": "7565c8ea2ffa802ba5b6d17816887f4f65868ed519aa8ad006dd7cf2124a63bd" + "hash": "3302ed990a043926109579e49b19077b8dd5628fadebd5ab936a55ef038786ca" } diff --git a/api/.sqlx/query-35632bc48d97695339900965cf98b93a12da06bb610173c1e39f9997f97e9610.json b/api/.sqlx/query-35632bc48d97695339900965cf98b93a12da06bb610173c1e39f9997f97e9610.json deleted file mode 100644 index 8dd84463c..000000000 --- a/api/.sqlx/query-35632bc48d97695339900965cf98b93a12da06bb610173c1e39f9997f97e9610.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT time_bucket, kind as \"kind: DownloadKind\", count\n FROM version_download_counts_4h\n WHERE scope = $1 AND package = $2 AND version = $3 AND time_bucket >= $4 AND time_bucket < $5\n ORDER BY time_bucket ASC\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "time_bucket", - "type_info": "Timestamptz" - }, - { - "ordinal": 1, - "name": "kind: DownloadKind", - "type_info": { - "Custom": { - "name": "download_kind", - "kind": { - "Enum": [ - "npm_tgz", - "jsr_meta" - ] - } - } - } - }, - { - "ordinal": 2, - "name": "count", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text", - "Timestamptz", - "Timestamptz" - ] - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "35632bc48d97695339900965cf98b93a12da06bb610173c1e39f9997f97e9610" -} diff --git a/api/.sqlx/query-3f118b22cfc900a7f6e3e483f97e9761bc495a34441ddf2a128fec6364be6a62.json b/api/.sqlx/query-3f118b22cfc900a7f6e3e483f97e9761bc495a34441ddf2a128fec6364be6a62.json deleted file mode 100644 index e019b4269..000000000 --- a/api/.sqlx/query-3f118b22cfc900a7f6e3e483f97e9761bc495a34441ddf2a128fec6364be6a62.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO scope_members (scope, user_id, is_admin)\n VALUES ($1, $2, $3)\n RETURNING scope as \"scope: ScopeName\", user_id, is_admin, updated_at, created_at", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "scope: ScopeName", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "user_id", - "type_info": "Uuid" - }, - { - "ordinal": 2, - "name": "is_admin", - "type_info": "Bool" - }, - { - "ordinal": 3, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 4, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text", - "Uuid", - "Bool" - ] - }, - "nullable": [ - false, - false, - false, - false, - false - ] - }, - "hash": "3f118b22cfc900a7f6e3e483f97e9761bc495a34441ddf2a128fec6364be6a62" -} diff --git a/api/.sqlx/query-51fd48d86ae305c6fc527fd5b062a35c41672736f94acea3afe9ad369b9f53b5.json b/api/.sqlx/query-51fd48d86ae305c6fc527fd5b062a35c41672736f94acea3afe9ad369b9f53b5.json deleted file mode 100644 index bd69526a4..000000000 --- a/api/.sqlx/query-51fd48d86ae305c6fc527fd5b062a35c41672736f94acea3afe9ad369b9f53b5.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO package_files (scope, name, version, path, size, checksum)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", version as \"version: Version\", path as \"path: PackagePath\", size, checksum, updated_at, created_at", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "scope: ScopeName", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "name: PackageName", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "version: Version", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "path: PackagePath", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "size", - "type_info": "Int4" - }, - { - "ordinal": 5, - "name": "checksum", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "updated_at", - "type_info": "Timestamptz" - }, - { - "ordinal": 7, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text", - "Text", - "Int4", - "Text" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - true, - false, - false - ] - }, - "hash": "51fd48d86ae305c6fc527fd5b062a35c41672736f94acea3afe9ad369b9f53b5" -} diff --git a/api/.sqlx/query-ad4dad0f6bf1d42a787cbb3fbc0854131f1d2d293cbb4faa8f69ae7f91c8ecb9.json b/api/.sqlx/query-9a6acbec173320e7732884e30fc9d59d2a1acd00d8086ef42599cdd12e3196a1.json similarity index 65% rename from api/.sqlx/query-ad4dad0f6bf1d42a787cbb3fbc0854131f1d2d293cbb4faa8f69ae7f91c8ecb9.json rename to api/.sqlx/query-9a6acbec173320e7732884e30fc9d59d2a1acd00d8086ef42599cdd12e3196a1.json index 834d1032a..4b2838fdf 100644 --- a/api/.sqlx/query-ad4dad0f6bf1d42a787cbb3fbc0854131f1d2d293cbb4faa8f69ae7f91c8ecb9.json +++ b/api/.sqlx/query-9a6acbec173320e7732884e30fc9d59d2a1acd00d8086ef42599cdd12e3196a1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat as \"package_runtime_compat: RuntimeCompat\", packages.readme_source as \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE packages.when_featured IS NOT NULL AND NOT packages.is_archived\n ORDER BY packages.when_featured DESC\n LIMIT 10", + "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat as \"package_runtime_compat: RuntimeCompat\", packages.readme_source as \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.is_private \"package_is_private\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND is_yanked = false AND version IS NOT NULL ORDER BY version DESC LIMIT 1) IS NOT NULL AND NOT packages.is_archived AND NOT packages.is_private\n ORDER BY packages.created_at DESC\n LIMIT 10", "describe": { "columns": [ { @@ -55,51 +55,56 @@ }, { "ordinal": 8, + "name": "package_is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "package_updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "package_created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "package_version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "package_latest_version", "type_info": "Text" }, { - "ordinal": 12, + "ordinal": 13, "name": "package_version_meta: PackageVersionMeta", "type_info": "Jsonb" }, { - "ordinal": 13, + "ordinal": 14, "name": "github_repository_id?", "type_info": "Int8" }, { - "ordinal": 14, + "ordinal": 15, "name": "github_repository_owner?", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 16, "name": "github_repository_name?", "type_info": "Text" }, { - "ordinal": 16, + "ordinal": 17, "name": "github_repository_updated_at?", "type_info": "Timestamptz" }, { - "ordinal": 17, + "ordinal": 18, "name": "github_repository_created_at?", "type_info": "Timestamptz" } @@ -118,6 +123,7 @@ false, false, false, + false, null, null, null, @@ -128,5 +134,5 @@ false ] }, - "hash": "ad4dad0f6bf1d42a787cbb3fbc0854131f1d2d293cbb4faa8f69ae7f91c8ecb9" + "hash": "9a6acbec173320e7732884e30fc9d59d2a1acd00d8086ef42599cdd12e3196a1" } diff --git a/api/.sqlx/query-a8050aebace5099be9493ab562f709cfe002530b6e0cfca2cdf1724da907d051.json b/api/.sqlx/query-a8050aebace5099be9493ab562f709cfe002530b6e0cfca2cdf1724da907d051.json new file mode 100644 index 000000000..6607c926e --- /dev/null +++ b/api/.sqlx/query-a8050aebace5099be9493ab562f709cfe002530b6e0cfca2cdf1724da907d051.json @@ -0,0 +1,106 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE packages\n SET is_private = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "scope: ScopeName", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "name: PackageName", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "github_repository_id", + "type_info": "Int8" + }, + { + "ordinal": 4, + "name": "runtime_compat: RuntimeCompat", + "type_info": "Jsonb" + }, + { + "ordinal": 5, + "name": "readme_source: ReadmeSource", + "type_info": { + "Custom": { + "name": "package_readme_source", + "kind": { + "Enum": [ + "readme", + "jsdoc" + ] + } + } + } + }, + { + "ordinal": 6, + "name": "when_featured", + "type_info": "Timestamptz" + }, + { + "ordinal": 7, + "name": "is_archived", + "type_info": "Bool" + }, + { + "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, + "name": "updated_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 10, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 11, + "name": "version_count!", + "type_info": "Int8" + }, + { + "ordinal": 12, + "name": "latest_version", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Bool" + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + false, + true, + false, + false, + false, + false, + null, + null + ] + }, + "hash": "a8050aebace5099be9493ab562f709cfe002530b6e0cfca2cdf1724da907d051" +} diff --git a/api/.sqlx/query-af37a947380c91b98ef36a7f2bcafabc2574dae3f240befb41e4fb4e74280528.json b/api/.sqlx/query-af37a947380c91b98ef36a7f2bcafabc2574dae3f240befb41e4fb4e74280528.json deleted file mode 100644 index c1da7eb2b..000000000 --- a/api/.sqlx/query-af37a947380c91b98ef36a7f2bcafabc2574dae3f240befb41e4fb4e74280528.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO bad_words (word) VALUES ($1)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "af37a947380c91b98ef36a7f2bcafabc2574dae3f240befb41e4fb4e74280528" -} diff --git a/api/.sqlx/query-246465b7114a74de5f74426e402ff9f2d708119dd91b92100d441d338e3e7383.json b/api/.sqlx/query-b3a4a3c8d04a6a26c21054b9e104f19538e11c45b33414c29609729ed417bdb6.json similarity index 81% rename from api/.sqlx/query-246465b7114a74de5f74426e402ff9f2d708119dd91b92100d441d338e3e7383.json rename to api/.sqlx/query-b3a4a3c8d04a6a26c21054b9e104f19538e11c45b33414c29609729ed417bdb6.json index 1963d0356..09c4d6e41 100644 --- a/api/.sqlx/query-246465b7114a74de5f74426e402ff9f2d708119dd91b92100d441d338e3e7383.json +++ b/api/.sqlx/query-b3a4a3c8d04a6a26c21054b9e104f19538e11c45b33414c29609729ed417bdb6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE packages\n SET when_featured = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", + "query": "UPDATE packages\n SET when_featured = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", "describe": { "columns": [ { @@ -55,21 +55,26 @@ }, { "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "latest_version", "type_info": "Text" } @@ -92,9 +97,10 @@ false, false, false, + false, null, null ] }, - "hash": "246465b7114a74de5f74426e402ff9f2d708119dd91b92100d441d338e3e7383" + "hash": "b3a4a3c8d04a6a26c21054b9e104f19538e11c45b33414c29609729ed417bdb6" } diff --git a/api/.sqlx/query-1fa16ba98cff2cac39f2226eed339e74192378e1f664ab7fa32958fce062aa01.json b/api/.sqlx/query-d2957473c284f6b790cd3e21c1cfe7cf6d7581ad92acced7fcbc7aad0ca28d51.json similarity index 80% rename from api/.sqlx/query-1fa16ba98cff2cac39f2226eed339e74192378e1f664ab7fa32958fce062aa01.json rename to api/.sqlx/query-d2957473c284f6b790cd3e21c1cfe7cf6d7581ad92acced7fcbc7aad0ca28d51.json index dcdad6e73..a8c3367c7 100644 --- a/api/.sqlx/query-1fa16ba98cff2cac39f2226eed339e74192378e1f664ab7fa32958fce062aa01.json +++ b/api/.sqlx/query-d2957473c284f6b790cd3e21c1cfe7cf6d7581ad92acced7fcbc7aad0ca28d51.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE packages\n SET github_repository_id = NULL\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", + "query": "UPDATE packages\n SET github_repository_id = NULL\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", "describe": { "columns": [ { @@ -55,21 +55,26 @@ }, { "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "latest_version", "type_info": "Text" } @@ -91,9 +96,10 @@ false, false, false, + false, null, null ] }, - "hash": "1fa16ba98cff2cac39f2226eed339e74192378e1f664ab7fa32958fce062aa01" + "hash": "d2957473c284f6b790cd3e21c1cfe7cf6d7581ad92acced7fcbc7aad0ca28d51" } diff --git a/api/.sqlx/query-9ee1b77a2fbb5b60b0688552a386dc3c4461f3389285880d028c7e3e10b68acc.json b/api/.sqlx/query-ecc554906f315d9f5f152b9851c0df9094a39a8ab3b5a751f437668c47c3a56f.json similarity index 81% rename from api/.sqlx/query-9ee1b77a2fbb5b60b0688552a386dc3c4461f3389285880d028c7e3e10b68acc.json rename to api/.sqlx/query-ecc554906f315d9f5f152b9851c0df9094a39a8ab3b5a751f437668c47c3a56f.json index ce7e5eb88..9798cdfe4 100644 --- a/api/.sqlx/query-9ee1b77a2fbb5b60b0688552a386dc3c4461f3389285880d028c7e3e10b68acc.json +++ b/api/.sqlx/query-ecc554906f315d9f5f152b9851c0df9094a39a8ab3b5a751f437668c47c3a56f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "UPDATE packages\n SET runtime_compat = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", + "query": "UPDATE packages\n SET runtime_compat = $3\n WHERE scope = $1 AND name = $2\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as \"latest_version\"", "describe": { "columns": [ { @@ -55,21 +55,26 @@ }, { "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "latest_version", "type_info": "Text" } @@ -92,9 +97,10 @@ false, false, false, + false, null, null ] }, - "hash": "9ee1b77a2fbb5b60b0688552a386dc3c4461f3389285880d028c7e3e10b68acc" + "hash": "ecc554906f315d9f5f152b9851c0df9094a39a8ab3b5a751f437668c47c3a56f" } diff --git a/api/.sqlx/query-532e3e4dd5ca26105b9cc92f3937ea2c7b486726f9e00245805933a76a0035e7.json b/api/.sqlx/query-f1a90c113e428cb8dfb77979bdef8ba365f1efc2f2ecc9a98e272c25def1def1.json similarity index 69% rename from api/.sqlx/query-532e3e4dd5ca26105b9cc92f3937ea2c7b486726f9e00245805933a76a0035e7.json rename to api/.sqlx/query-f1a90c113e428cb8dfb77979bdef8ba365f1efc2f2ecc9a98e272c25def1def1.json index 63de5b27d..5ae5f3ccb 100644 --- a/api/.sqlx/query-532e3e4dd5ca26105b9cc92f3937ea2c7b486726f9e00245805933a76a0035e7.json +++ b/api/.sqlx/query-f1a90c113e428cb8dfb77979bdef8ba365f1efc2f2ecc9a98e272c25def1def1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat \"package_runtime_compat: RuntimeCompat\", packages.readme_source \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE packages.scope = $1 AND packages.name = $2", + "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat \"package_runtime_compat: RuntimeCompat\", packages.readme_source \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.is_private \"package_is_private\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE packages.scope = $1 AND packages.name = $2", "describe": { "columns": [ { @@ -55,51 +55,56 @@ }, { "ordinal": 8, + "name": "package_is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "package_updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "package_created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "package_version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "package_latest_version", "type_info": "Text" }, { - "ordinal": 12, + "ordinal": 13, "name": "package_version_meta: PackageVersionMeta", "type_info": "Jsonb" }, { - "ordinal": 13, + "ordinal": 14, "name": "github_repository_id?", "type_info": "Int8" }, { - "ordinal": 14, + "ordinal": 15, "name": "github_repository_owner?", "type_info": "Text" }, { - "ordinal": 15, + "ordinal": 16, "name": "github_repository_name?", "type_info": "Text" }, { - "ordinal": 16, + "ordinal": 17, "name": "github_repository_updated_at?", "type_info": "Timestamptz" }, { - "ordinal": 17, + "ordinal": 18, "name": "github_repository_created_at?", "type_info": "Timestamptz" } @@ -121,6 +126,7 @@ false, false, false, + false, null, null, null, @@ -131,5 +137,5 @@ false ] }, - "hash": "532e3e4dd5ca26105b9cc92f3937ea2c7b486726f9e00245805933a76a0035e7" + "hash": "f1a90c113e428cb8dfb77979bdef8ba365f1efc2f2ecc9a98e272c25def1def1" } diff --git a/api/.sqlx/query-0ae764ade6ef1d0d5e29732aa68f0c9cf47f1ae339303eb5be8864b5df07e018.json b/api/.sqlx/query-f53e7cd042933a6427086472f9a710b1c00bf0bb5093be0707abb71431716b58.json similarity index 78% rename from api/.sqlx/query-0ae764ade6ef1d0d5e29732aa68f0c9cf47f1ae339303eb5be8864b5df07e018.json rename to api/.sqlx/query-f53e7cd042933a6427086472f9a710b1c00bf0bb5093be0707abb71431716b58.json index edf9d7314..532412b60 100644 --- a/api/.sqlx/query-0ae764ade6ef1d0d5e29732aa68f0c9cf47f1ae339303eb5be8864b5df07e018.json +++ b/api/.sqlx/query-f53e7cd042933a6427086472f9a710b1c00bf0bb5093be0707abb71431716b58.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO packages (scope, name)\n VALUES ($1, $2)\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"latest_version\"\n ", + "query": "\n INSERT INTO packages (scope, name)\n VALUES ($1, $2)\n RETURNING scope as \"scope: ScopeName\", name as \"name: PackageName\", description, github_repository_id, runtime_compat as \"runtime_compat: RuntimeCompat\", readme_source as \"readme_source: ReadmeSource\", when_featured, is_archived, is_private, updated_at, created_at,\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"latest_version\"\n ", "describe": { "columns": [ { @@ -55,21 +55,26 @@ }, { "ordinal": 8, + "name": "is_private", + "type_info": "Bool" + }, + { + "ordinal": 9, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 11, + "ordinal": 12, "name": "latest_version", "type_info": "Text" } @@ -91,9 +96,10 @@ false, false, false, + false, null, null ] }, - "hash": "0ae764ade6ef1d0d5e29732aa68f0c9cf47f1ae339303eb5be8864b5df07e018" + "hash": "f53e7cd042933a6427086472f9a710b1c00bf0bb5093be0707abb71431716b58" } diff --git a/api/migrations/20260125100000_private_packages.sql b/api/migrations/20260125100000_private_packages.sql new file mode 100644 index 000000000..15f9b4048 --- /dev/null +++ b/api/migrations/20260125100000_private_packages.sql @@ -0,0 +1,2 @@ +ALTER TABLE packages ADD COLUMN is_private BOOLEAN NOT NULL DEFAULT FALSE; +CREATE INDEX idx_packages_is_private ON packages(scope, is_private); diff --git a/api/src/api/package.rs b/api/src/api/package.rs index bc93efe07..28b791dcb 100644 --- a/api/src/api/package.rs +++ b/api/src/api/package.rs @@ -191,6 +191,7 @@ pub fn package_router() -> Router { util::json(list_publishing_tasks_handler), ) .get("/:package/score", util::json(get_score_handler)) + .get("/:package/check-access", util::auth(check_access_handler)) .build() .unwrap() } @@ -501,6 +502,27 @@ pub async fn update_handler(mut req: Request) -> ApiResult { ) .await?; + Ok(ApiPackage::from((package, repo, meta))) + } + ApiUpdatePackageRequest::IsPrivate(is_private) => { + let package = db + .update_package_is_private( + &user.id, + sudo, + &scope, + &package_name, + is_private, + ) + .await?; + + if let Some(orama_client) = orama_client { + if package.is_private { + orama_client.delete_package(&scope, &package.name); + } else { + orama_client.upsert_package(&package, &meta); + } + } + Ok(ApiPackage::from((package, repo, meta))) } } @@ -2373,6 +2395,32 @@ pub async fn get_score_handler( Ok(ApiPackageScore::from((&meta, &pkg))) } +#[instrument( + name = "GET /api/scopes/:scope/packages/:package/check-access", + skip(req), + err, + fields(scope, package) +)] +pub async fn check_access_handler( + req: Request, +) -> ApiResult> { + let scope = req.param_scope()?; + let package = req.param_package()?; + + Span::current().record("scope", field::display(&scope)); + Span::current().record("package", field::display(&package)); + + let iam = req.iam(); + + iam.check_package_read_access(&scope, &package).await?; + + let res = Response::builder() + .status(StatusCode::NO_CONTENT) + .body(Body::empty()) + .unwrap(); + Ok(res) +} + #[cfg(test)] mod test { use hyper::Body; diff --git a/api/src/api/self_user.rs b/api/src/api/self_user.rs index b7244fc39..7071d5796 100644 --- a/api/src/api/self_user.rs +++ b/api/src/api/self_user.rs @@ -12,11 +12,11 @@ use tracing::instrument; use std::borrow::Cow; use crate::RegistryUrl; -use crate::db::Database; use crate::db::PackagePublishPermission; use crate::db::Permission; use crate::db::TokenType; use crate::db::UserPublic; +use crate::db::{Database, PackageReadPermission}; use crate::emails::EmailArgs; use crate::emails::EmailSender; use crate::iam::ReqIamExt; @@ -263,6 +263,19 @@ async fn create_token( "Publish the {} version of the @{}/{} package", version, scope, package )), + Permission::PackageRead(PackageReadPermission::Package { + scope, + package, + }) => Cow::Owned(format!( + "Read the private @{}/{} package", + scope, package + )), + Permission::PackageRead(PackageReadPermission::Scope { scope }) => { + Cow::Owned(format!( + "Read any private package of the @{} scope", + scope + )) + } } } else { Cow::Borrowed("Full account access") diff --git a/api/src/api/types.rs b/api/src/api/types.rs index c17befe92..33f8e4a31 100644 --- a/api/src/api/types.rs +++ b/api/src/api/types.rs @@ -454,6 +454,7 @@ pub struct ApiPackage { pub latest_version: Option, pub when_featured: Option>, pub is_archived: bool, + pub is_private: bool, pub readme_source: ApiReadmeSource, } @@ -481,6 +482,7 @@ impl From for ApiPackage { latest_version: package.latest_version, when_featured: package.when_featured, is_archived: package.is_archived, + is_private: package.is_private, readme_source: package.readme_source.into(), } } @@ -501,6 +503,7 @@ pub enum ApiUpdatePackageRequest { ReadmeSource(ApiReadmeSource), IsFeatured(bool), IsArchived(bool), + IsPrivate(bool), } #[derive(Debug, Deserialize, Serialize, Eq, PartialEq)] diff --git a/api/src/buckets.rs b/api/src/buckets.rs index fd53b6646..dd8cef4b2 100644 --- a/api/src/buckets.rs +++ b/api/src/buckets.rs @@ -117,8 +117,10 @@ impl BucketWithQueue { pub struct Buckets { pub publishing_bucket: BucketWithQueue, pub modules_bucket: BucketWithQueue, + pub modules_private_bucket: BucketWithQueue, pub docs_bucket: BucketWithQueue, pub npm_bucket: BucketWithQueue, + pub npm_private_bucket: BucketWithQueue, } struct UploadTask { diff --git a/api/src/config.rs b/api/src/config.rs index ca75aa9ae..e1a9d44bf 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -41,6 +41,22 @@ pub struct Config { /// The name of the GCS bucket where npm tarballs and metadata are stored. pub npm_bucket: String, + #[clap( + long = "modules_private_bucket", + env = "MODULES_PRIVATE_BUCKET", + default_value = "modules-private" + )] + /// The name of the GCS bucket where private module files and metadata is stored. + pub modules_private_bucket: String, + + #[clap( + long = "npm_private_bucket", + env = "NPM_PRIVATE_BUCKET", + default_value = "npm-private" + )] + /// The name of the GCS bucket where private npm tarballs and metadata are stored. + pub npm_private_bucket: String, + #[clap( long = "metadata_strategy", env = "METADATA_STRATEGY", diff --git a/api/src/db/database.rs b/api/src/db/database.rs index 7032ff8fa..c9ae315a1 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -488,7 +488,7 @@ impl Database { name: &PackageName, ) -> Result> { sqlx::query!( - r#"SELECT packages.scope "package_scope: ScopeName", packages.name "package_name: PackageName", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat "package_runtime_compat: RuntimeCompat", packages.readme_source "package_readme_source: ReadmeSource", packages.when_featured "package_when_featured", packages.is_archived "package_is_archived", packages.updated_at "package_updated_at", packages.created_at "package_created_at", + r#"SELECT packages.scope "package_scope: ScopeName", packages.name "package_name: PackageName", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat "package_runtime_compat: RuntimeCompat", packages.readme_source "package_readme_source: ReadmeSource", packages.when_featured "package_when_featured", packages.is_archived "package_is_archived", packages.is_private "package_is_private", packages.updated_at "package_updated_at", packages.created_at "package_created_at", (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as "package_version_count!", (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_latest_version", (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_version_meta: PackageVersionMeta", @@ -512,6 +512,7 @@ impl Database { latest_version: r.package_latest_version, when_featured: r.package_when_featured, is_archived: r.package_is_archived, + is_private: r.package_is_private, readme_source: r.package_readme_source, }; let github_repository = if r.package_github_repository_id.is_some() { @@ -546,7 +547,7 @@ impl Database { r#" INSERT INTO packages (scope, name) VALUES ($1, $2) - RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, updated_at, created_at, + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as "version_count!", (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "latest_version" "#, @@ -628,7 +629,7 @@ impl Database { r#"UPDATE packages SET description = $3 WHERE scope = $1 AND name = $2 - RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, updated_at, created_at, + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as "version_count!", (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as "latest_version", (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_version_meta: PackageVersionMeta""#, @@ -649,6 +650,7 @@ impl Database { latest_version: r.latest_version, when_featured: r.when_featured, is_archived: r.is_archived, + is_private: r.is_private, readme_source: r.readme_source, }; @@ -708,7 +710,7 @@ impl Database { r#"UPDATE packages SET github_repository_id = $3 WHERE scope = $1 AND name = $2 - RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, updated_at, created_at, + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as "version_count!", (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "latest_version", (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_version_meta: PackageVersionMeta""#, @@ -729,6 +731,7 @@ impl Database { latest_version: r.latest_version, when_featured: r.when_featured, is_archived: r.is_archived, + is_private: r.is_private, readme_source: r.readme_source, }; @@ -773,7 +776,7 @@ impl Database { r#"UPDATE packages SET github_repository_id = NULL WHERE scope = $1 AND name = $2 - RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, updated_at, created_at, + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as "version_count!", (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as "latest_version""#, scope as _, @@ -820,7 +823,7 @@ impl Database { r#"UPDATE packages SET runtime_compat = $3 WHERE scope = $1 AND name = $2 - RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, updated_at, created_at, + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as "version_count!", (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as "latest_version""#, scope as _, @@ -865,7 +868,7 @@ impl Database { r#"UPDATE packages SET when_featured = $3 WHERE scope = $1 AND name = $2 - RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, updated_at, created_at, + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as "version_count!", (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as "latest_version""#, scope as _, @@ -909,7 +912,7 @@ impl Database { r#"UPDATE packages SET is_archived = $3 WHERE scope = $1 AND name = $2 - RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, updated_at, created_at, + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as "version_count!", (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as "latest_version""#, scope as _, @@ -924,6 +927,50 @@ impl Database { Ok(package) } + #[instrument(name = "Database::update_package_is_private", skip(self), err)] + pub async fn update_package_is_private( + &self, + actor_id: &Uuid, + is_sudo: bool, + scope: &ScopeName, + name: &PackageName, + is_private: bool, + ) -> Result { + let mut tx = self.pool.begin().await?; + + audit_log( + &mut tx, + actor_id, + is_sudo, + "package_set_private", + json!({ + "scope": scope, + "name": name, + "is_private": is_private, + }), + ) + .await?; + + let package = sqlx::query_as!( + Package, + r#"UPDATE packages + SET is_private = $3 + WHERE scope = $1 AND name = $2 + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, + (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as "version_count!", + (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as "latest_version""#, + scope as _, + name as _, + is_private, + ) + .fetch_one(&mut *tx) + .await?; + + tx.commit().await?; + + Ok(package) + } + #[instrument(name = "Database::update_package_source", skip(self), err)] pub async fn update_package_source( &self, @@ -953,7 +1000,7 @@ impl Database { r#"UPDATE packages SET readme_source = $3 WHERE scope = $1 AND name = $2 - RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, updated_at, created_at, + RETURNING scope as "scope: ScopeName", name as "name: PackageName", description, github_repository_id, runtime_compat as "runtime_compat: RuntimeCompat", readme_source as "readme_source: ReadmeSource", when_featured, is_archived, is_private, updated_at, created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = scope AND name = name) as "version_count!", (SELECT version FROM package_versions WHERE scope = scope AND name = name ORDER BY version DESC LIMIT 1) as "latest_version""#, scope as _, @@ -1488,7 +1535,7 @@ impl Database { let mut tx = self.pool.begin().await?; let packages = sqlx::query!( - r#"SELECT packages.scope "package_scope: ScopeName", packages.name "package_name: PackageName", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat as "package_runtime_compat: RuntimeCompat", packages.readme_source as "package_readme_source: ReadmeSource", packages.when_featured "package_when_featured", packages.is_archived "package_is_archived", packages.updated_at "package_updated_at", packages.created_at "package_created_at", + r#"SELECT packages.scope "package_scope: ScopeName", packages.name "package_name: PackageName", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat as "package_runtime_compat: RuntimeCompat", packages.readme_source as "package_readme_source: ReadmeSource", packages.when_featured "package_when_featured", packages.is_archived "package_is_archived", packages.is_private "package_is_private", packages.updated_at "package_updated_at", packages.created_at "package_created_at", (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as "package_version_count!", (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_latest_version", (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_version_meta: PackageVersionMeta", @@ -1516,6 +1563,7 @@ impl Database { latest_version: r.package_latest_version, when_featured: r.package_when_featured, is_archived: r.package_is_archived, + is_private: r.package_is_private, readme_source: r.package_readme_source, }; let github_repository = if r.package_github_repository_id.is_some() { @@ -1615,14 +1663,14 @@ impl Database { } || "packages.name ASC, packages.scope ASC"); let packages = sqlx::query( - &format!(r#"SELECT packages.scope "package_scope", packages.name "package_name", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat as "package_runtime_compat", packages.when_featured "package_when_featured", packages.readme_source "package_readme_source", packages.is_archived "package_is_archived", packages.updated_at "package_updated_at", packages.created_at "package_created_at", + &format!(r#"SELECT packages.scope "package_scope", packages.name "package_name", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat as "package_runtime_compat", packages.when_featured "package_when_featured", packages.readme_source "package_readme_source", packages.is_archived "package_is_archived", packages.is_private "package_is_private", packages.updated_at "package_updated_at", packages.created_at "package_created_at", (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as "package_version_count", (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_latest_version", (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_version_meta", github_repositories.id "github_repository_id", github_repositories.owner "github_repository_owner", github_repositories.name "github_repository_name", github_repositories.updated_at "github_repository_updated_at", github_repositories.created_at "github_repository_created_at" FROM packages LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id - WHERE (packages.scope ILIKE $1 OR packages.name ILIKE $2) AND (packages.github_repository_id = $5 OR $5 IS NULL) AND NOT packages.is_archived + WHERE (packages.scope ILIKE $1 OR packages.name ILIKE $2) AND (packages.github_repository_id = $5 OR $5 IS NULL) AND NOT packages.is_archived AND NOT packages.is_private ORDER BY CASE WHEN packages.name ILIKE $3 THEN 1 -- Exact match for package name @@ -1678,14 +1726,14 @@ impl Database { Vec, )> { let newest = sqlx::query!( - r#"SELECT packages.scope "package_scope: ScopeName", packages.name "package_name: PackageName", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat as "package_runtime_compat: RuntimeCompat", packages.readme_source as "package_readme_source: ReadmeSource", packages.when_featured "package_when_featured", packages.is_archived "package_is_archived", packages.updated_at "package_updated_at", packages.created_at "package_created_at", + r#"SELECT packages.scope "package_scope: ScopeName", packages.name "package_name: PackageName", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat as "package_runtime_compat: RuntimeCompat", packages.readme_source as "package_readme_source: ReadmeSource", packages.when_featured "package_when_featured", packages.is_archived "package_is_archived", packages.is_private "package_is_private", packages.updated_at "package_updated_at", packages.created_at "package_created_at", (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as "package_version_count!", (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_latest_version", (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_version_meta: PackageVersionMeta", github_repositories.id "github_repository_id?", github_repositories.owner "github_repository_owner?", github_repositories.name "github_repository_name?", github_repositories.updated_at "github_repository_updated_at?", github_repositories.created_at "github_repository_created_at?" FROM packages LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id - WHERE (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND is_yanked = false AND version IS NOT NULL ORDER BY version DESC LIMIT 1) IS NOT NULL AND NOT packages.is_archived + WHERE (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND is_yanked = false AND version IS NOT NULL ORDER BY version DESC LIMIT 1) IS NOT NULL AND NOT packages.is_archived AND NOT packages.is_private ORDER BY packages.created_at DESC LIMIT 10"#, ) @@ -1702,6 +1750,7 @@ impl Database { latest_version: r.package_latest_version, when_featured: r.package_when_featured, is_archived: r.package_is_archived, + is_private: r.package_is_private, readme_source: r.package_readme_source, }; let github_repository = if r.package_github_repository_id.is_some() { @@ -1746,14 +1795,14 @@ impl Database { .await?; let featured = sqlx::query!( - r#"SELECT packages.scope "package_scope: ScopeName", packages.name "package_name: PackageName", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat as "package_runtime_compat: RuntimeCompat", packages.readme_source as "package_readme_source: ReadmeSource", packages.when_featured "package_when_featured", packages.is_archived "package_is_archived", packages.updated_at "package_updated_at", packages.created_at "package_created_at", + r#"SELECT packages.scope "package_scope: ScopeName", packages.name "package_name: PackageName", packages.description "package_description", packages.github_repository_id "package_github_repository_id", packages.runtime_compat as "package_runtime_compat: RuntimeCompat", packages.readme_source as "package_readme_source: ReadmeSource", packages.when_featured "package_when_featured", packages.is_archived "package_is_archived", packages.is_private "package_is_private", packages.updated_at "package_updated_at", packages.created_at "package_created_at", (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as "package_version_count!", (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_latest_version", (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as "package_version_meta: PackageVersionMeta", github_repositories.id "github_repository_id?", github_repositories.owner "github_repository_owner?", github_repositories.name "github_repository_name?", github_repositories.updated_at "github_repository_updated_at?", github_repositories.created_at "github_repository_created_at?" FROM packages LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id - WHERE packages.when_featured IS NOT NULL AND NOT packages.is_archived + WHERE packages.when_featured IS NOT NULL AND NOT packages.is_archived AND NOT packages.is_private ORDER BY packages.when_featured DESC LIMIT 10"#, ) @@ -1770,6 +1819,7 @@ impl Database { latest_version: r.package_latest_version, when_featured: r.package_when_featured, is_archived: r.package_is_archived, + is_private: r.package_is_private, readme_source: r.package_readme_source, }; let github_repository = if r.package_github_repository_id.is_some() { diff --git a/api/src/db/models.rs b/api/src/db/models.rs index f6e959933..670b220c8 100644 --- a/api/src/db/models.rs +++ b/api/src/db/models.rs @@ -349,6 +349,7 @@ pub struct Package { pub latest_version: Option, pub when_featured: Option>, pub is_archived: bool, + pub is_private: bool, pub readme_source: ReadmeSource, } @@ -394,6 +395,7 @@ impl FromRow<'_, sqlx::postgres::PgRow> for Package { "package_when_featured", )?, is_archived: try_get_row_or(row, "is_archived", "package_is_archived")?, + is_private: try_get_row_or(row, "is_private", "package_is_private")?, readme_source: try_get_row_or( row, "readme_source", @@ -624,6 +626,8 @@ pub struct Permissions(pub Vec); pub enum Permission { #[serde(rename = "package/publish")] PackagePublish(PackagePublishPermission), + #[serde(rename = "package/read")] + PackageRead(PackageReadPermission), } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -645,6 +649,18 @@ pub enum PackagePublishPermission { Scope { scope: ScopeName }, } +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum PackageReadPermission { + #[serde(rename_all = "camelCase")] + Package { + scope: ScopeName, + package: PackageName, + }, + #[serde(rename_all = "camelCase")] + Scope { scope: ScopeName }, +} + impl sqlx::Decode<'_, sqlx::Postgres> for Permissions { fn decode( value: sqlx::postgres::PgValueRef<'_>, diff --git a/api/src/iam.rs b/api/src/iam.rs index da1079a2a..1342c40e2 100644 --- a/api/src/iam.rs +++ b/api/src/iam.rs @@ -7,6 +7,7 @@ use uuid::Uuid; use crate::api::ApiError; use crate::db::Database; use crate::db::PackagePublishPermission; +use crate::db::PackageReadPermission; use crate::db::Permission; use crate::db::Permissions; use crate::db::Token; @@ -251,6 +252,72 @@ impl<'s> IamHandler<'s> { Principal::Anonymous => Err(ApiError::MissingAuthentication), } } + + pub async fn check_package_read_access( + &self, + scope: &ScopeName, + package: &PackageName, + ) -> Result, ApiError> { + // Get the package to check if it's private + let (pkg, _, _) = self + .db + .get_package(scope, package) + .await? + .ok_or(ApiError::PackageNotFound)?; + + // Public packages are accessible to everyone + if !pkg.is_private { + return Ok(match &self.principal { + Principal::User(user) => Some(user), + _ => None, + }); + } + + // Private packages require authentication and authorization + // Check if token has explicit PackageRead permission + if let Some(permissions) = &self.permissions { + let has_read_permission = + permissions.0.iter().any(|permission| match permission { + Permission::PackageRead(PackageReadPermission::Scope { + scope: s, + }) if s == scope => true, + Permission::PackageRead(PackageReadPermission::Package { + scope: s, + package: p, + }) if s == scope && p == package => true, + _ => false, + }); + if !has_read_permission { + return Err(ApiError::MissingPermission); + } + } + + match &self.principal { + Principal::User(user) => { + // Check if user is a scope member + if self.db.get_scope_member(scope, user.id).await?.is_some() { + Ok(Some(user)) + } else if user.is_staff && self.sudo { + Ok(Some(user)) + } else { + Err(ApiError::ActorNotScopeMember) + } + } + Principal::GitHubActions { user, .. } => { + // GitHub Actions can access if the user is a scope member + if let Some(user) = user { + if self.db.get_scope_member(scope, user.id).await?.is_some() { + Ok(Some(user)) + } else { + Err(ApiError::ActorNotScopeMember) + } + } else { + Err(ApiError::ActorNotScopeMember) + } + } + Principal::Anonymous => Err(ApiError::MissingAuthentication), + } + } } pub struct PublishAccessRestriction { diff --git a/api/src/main.rs b/api/src/main.rs index c4edcfb55..b8e5bee51 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -178,13 +178,25 @@ async fn main() { let npm_bucket = BucketWithQueue::new(gcp::Bucket::new( gcp_client.clone(), config.npm_bucket, + config.gcs_endpoint.clone(), + )); + let modules_private_bucket = BucketWithQueue::new(gcp::Bucket::new( + gcp_client.clone(), + config.modules_private_bucket, + config.gcs_endpoint.clone(), + )); + let npm_private_bucket = BucketWithQueue::new(gcp::Bucket::new( + gcp_client.clone(), + config.npm_private_bucket, config.gcs_endpoint, )); let buckets = Buckets { publishing_bucket, modules_bucket: modules_bucket.clone(), + modules_private_bucket, docs_bucket, npm_bucket, + npm_private_bucket, }; let publish_queue = config diff --git a/api/src/orama.rs b/api/src/orama.rs index efb3ce861..6596db871 100644 --- a/api/src/orama.rs +++ b/api/src/orama.rs @@ -54,7 +54,7 @@ impl OramaClient { #[instrument(name = "OramaClient::upsert_package", skip(self))] pub fn upsert_package(&self, package: &Package, meta: &PackageVersionMeta) { - if package.version_count == 0 || package.is_archived { + if package.version_count == 0 || package.is_archived || package.is_private { return; } diff --git a/api/src/publish.rs b/api/src/publish.rs index e6c6b8b80..1f0c13552 100644 --- a/api/src/publish.rs +++ b/api/src/publish.rs @@ -122,9 +122,34 @@ pub async fn publish_task( return Err(ApiError::InternalServerError); } PublishingTaskStatus::Processed => { - upload_package_manifest(&db, &buckets, &publishing_task).await?; - upload_npm_version_manifest(&db, &buckets, &npm_url, &publishing_task) - .await?; + let (package, _, _) = db + .get_package( + &publishing_task.package_scope, + &publishing_task.package_name, + ) + .await? + .ok_or_else(|| { + error!( + "package not found during manifest upload: {}/{}", + &publishing_task.package_scope, &publishing_task.package_name + ); + ApiError::InternalServerError + })?; + upload_package_manifest( + &db, + &buckets, + &publishing_task, + package.is_private, + ) + .await?; + upload_npm_version_manifest( + &db, + &buckets, + &npm_url, + &publishing_task, + package.is_private, + ) + .await?; publishing_task = db .update_publishing_task_status( None, @@ -237,11 +262,22 @@ async fn process_publishing_task( .await?; if let Some(orama_client) = orama_client { - orama_client.upsert_symbols( - &publishing_task.package_scope, - &publishing_task.package_name, - doc_search_json, - ); + let (package, _, _) = db + .get_package( + &publishing_task.package_scope, + &publishing_task.package_name, + ) + .await? + .ok_or_else(|| { + anyhow::anyhow!("package not found during symbol indexing") + })?; + if !package.is_private { + orama_client.upsert_symbols( + &publishing_task.package_scope, + &publishing_task.package_name, + doc_search_json, + ); + } } Ok(()) @@ -371,6 +407,7 @@ async fn upload_package_manifest( db: &Database, buckets: &Buckets, publishing_task: &PublishingTask, + is_private: bool, ) -> Result<(), anyhow::Error> { let package_metadata_gcs_path = crate::gcs_paths::package_metadata( &publishing_task.package_scope, @@ -383,8 +420,12 @@ async fn upload_package_manifest( ) .await?; let content = serde_json::to_vec(&package_metadata)?; - buckets - .modules_bucket + let modules_bucket = if is_private { + &buckets.modules_private_bucket + } else { + &buckets.modules_bucket + }; + modules_bucket .upload( package_metadata_gcs_path.into(), UploadTaskBody::Bytes(content.into()), @@ -404,6 +445,7 @@ async fn upload_npm_version_manifest( buckets: &Buckets, npm_url: &Url, publishing_task: &PublishingTask, + is_private: bool, ) -> Result<(), anyhow::Error> { let npm_version_manifest_path_gcs_path = crate::gcs_paths::npm_version_manifest_path( @@ -418,8 +460,12 @@ async fn upload_npm_version_manifest( ) .await?; let content = serde_json::to_vec_pretty(&npm_version_manifest)?; - buckets - .npm_bucket + let npm_bucket = if is_private { + &buckets.npm_private_bucket + } else { + &buckets.npm_bucket + }; + npm_bucket .upload( npm_version_manifest_path_gcs_path.into(), UploadTaskBody::Bytes(content.into()), diff --git a/api/src/tarball.rs b/api/src/tarball.rs index 35531c2fc..0d1c35a49 100644 --- a/api/src/tarball.rs +++ b/api/src/tarball.rs @@ -33,9 +33,10 @@ use crate::analysis::analyze_package; use crate::buckets::Buckets; use crate::buckets::UploadTaskBody; use crate::db::Database; +use crate::db::DependencyKind; use crate::db::ExportsMap; +use crate::db::PackageVersionMeta; use crate::db::PublishingTask; -use crate::db::{DependencyKind, PackageVersionMeta}; use crate::gcp::CACHE_CONTROL_IMMUTABLE; use crate::gcp::GcsError; use crate::gcp::GcsUploadOptions; @@ -89,6 +90,20 @@ pub async fn process_tarball( registry_url: Url, publishing_task: &PublishingTask, ) -> Result { + let (package, _, _) = db + .get_package( + &publishing_task.package_scope, + &publishing_task.package_name, + ) + .await? + .ok_or(PublishError::PackageNotFound)?; + + let (modules_bucket, npm_bucket) = if package.is_private { + (&buckets.modules_private_bucket, &buckets.npm_private_bucket) + } else { + (&buckets.modules_bucket, &buckets.npm_bucket) + }; + let tarball_path = gcs_tarball_path(publishing_task.id); let stream = buckets .publishing_bucket @@ -383,8 +398,7 @@ pub async fn process_tarball( &publishing_task.package_version, NPM_TARBALL_REVISION, ); - buckets - .npm_bucket + npm_bucket .upload( npm_tarball_path.into(), UploadTaskBody::Bytes(Bytes::from(npm_tarball.tarball)), @@ -429,8 +443,7 @@ pub async fn process_tarball( ); async move { - buckets - .modules_bucket + modules_bucket .upload( gcs_path.into(), UploadTaskBody::Bytes(bytes), @@ -476,6 +489,9 @@ pub enum PublishError { #[error("missing tarball")] MissingTarball, + #[error("package not found")] + PackageNotFound, + #[error("gcs upload error: {0}")] GcsUploadError(GcsError), @@ -696,6 +712,7 @@ impl PublishError { PublishError::InvalidJsrDependencySubPath { .. } => { Some("invalidJsrDependencySubPath") } + PublishError::PackageNotFound => Some("packageNotFound"), } } } diff --git a/api/src/util.rs b/api/src/util.rs index c106479a6..adee44fee 100644 --- a/api/src/util.rs +++ b/api/src/util.rs @@ -472,13 +472,17 @@ pub mod test { let gcs = FakeGcsTester::new().await; let publishing_bucket = gcs.create_bucket("publishing").await; let modules_bucket = gcs.create_bucket("modules").await; + let modules_private_bucket = gcs.create_bucket("modules-private").await; let docs_bucket = gcs.create_bucket("docs").await; let npm_bucket = gcs.create_bucket("npm").await; + let npm_private_bucket = gcs.create_bucket("npm-private").await; let buckets = Buckets { publishing_bucket: BucketWithQueue::new(publishing_bucket), modules_bucket: BucketWithQueue::new(modules_bucket), + modules_private_bucket: BucketWithQueue::new(modules_private_bucket), docs_bucket: BucketWithQueue::new(docs_bucket), npm_bucket: BucketWithQueue::new(npm_bucket), + npm_private_bucket: BucketWithQueue::new(npm_private_bucket), }; let github_oauth2_client = GithubOauth2Client::new( oauth2::ClientId::new("".to_string()), diff --git a/frontend/routes/package/(_components)/PackageHeader.tsx b/frontend/routes/package/(_components)/PackageHeader.tsx index dd279b292..10d43aa5c 100644 --- a/frontend/routes/package/(_components)/PackageHeader.tsx +++ b/frontend/routes/package/(_components)/PackageHeader.tsx @@ -138,6 +138,12 @@ export function PackageHeader({
+ {pkg.isPrivate && ( +
+ private +
+ )} + {selectedVersion && pkg.latestVersion === selectedVersion?.version && (
diff --git a/frontend/routes/package/settings.tsx b/frontend/routes/package/settings.tsx index 1eb5ef99f..0f08f21aa 100644 --- a/frontend/routes/package/settings.tsx +++ b/frontend/routes/package/settings.tsx @@ -39,6 +39,8 @@ export default define.page( + + 0} /> @@ -211,6 +213,54 @@ function RuntimeCompatEditorItem({ name, id, value }: { ); } +function PrivatePackage(props: { isPrivate: boolean }) { + if (!props.isPrivate) { + return ( +
+
+

Make package private

+

+ Making a package private restricts access to scope members only. + Private packages are not visible in search results or on the scope + page. Users will need a valid bearer token to access the package + files. +

+
+ + +
+ ); + } else { + return ( +
+
+

Make package public

+

+ Making a package public allows anyone to view and use the package. + The package will appear in search results and on the scope page. +

+
+ + +
+ ); + } +} + function ArchivePackage(props: { isArchived: boolean }) { if (!props.isArchived) { return ( @@ -477,6 +527,28 @@ export const handler = define.handlers({ headers: { Location: `/@${scope}/${packageName}/settings` }, }); } + case "makePrivate": { + const repoRes = await api.patch( + path`/scopes/${scope}/packages/${packageName}`, + { isPrivate: true }, + ); + if (!repoRes.ok) throw repoRes; + return new Response(null, { + status: 303, + headers: { Location: `/@${scope}/${packageName}/settings` }, + }); + } + case "makePublic": { + const repoRes = await api.patch( + path`/scopes/${scope}/packages/${packageName}`, + { isPrivate: false }, + ); + if (!repoRes.ok) throw repoRes; + return new Response(null, { + status: 303, + headers: { Location: `/@${scope}/${packageName}/settings` }, + }); + } default: { throw new Error("Invalid action " + action); } diff --git a/frontend/utils/api_types.ts b/frontend/utils/api_types.ts index c373f7490..5a8995078 100644 --- a/frontend/utils/api_types.ts +++ b/frontend/utils/api_types.ts @@ -123,6 +123,7 @@ export interface Package { latestVersion: string | null; whenFeatured: string | null; isArchived: boolean; + isPrivate: boolean; readmeSource: ReadmeSource; } diff --git a/lb/local.ts b/lb/local.ts index 385a48d13..403eab95f 100755 --- a/lb/local.ts +++ b/lb/local.ts @@ -9,6 +9,8 @@ const REGISTRY_API_URL = Deno.env.get("REGISTRY_API_URL") ?? const GCS_ENDPOINT = Deno.env.get("GCS_ENDPOINT") ?? "http://localhost:4080"; const MODULES_BUCKET = Deno.env.get("MODULES_BUCKET") ?? "modules"; const NPM_BUCKET = Deno.env.get("NPM_BUCKET") ?? "npm"; +const MODULES_PRIVATE_BUCKET = Deno.env.get("MODULES_PRIVATE_BUCKET") ?? "modules_private"; +const NPM_PRIVATE_BUCKET = Deno.env.get("NPM_PRIVATE_BUCKET") ?? "npm_private"; const ROOT_DOMAIN = Deno.env.get("ROOT_DOMAIN") ?? "jsr.test"; const API_DOMAIN = Deno.env.get("API_DOMAIN") ?? "api.jsr.test"; @@ -47,7 +49,9 @@ function handler(req: Request): Promise { REGISTRY_FRONTEND_URL, GCS_ENDPOINT, MODULES_BUCKET, + MODULES_PRIVATE_BUCKET, NPM_BUCKET, + NPM_PRIVATE_BUCKET, ROOT_DOMAIN, API_DOMAIN, NPM_DOMAIN, diff --git a/lb/main.ts b/lb/main.ts index 728dc783c..62df19bbc 100644 --- a/lb/main.ts +++ b/lb/main.ts @@ -86,6 +86,13 @@ export async function handleAPIRequest( return response; } +function NpmPathRewrite(path: string): string { + if (path === "/" || path === "/-/ping") { + return "/root.json"; + } + return path; +} + export async function handleNPMRequest( request: Request, env: WorkerEnv, @@ -95,18 +102,33 @@ export async function handleNPMRequest( } const url = new URL(request.url); - const response = await proxyToGCS( + let response = await proxyToGCS( request, env.GCS_ENDPOINT, env.NPM_BUCKET, - (path) => { - if (path === "/" || path === "/-/ping") { - return "/root.json"; - } - return path; - }, + NpmPathRewrite, ); + if (response.status === 404) { + const match = url.pathname.match(/^\/@jsr\/([^_]+)__([^/]+)/); + if (match) { + const hasAccess = await validatePackageAccess( + request, + match[1], + match[2], + env, + ); + if (hasAccess) { + response = await proxyToGCS( + request, + env.GCS_ENDPOINT, + env.NPM_PRIVATE_BUCKET, + NpmPathRewrite, + ); + } + } + } + setSecurityHeaders(response, NPM); setCORSHeaders(response, NPM); setDebugHeaders(response, { @@ -120,6 +142,25 @@ export async function handleNPMRequest( return response; } +async function validatePackageAccess( + request: Request, + scope: string, + pkg: string, + env: WorkerEnv, +): Promise { + const authHeader = request.headers.get("Authorization"); + const resp = await fetch( + `${env.REGISTRY_API_URL}/api/scopes/${scope}/packages/${pkg}/check-access`, + { + headers: { + Authorization: authHeader ?? "", + }, + }, + ); + + return resp.ok; +} + /** * By default, requests to jsr.io are proxied to the frontend hosted on Cloud * Run. @@ -151,6 +192,10 @@ export async function handleNPMRequest( * WARNING: Exercise extreme caution when modifying this. Untrusted files are * stored under the /@ prefix. It's crucial that the browser never loads these * untrusted files. + * + * PRIVATE PACKAGES: For performance, we try public bucket first. + * On 404, we check if the package is private and validate auth. + * This keeps public package requests fast with zero overhead. */ export async function handleRootRequest( request: Request, @@ -230,12 +275,31 @@ async function handleModuleFileRoute( env: WorkerEnv, ): Promise { const url = new URL(request.url); - const response = await proxyToGCS( + let response = await proxyToGCS( request, env.GCS_ENDPOINT, env.MODULES_BUCKET, ); + if (response.status === 404) { + const match = url.pathname.match(/^\/@([^/]+)\/([^/]+)/); + if (match) { + const hasAccess = await validatePackageAccess( + request, + match[1], + match[2], + env, + ); + if (hasAccess) { + response = await proxyToGCS( + request, + env.GCS_ENDPOINT, + env.MODULES_PRIVATE_BUCKET, + ); + } + } + } + setSecurityHeaders(response, MODULES); setCORSHeaders(response, MODULES); setDebugHeaders(response, { diff --git a/lb/types.ts b/lb/types.ts index 5eb463556..3b454ef4f 100644 --- a/lb/types.ts +++ b/lb/types.ts @@ -22,6 +22,12 @@ declare global { cacheKey?: string; }; } + + interface KVNamespace { + get(key: string): Promise; + put(key: string, value: string, options?: { expirationTtl?: number }): Promise; + delete(key: string): Promise; + } } export interface WorkerEnv { @@ -31,11 +37,14 @@ export interface WorkerEnv { GCS_ENDPOINT?: string; MODULES_BUCKET: string; + MODULES_PRIVATE_BUCKET: string; NPM_BUCKET: string; + NPM_PRIVATE_BUCKET: string; ROOT_DOMAIN: string; API_DOMAIN: string; NPM_DOMAIN: string; DOWNLOADS?: AnalyticsEngineDataset; + PRIVATE_PACKAGES_KV?: KVNamespace; } diff --git a/terraform/buckets.tf b/terraform/buckets.tf index c154673a0..37f7c1417 100644 --- a/terraform/buckets.tf +++ b/terraform/buckets.tf @@ -29,6 +29,18 @@ resource "google_storage_bucket" "docs" { force_destroy = true } +resource "google_storage_bucket" "modules_private" { + name = "${var.gcp_project}-modules-private" + location = "US" + force_destroy = true +} + +resource "google_storage_bucket" "npm_private" { + name = "${var.gcp_project}-npm-private" + location = "US" + force_destroy = true +} + resource "google_storage_bucket" "npm" { name = "${var.gcp_project}-npm" location = "US" diff --git a/terraform/lb.tf b/terraform/lb.tf index 477fa68a8..1944d6d35 100644 --- a/terraform/lb.tf +++ b/terraform/lb.tf @@ -51,8 +51,16 @@ resource "cloudflare_workers_script" "jsr_lb" { text = google_storage_bucket.modules.name }, { type = "secret_text" + name = "MODULES_PRIVATE_BUCKET" + text = google_storage_bucket.modules_private.name + }, { + type = "secret_text" name = "NPM_BUCKET" text = google_storage_bucket.npm.name + }, { + type = "secret_text" + name = "NPM_PRIVATE_BUCKET" + text = google_storage_bucket.npm_private.name } ] From 0c26210d154e3b62616c818f812105c818ec4d8a Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Mon, 26 Jan 2026 13:49:41 +0100 Subject: [PATCH 2/3] rework token handling & inprove privacy checks --- ...f5c2e4067899bdc4e83b61976defce4ab98d.json} | 5 +- ...96be21408a971c6c422f072c23ce3c64dfcf.json} | 4 +- ...ea9f49cd312dcf6d4a891d9dbdb46d7ffbbc.json} | 7 +- api/src/api/package.rs | 116 ++++++++++++- api/src/api/self_user.rs | 3 + api/src/db/database.rs | 11 +- api/src/db/models.rs | 2 + api/src/iam.rs | 14 ++ deno.lock | 22 +++ frontend/components/PackageHit.tsx | 11 +- .../account/tokens/(_islands)/CreateToken.tsx | 157 +++++++++++++----- frontend/utils/api_types.ts | 25 ++- lb/local.ts | 14 +- lb/types.ts | 6 +- 14 files changed, 337 insertions(+), 60 deletions(-) rename api/.sqlx/{query-0824a1acff26ba7c43687f2c3305e9a0824a01e40c708b246881d0faa429e51d.json => query-5c25db7b857ff64e0004ae6122a0f5c2e4067899bdc4e83b61976defce4ab98d.json} (64%) rename api/.sqlx/{query-ae936fd4921cd295d2b577c20976f56a46a140f78eee41f4dadbddec40c8852b.json => query-ba22535d224791f09049bb039d4596be21408a971c6c422f072c23ce3c64dfcf.json} (93%) rename api/.sqlx/{query-1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b.json => query-bcd2d6abf8d3ff95b1dc96d68db1ea9f49cd312dcf6d4a891d9dbdb46d7ffbbc.json} (93%) diff --git a/api/.sqlx/query-0824a1acff26ba7c43687f2c3305e9a0824a01e40c708b246881d0faa429e51d.json b/api/.sqlx/query-5c25db7b857ff64e0004ae6122a0f5c2e4067899bdc4e83b61976defce4ab98d.json similarity index 64% rename from api/.sqlx/query-0824a1acff26ba7c43687f2c3305e9a0824a01e40c708b246881d0faa429e51d.json rename to api/.sqlx/query-5c25db7b857ff64e0004ae6122a0f5c2e4067899bdc4e83b61976defce4ab98d.json index 8631733f9..e8b87b0e1 100644 --- a/api/.sqlx/query-0824a1acff26ba7c43687f2c3305e9a0824a01e40c708b246881d0faa429e51d.json +++ b/api/.sqlx/query-5c25db7b857ff64e0004ae6122a0f5c2e4067899bdc4e83b61976defce4ab98d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT COUNT(created_at) FROM packages WHERE scope = $1 AND ($2 = true OR packages.is_archived = false);", + "query": "SELECT COUNT(created_at) FROM packages WHERE scope = $1 AND ($2 = true OR packages.is_archived = false) AND ($3 = true OR packages.is_private = false);", "describe": { "columns": [ { @@ -12,6 +12,7 @@ "parameters": { "Left": [ "Text", + "Bool", "Bool" ] }, @@ -19,5 +20,5 @@ null ] }, - "hash": "0824a1acff26ba7c43687f2c3305e9a0824a01e40c708b246881d0faa429e51d" + "hash": "5c25db7b857ff64e0004ae6122a0f5c2e4067899bdc4e83b61976defce4ab98d" } diff --git a/api/.sqlx/query-ae936fd4921cd295d2b577c20976f56a46a140f78eee41f4dadbddec40c8852b.json b/api/.sqlx/query-ba22535d224791f09049bb039d4596be21408a971c6c422f072c23ce3c64dfcf.json similarity index 93% rename from api/.sqlx/query-ae936fd4921cd295d2b577c20976f56a46a140f78eee41f4dadbddec40c8852b.json rename to api/.sqlx/query-ba22535d224791f09049bb039d4596be21408a971c6c422f072c23ce3c64dfcf.json index 3dccd63f7..92b186b63 100644 --- a/api/.sqlx/query-ae936fd4921cd295d2b577c20976f56a46a140f78eee41f4dadbddec40c8852b.json +++ b/api/.sqlx/query-ba22535d224791f09049bb039d4596be21408a971c6c422f072c23ce3c64dfcf.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT package_versions.scope as \"scope: ScopeName\", package_versions.name as \"name: PackageName\", package_versions.version as \"version: Version\", package_versions.user_id, package_versions.readme_path as \"readme_path: PackagePath\", package_versions.exports as \"exports: ExportsMap\", package_versions.is_yanked, package_versions.uses_npm, package_versions.meta as \"meta: PackageVersionMeta\", package_versions.updated_at, package_versions.created_at, package_versions.rekor_log_id,\n (SELECT COUNT(*)\n FROM package_versions AS pv\n WHERE pv.scope = package_versions.scope\n AND pv.name = package_versions.name\n AND pv.version > package_versions.version\n AND pv.version NOT LIKE '%-%'\n AND pv.is_yanked = false) as \"newer_versions_count!\",\n (SELECT COALESCE(SUM(dl.count), 0)\n FROM version_download_counts_24h as dl\n WHERE dl.scope = package_versions.scope\n AND dl.package = package_versions.name\n AND dl.version = package_versions.version) as \"lifetime_download_count!\"\n FROM package_versions\n JOIN packages ON packages.scope = package_versions.scope AND packages.name = package_versions.name\n WHERE NOT packages.is_archived\n ORDER BY package_versions.created_at DESC\n LIMIT 10", + "query": "SELECT package_versions.scope as \"scope: ScopeName\", package_versions.name as \"name: PackageName\", package_versions.version as \"version: Version\", package_versions.user_id, package_versions.readme_path as \"readme_path: PackagePath\", package_versions.exports as \"exports: ExportsMap\", package_versions.is_yanked, package_versions.uses_npm, package_versions.meta as \"meta: PackageVersionMeta\", package_versions.updated_at, package_versions.created_at, package_versions.rekor_log_id,\n (SELECT COUNT(*)\n FROM package_versions AS pv\n WHERE pv.scope = package_versions.scope\n AND pv.name = package_versions.name\n AND pv.version > package_versions.version\n AND pv.version NOT LIKE '%-%'\n AND pv.is_yanked = false) as \"newer_versions_count!\",\n (SELECT COALESCE(SUM(dl.count), 0)\n FROM version_download_counts_24h as dl\n WHERE dl.scope = package_versions.scope\n AND dl.package = package_versions.name\n AND dl.version = package_versions.version) as \"lifetime_download_count!\"\n FROM package_versions\n JOIN packages ON packages.scope = package_versions.scope AND packages.name = package_versions.name\n WHERE NOT packages.is_archived AND NOT packages.is_private\n ORDER BY package_versions.created_at DESC\n LIMIT 10", "describe": { "columns": [ { @@ -94,5 +94,5 @@ null ] }, - "hash": "ae936fd4921cd295d2b577c20976f56a46a140f78eee41f4dadbddec40c8852b" + "hash": "ba22535d224791f09049bb039d4596be21408a971c6c422f072c23ce3c64dfcf" } diff --git a/api/.sqlx/query-1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b.json b/api/.sqlx/query-bcd2d6abf8d3ff95b1dc96d68db1ea9f49cd312dcf6d4a891d9dbdb46d7ffbbc.json similarity index 93% rename from api/.sqlx/query-1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b.json rename to api/.sqlx/query-bcd2d6abf8d3ff95b1dc96d68db1ea9f49cd312dcf6d4a891d9dbdb46d7ffbbc.json index 52cdfb1cc..dc3138bb0 100644 --- a/api/.sqlx/query-1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b.json +++ b/api/.sqlx/query-bcd2d6abf8d3ff95b1dc96d68db1ea9f49cd312dcf6d4a891d9dbdb46d7ffbbc.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat as \"package_runtime_compat: RuntimeCompat\", packages.readme_source as \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.is_private \"package_is_private\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE packages.scope = $1 AND ($2 = true OR packages.is_archived = false)\n ORDER BY packages.is_archived ASC, packages.name\n OFFSET $3 LIMIT $4", + "query": "SELECT packages.scope \"package_scope: ScopeName\", packages.name \"package_name: PackageName\", packages.description \"package_description\", packages.github_repository_id \"package_github_repository_id\", packages.runtime_compat as \"package_runtime_compat: RuntimeCompat\", packages.readme_source as \"package_readme_source: ReadmeSource\", packages.when_featured \"package_when_featured\", packages.is_archived \"package_is_archived\", packages.is_private \"package_is_private\", packages.updated_at \"package_updated_at\", packages.created_at \"package_created_at\",\n (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as \"package_version_count!\",\n (SELECT version FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_latest_version\",\n (SELECT meta FROM package_versions WHERE scope = packages.scope AND name = packages.name AND version NOT LIKE '%-%' AND is_yanked = false ORDER BY version DESC LIMIT 1) as \"package_version_meta: PackageVersionMeta\",\n github_repositories.id \"github_repository_id?\", github_repositories.owner \"github_repository_owner?\", github_repositories.name \"github_repository_name?\", github_repositories.updated_at \"github_repository_updated_at?\", github_repositories.created_at \"github_repository_created_at?\"\n FROM packages\n LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id\n WHERE packages.scope = $1 AND ($2 = true OR packages.is_archived = false) AND ($5 = true OR packages.is_private = false)\n ORDER BY packages.is_archived ASC, packages.name\n OFFSET $3 LIMIT $4", "describe": { "columns": [ { @@ -114,7 +114,8 @@ "Text", "Bool", "Int8", - "Int8" + "Int8", + "Bool" ] }, "nullable": [ @@ -139,5 +140,5 @@ false ] }, - "hash": "1830dcd1569cc8a67768bcdd8170199464a66b69f4b7c31a90957c9aa4f9d58b" + "hash": "bcd2d6abf8d3ff95b1dc96d68db1ea9f49cd312dcf6d4a891d9dbdb46d7ffbbc" } diff --git a/api/src/api/package.rs b/api/src/api/package.rs index 28b791dcb..a58bb9197 100644 --- a/api/src/api/package.rs +++ b/api/src/api/package.rs @@ -269,8 +269,15 @@ pub async fn list_handler( let iam = req.iam(); let can_see_archived = iam.check_scope_admin_access(&scope).await.is_ok(); + let can_see_private = iam.is_scope_member(&scope).await; let (total, packages) = db - .list_packages_by_scope(&scope, can_see_archived, start, limit) + .list_packages_by_scope( + &scope, + can_see_archived, + can_see_private, + start, + limit, + ) .await?; Ok(ApiList { @@ -344,6 +351,13 @@ pub async fn get_handler(req: Request) -> ApiResult { .await? .ok_or(ApiError::PackageNotFound)?; + if res_package.0.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let mut api_package = ApiPackage::from(res_package); if let Some(latest_v) = &api_package.latest_version { @@ -678,10 +692,18 @@ pub async fn list_versions_handler( let db = req.data::().unwrap(); - db.get_package(&scope, &package) + let (pkg, _, _) = db + .get_package(&scope, &package) .await? .ok_or(ApiError::PackageNotFound)?; + if pkg.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let versions = db .list_package_versions(&scope, &package) .await? @@ -746,11 +768,18 @@ pub async fn get_version_handler( Span::current().record("version", field::display(&version)); let db = req.data::().unwrap(); - let _ = db + let (pkg, _, _) = db .get_package(&scope, &package) .await? .ok_or(ApiError::PackageNotFound)?; + if pkg.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let maybe_version = match version { VersionOrLatest::Version(version) => { db.get_package_version(&scope, &package, &version).await? @@ -1199,6 +1228,13 @@ pub async fn get_docs_handler( .await? .ok_or(ApiError::PackageNotFound)?; + if package.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let maybe_version = match &version_or_latest { VersionOrLatest::Version(version) => { db.get_package_version(&scope, &package_name, version) @@ -1330,6 +1366,13 @@ pub async fn get_docs_search_handler( .await? .ok_or(ApiError::PackageNotFound)?; + if package.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let maybe_version = match &version_or_latest { VersionOrLatest::Version(version) => { db.get_package_version(&scope, &package_name, version) @@ -1401,6 +1444,13 @@ pub async fn get_docs_search_html_handler( .await? .ok_or(ApiError::PackageNotFound)?; + if package.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let maybe_version = match &version_or_latest { VersionOrLatest::Version(version) => { db.get_package_version(&scope, &package_name, version) @@ -1480,11 +1530,18 @@ pub async fn get_source_handler( let db = req.data::().unwrap(); let buckets = req.data::().unwrap(); - let _ = db + let (pkg, _, _) = db .get_package(&scope, &package) .await? .ok_or(ApiError::PackageNotFound)?; + if pkg.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let maybe_version = match &version_or_latest { VersionOrLatest::Version(version) => { db.get_package_version(&scope, &package, version).await? @@ -1641,10 +1698,18 @@ pub async fn list_dependents_handler( .clamp(1, 10); let db = req.data::().unwrap(); - db.get_package(&scope, &package) + let (pkg, _, _) = db + .get_package(&scope, &package) .await? .ok_or(ApiError::PackageNotFound)?; + if pkg.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let (total, deps) = db .list_package_dependents( crate::db::DependencyKind::Jsr, @@ -1677,10 +1742,18 @@ pub async fn get_downloads_handler( Span::current().record("package", field::display(&package)); let db = req.data::().unwrap(); - db.get_package(&scope, &package) + let (pkg, _, _) = db + .get_package(&scope, &package) .await? .ok_or(ApiError::PackageNotFound)?; + if pkg.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let current = Utc::now(); let start = current - chrono::Duration::days(90); @@ -1754,6 +1827,17 @@ pub async fn list_dependencies_handler( let db = req.data::().unwrap(); + let (pkg, _, _) = db + .get_package(&scope, &package) + .await? + .ok_or(ApiError::PackageNotFound)?; + if pkg.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + db.get_package_version(&scope, &package, &version) .await? .ok_or(ApiError::PackageVersionNotFound)?; @@ -2307,6 +2391,19 @@ pub async fn get_dependencies_graph_handler( Span::current().record("package", field::display(&package)); Span::current().record("version", field::display(&version)); + let db = req.data::().unwrap(); + + let (pkg, _, _) = db + .get_package(&scope, &package) + .await? + .ok_or(ApiError::PackageNotFound)?; + if pkg.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + let buckets = req.data::().unwrap().clone(); let gcs_path = crate::gcs_paths::version_metadata(&scope, &package, &version).into(); @@ -2392,6 +2489,13 @@ pub async fn get_score_handler( .await? .ok_or(ApiError::PackageNotFound)?; + if pkg.is_private { + let iam = req.iam(); + if !iam.is_scope_member(&scope).await { + return Err(ApiError::PackageNotFound); + } + } + Ok(ApiPackageScore::from((&meta, &pkg))) } diff --git a/api/src/api/self_user.rs b/api/src/api/self_user.rs index 7071d5796..399d105f9 100644 --- a/api/src/api/self_user.rs +++ b/api/src/api/self_user.rs @@ -241,6 +241,9 @@ async fn create_token( if let Some(email_sender) = email_sender { let permissions = if let Some(permissions) = &token.permissions { match &permissions.0[0] { + Permission::PackagePublish(PackagePublishPermission::Account) => { + Cow::Borrowed("Publish new versions to any package in any scope") + } Permission::PackagePublish(PackagePublishPermission::Scope { scope, }) => Cow::Owned(format!( diff --git a/api/src/db/database.rs b/api/src/db/database.rs index c9ae315a1..9664abeff 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -1529,6 +1529,7 @@ impl Database { &self, scope: &ScopeName, show_archived: bool, + show_private: bool, start: i64, limit: i64, ) -> Result<(usize, Vec)> { @@ -1542,13 +1543,14 @@ impl Database { github_repositories.id "github_repository_id?", github_repositories.owner "github_repository_owner?", github_repositories.name "github_repository_name?", github_repositories.updated_at "github_repository_updated_at?", github_repositories.created_at "github_repository_created_at?" FROM packages LEFT JOIN github_repositories ON packages.github_repository_id = github_repositories.id - WHERE packages.scope = $1 AND ($2 = true OR packages.is_archived = false) + WHERE packages.scope = $1 AND ($2 = true OR packages.is_archived = false) AND ($5 = true OR packages.is_private = false) ORDER BY packages.is_archived ASC, packages.name OFFSET $3 LIMIT $4"#, scope as _, show_archived, start, - limit + limit, + show_private, ) .map(|r| { let package = Package { @@ -1586,9 +1588,10 @@ impl Database { .await?; let total_packages = sqlx::query!( - r#"SELECT COUNT(created_at) FROM packages WHERE scope = $1 AND ($2 = true OR packages.is_archived = false);"#, + r#"SELECT COUNT(created_at) FROM packages WHERE scope = $1 AND ($2 = true OR packages.is_archived = false) AND ($3 = true OR packages.is_private = false);"#, scope as _, show_archived, + show_private, ) .map(|r| r.count.unwrap()) .fetch_one(&mut *tx) @@ -1787,7 +1790,7 @@ impl Database { AND dl.version = package_versions.version) as "lifetime_download_count!" FROM package_versions JOIN packages ON packages.scope = package_versions.scope AND packages.name = package_versions.name - WHERE NOT packages.is_archived + WHERE NOT packages.is_archived AND NOT packages.is_private ORDER BY package_versions.created_at DESC LIMIT 10"#, ) diff --git a/api/src/db/models.rs b/api/src/db/models.rs index 670b220c8..c99a3ae8d 100644 --- a/api/src/db/models.rs +++ b/api/src/db/models.rs @@ -647,6 +647,8 @@ pub enum PackagePublishPermission { }, #[serde(rename_all = "camelCase")] Scope { scope: ScopeName }, + #[serde(rename_all = "camelCase")] + Account, } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/api/src/iam.rs b/api/src/iam.rs index 1342c40e2..2a03b754d 100644 --- a/api/src/iam.rs +++ b/api/src/iam.rs @@ -97,6 +97,20 @@ impl<'s> IamHandler<'s> { } } + /// Returns true if the current user is a scope member (or staff using sudo). + /// Returns false for anonymous users or non-members. + pub async fn is_scope_member(&self, scope: &ScopeName) -> bool { + match &self.principal { + Principal::User(user) => { + if user.is_staff && self.sudo { + return true; + } + self.db.get_scope_member(scope, user.id).await.ok().flatten().is_some() + } + Principal::GitHubActions { .. } | Principal::Anonymous => false, + } + } + pub async fn check_scope_member_delete_access( &self, scope: &ScopeName, diff --git a/deno.lock b/deno.lock index 1526b1974..a2d61fa93 100644 --- a/deno.lock +++ b/deno.lock @@ -7,6 +7,9 @@ "jsr:@deno/graph@0.86": "0.86.9", "jsr:@deno/graph@~0.82.3": "0.82.3", "jsr:@denosaurs/emoji@0.3": "0.3.1", + "jsr:@foo/bar@*": "0.0.2", + "jsr:@scope/foo@*": "1.2.3", + "jsr:@std/assert@^1.0.17": "1.0.17", "jsr:@std/async@^1.0.8": "1.0.9", "jsr:@std/bytes@^1.0.5": "1.0.6", "jsr:@std/bytes@^1.0.6": "1.0.6", @@ -18,6 +21,7 @@ "jsr:@std/fmt@^1.0.8": "1.0.8", "jsr:@std/front-matter@^1.0.5": "1.0.5", "jsr:@std/fs@^1.0.6": "1.0.19", + "jsr:@std/internal@^1.0.12": "1.0.12", "jsr:@std/io@0.225": "0.225.2", "jsr:@std/path@^1.0.8": "1.0.8", "jsr:@std/toml@^1.0.1": "1.0.2", @@ -75,6 +79,21 @@ "@denosaurs/emoji@0.3.1": { "integrity": "b0aed5f55dec99e83da7c9637fe0a36d1d6252b7c99deaaa3fc5dea3fcf3da8b" }, + "@foo/bar@0.0.2": { + "integrity": "73a5cd2c7a0de2d0cf85429e177482dc5f215ce797aaed6aa9bb2ec339268455", + "dependencies": [ + "jsr:@std/assert" + ] + }, + "@scope/foo@1.2.3": { + "integrity": "aaf76d364e8f7ffc416c76c971772be87388eef8c5407dd50dbd96d6dc1d6ec6" + }, + "@std/assert@1.0.17": { + "integrity": "df5ebfffe77c03b3fa1401e11c762cc8f603d51021c56c4d15a8c7ab45e90dbe", + "dependencies": [ + "jsr:@std/internal" + ] + }, "@std/async@1.0.9": { "integrity": "c6472fd0623b3f3daae023cdf7ca5535e1b721dfbf376562c0c12b3fb4867f91" }, @@ -106,6 +125,9 @@ "@std/fs@1.0.19": { "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06" }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, "@std/io@0.225.2": { "integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7", "dependencies": [ diff --git a/frontend/components/PackageHit.tsx b/frontend/components/PackageHit.tsx index 00c111139..0067394a7 100644 --- a/frontend/components/PackageHit.tsx +++ b/frontend/components/PackageHit.tsx @@ -4,7 +4,8 @@ import type { Package, RuntimeCompat } from "../utils/api_types.ts"; import { getScoreBgColorClass } from "../utils/score_ring_color.ts"; import type { ListDisplayItem } from "./List.tsx"; import { RuntimeCompatIndicator } from "./RuntimeCompatIndicator.tsx"; -import TbArchive from "tb-icons/TbArchive"; +import TbArchiveFilled from "tb-icons/TbArchiveFilled"; +import TbLockFilled from "tb-icons/TbLockFilled"; const runtimeCompatExists = (compat: RuntimeCompat) => { return compat?.browser || compat?.deno || compat?.node || compat?.workerd || @@ -28,10 +29,16 @@ export function PackageHit(pkg: OramaPackageHit | Package): ListDisplayItem { )} {(pkg as Package).isArchived && (
- + Archived
)} + {(pkg as Package).isPrivate && ( +
+ + Private +
+ )}
{pkg.description} diff --git a/frontend/routes/account/tokens/(_islands)/CreateToken.tsx b/frontend/routes/account/tokens/(_islands)/CreateToken.tsx index 045fc94ae..f0ce138b7 100644 --- a/frontend/routes/account/tokens/(_islands)/CreateToken.tsx +++ b/frontend/routes/account/tokens/(_islands)/CreateToken.tsx @@ -1,4 +1,5 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. +import type { ComponentChildren } from "preact"; import { useCallback, useEffect, useRef } from "preact/hooks"; import { Signal, useComputed, useSignal } from "@preact/signals"; import { IS_BROWSER } from "fresh/runtime"; @@ -10,7 +11,21 @@ import { CreatedToken, Permission } from "../../../../utils/api_types.ts"; import { ErrorDisplay } from "../../../../components/ErrorDisplay.tsx"; export function CreateToken() { - const usage = useSignal<"publish" | "api" | null>(null); + const usage = useSignal<"publish" | "read" | "api" | null>(null); + + switch (usage.value) { + case null: + return ; + case "publish": + return ; + case "read": + return ; + case "api": + return ; + } +} + +function PublishTokenFlow() { const env = useSignal< "development" | "github_actions" | "other_ci_service" | null >(null); @@ -18,38 +33,77 @@ export function CreateToken() { const willStoreSafely = useSignal(false); const willBeSafe = useSignal(false); - if (usage.value === null) { - return ; - } - - if (usage.value === "publish" && env.value === null) { + if (env.value === null) { return ; } - if ( - usage.value === "publish" && env.value === "development" && - !localMachineAnyway.value - ) { + if (env.value === "development" && !localMachineAnyway.value) { return ; } - if (usage.value === "publish" && env.value === "github_actions") { + if (env.value === "github_actions") { return ; } - if ( - !willStoreSafely.value && - (usage.value === "api" || - (usage.value === "publish" && env.value !== "other_ci_service")) - ) { - return ; + if (!willStoreSafely.value && env.value !== "other_ci_service") { + return ( + + Personal access tokens enable a malicious user to impersonate you and + perform any action you can on JSR,{" "} + + including publishing new versions of your packages + . + + ); } if (!willBeSafe.value) { return ; } - return ; + return ; +} + +function ReadTokenFlow() { + const willStoreSafely = useSignal(false); + const willBeSafe = useSignal(false); + + if (!willStoreSafely.value) { + return ( + + foo + + ); + } + + if (!willBeSafe.value) { + return ; + } + + return ; +} + +function ApiTokenFlow() { + const willStoreSafely = useSignal(false); + const willBeSafe = useSignal(false); + + if (!willStoreSafely.value) { + return ( + + Personal access tokens enable a malicious user to impersonate you and + perform any action you can on JSR,{" "} + + including publishing new versions of your packages + . + + ); + } + + if (!willBeSafe.value) { + return ; + } + + return ; } function useRadioGroup( @@ -84,7 +138,9 @@ function useRadioGroup( return { ref, disabled, onSubmit, onInput }; } -function ChooseUsage({ usage }: { usage: Signal<"publish" | "api" | null> }) { +function ChooseUsage( + { usage }: { usage: Signal<"publish" | "read" | "api" | null> }, +) { const { ref, disabled, onSubmit, onInput } = useRadioGroup("path", usage); return ( @@ -97,6 +153,10 @@ function ChooseUsage({ usage }: { usage: Signal<"publish" | "api" | null> }) { Publish packages +