From 5caa86f2649bcf27c64a9d7e9145485e529de94d Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Wed, 2 Apr 2025 15:55:23 +0800 Subject: [PATCH 1/7] feat: changes query --- .../20250402143550_create_changes.sql | 16 +++++++ api/src/api/changes.rs | 44 +++++++++++++++++++ api/src/api/mod.rs | 4 ++ api/src/db/database.rs | 27 ++++++++++++ api/src/db/models.rs | 34 ++++++++++++++ api/src/util.rs | 16 ++++--- 6 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 api/migrations/20250402143550_create_changes.sql create mode 100644 api/src/api/changes.rs diff --git a/api/migrations/20250402143550_create_changes.sql b/api/migrations/20250402143550_create_changes.sql new file mode 100644 index 00000000..b45e70fb --- /dev/null +++ b/api/migrations/20250402143550_create_changes.sql @@ -0,0 +1,16 @@ +CREATE TYPE change_type AS ENUM ( + 'PACKAGE_VERSION_ADDED', + 'PACKAGE_TAG_ADDED' +); + +CREATE TABLE changes ( + seq BIGSERIAL PRIMARY KEY, + change_type change_type NOT NULL, + package_id VARCHAR(255) NOT NULL, + data TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 创建索引 +CREATE INDEX changes_package_id_idx ON changes (package_id); +CREATE INDEX changes_created_at_idx ON changes (created_at); diff --git a/api/src/api/changes.rs b/api/src/api/changes.rs new file mode 100644 index 00000000..da7dc300 --- /dev/null +++ b/api/src/api/changes.rs @@ -0,0 +1,44 @@ +use crate::api::ApiError; +use hyper::{Body, Request}; +use routerify::Router; +use routerify::prelude::*; +use serde::Serialize; + +use crate::{ + db::{Change, Database}, + util::{self, pagination, ApiResult}, +}; + + +#[derive(Serialize)] +pub struct ApiChange { + pub seq: i64, + pub r#type: String, + pub id: String, + pub changes: serde_json::Value, +} + +impl From for ApiChange { + fn from(change: Change) -> Self { + Self { + seq: change.seq, + r#type: change.change_type.to_string(), + id: change.package_id, + changes: serde_json::from_str(&change.data).unwrap(), + } + } +} + +pub fn changes_router() -> Router { + Router::builder() + .get("/_changes", util::json(list_changes)) + .build() + .unwrap() +} + +async fn list_changes(req: Request) -> ApiResult> { + let db = req.data::().unwrap(); + let (start, limit) = pagination(&req); + let changes = db.list_changes(start, limit).await?; + Ok(changes.into_iter().map(ApiChange::from).collect()) +} diff --git a/api/src/api/mod.rs b/api/src/api/mod.rs index 4c0d206d..15e535ea 100644 --- a/api/src/api/mod.rs +++ b/api/src/api/mod.rs @@ -8,6 +8,8 @@ mod scope; mod self_user; mod types; mod users; +mod changes; + use hyper::Body; use hyper::Response; @@ -27,6 +29,7 @@ use self::admin::admin_router; use self::authorization::authorization_router; use self::scope::scope_router; use self::users::users_router; +use self::changes::changes_router; use crate::util; use crate::util::CacheDuration; @@ -43,6 +46,7 @@ pub fn api_router() -> Router { .middleware(Middleware::pre(util::auth_middleware)) .scope("/admin", admin_router()) .scope("/scopes", scope_router()) + .scope("/changes", changes_router()) .scope("/user", self_user_router()) .scope("/users", users_router()) .scope("/authorizations", authorization_router()) diff --git a/api/src/db/database.rs b/api/src/db/database.rs index 06af6a26..19f1a164 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -56,6 +56,33 @@ impl Database { .await } + #[instrument(name = "Database::list_changes", skip(self), err)] + pub async fn list_changes( + &self, + since: i64, + limit: i64, + ) -> Result> { + sqlx::query_as!( + Change, + r#" + SELECT + seq, + change_type as "change_type: ChangeType", + package_id, + data, + created_at + FROM changes + WHERE seq > $1 + ORDER BY seq ASC + LIMIT $2 + "#, + since, + limit + ) + .fetch_all(&self.pool) + .await + } + #[instrument(name = "Database::get_user_public", skip(self), err)] pub async fn get_user_public(&self, id: Uuid) -> Result> { sqlx::query_as!( diff --git a/api/src/db/models.rs b/api/src/db/models.rs index 5f179c1f..8df38652 100644 --- a/api/src/db/models.rs +++ b/api/src/db/models.rs @@ -824,3 +824,37 @@ impl sqlx::postgres::PgHasArrayType for DownloadKind { sqlx::postgres::PgTypeInfo::with_name("_download_kind") } } + +#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)] +#[sqlx(type_name = "change_type", rename_all = "SCREAMING_SNAKE_CASE")] +#[serde(rename_all = "snake_case")] +pub enum ChangeType { + + PackageVersionAdded, + PackageTagAdded, +} + +impl std::fmt::Display for ChangeType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::PackageVersionAdded => write!(f, "PACKAGE_VERSION_ADDED"), + Self::PackageTagAdded => write!(f, "PACKAGE_TAG_ADDED"), + } + } +} + +#[derive(Debug, Clone)] +pub struct Change { + pub seq: i64, + pub change_type: ChangeType, + pub package_id: String, + pub data: String, + pub created_at: DateTime, +} + +#[derive(Debug)] +pub struct NewChange<'s> { + pub change_type: ChangeType, + pub package_id: &'s str, + pub data: &'s str, +} diff --git a/api/src/util.rs b/api/src/util.rs index 1c56088f..19e24c27 100644 --- a/api/src/util.rs +++ b/api/src/util.rs @@ -283,13 +283,17 @@ pub fn pagination(req: &Request) -> (i64, i64) { .and_then(|page| page.parse::().ok()) .unwrap_or(100) .clamp(1, 100); - let page = req - .query("page") - .and_then(|page| page.parse::().ok()) - .unwrap_or(1) - .max(1); - let start = (page * limit) - limit; + let start = if let Some(since) = req.query("since").and_then(|s| s.parse::().ok()) { + since + } else { + let page = req + .query("page") + .and_then(|page| page.parse::().ok()) + .unwrap_or(1) + .max(1); + (page * limit) - limit + }; (start, limit) } From 4502b08149e8d7b4c6a00f9b1298374259845120 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Wed, 2 Apr 2025 15:55:31 +0800 Subject: [PATCH 2/7] feat: create change --- .../20250402143550_create_changes.sql | 1 - api/src/api/changes.rs | 13 ++------- api/src/api/mod.rs | 4 +-- api/src/db/database.rs | 29 +++++++++++++++++++ api/src/publish.rs | 20 +++++++++++++ 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/api/migrations/20250402143550_create_changes.sql b/api/migrations/20250402143550_create_changes.sql index b45e70fb..1265a53e 100644 --- a/api/migrations/20250402143550_create_changes.sql +++ b/api/migrations/20250402143550_create_changes.sql @@ -11,6 +11,5 @@ CREATE TABLE changes ( created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); --- 创建索引 CREATE INDEX changes_package_id_idx ON changes (package_id); CREATE INDEX changes_created_at_idx ON changes (created_at); diff --git a/api/src/api/changes.rs b/api/src/api/changes.rs index da7dc300..0dfceebd 100644 --- a/api/src/api/changes.rs +++ b/api/src/api/changes.rs @@ -1,12 +1,10 @@ -use crate::api::ApiError; use hyper::{Body, Request}; -use routerify::Router; use routerify::prelude::*; use serde::Serialize; use crate::{ db::{Change, Database}, - util::{self, pagination, ApiResult}, + util::{pagination, ApiResult}, }; @@ -29,14 +27,7 @@ impl From for ApiChange { } } -pub fn changes_router() -> Router { - Router::builder() - .get("/_changes", util::json(list_changes)) - .build() - .unwrap() -} - -async fn list_changes(req: Request) -> ApiResult> { +pub async fn list_changes(req: Request) -> ApiResult> { let db = req.data::().unwrap(); let (start, limit) = pagination(&req); let changes = db.list_changes(start, limit).await?; diff --git a/api/src/api/mod.rs b/api/src/api/mod.rs index 15e535ea..146da77c 100644 --- a/api/src/api/mod.rs +++ b/api/src/api/mod.rs @@ -16,6 +16,7 @@ use hyper::Response; use package::global_list_handler; use package::global_metrics_handler; use package::global_stats_handler; +use changes::list_changes; use routerify::Middleware; use routerify::Router; @@ -29,7 +30,6 @@ use self::admin::admin_router; use self::authorization::authorization_router; use self::scope::scope_router; use self::users::users_router; -use self::changes::changes_router; use crate::util; use crate::util::CacheDuration; @@ -43,10 +43,10 @@ pub fn api_router() -> Router { util::json(global_metrics_handler), ), ) + .get("/_changes", util::json(list_changes)) .middleware(Middleware::pre(util::auth_middleware)) .scope("/admin", admin_router()) .scope("/scopes", scope_router()) - .scope("/changes", changes_router()) .scope("/user", self_user_router()) .scope("/users", users_router()) .scope("/authorizations", authorization_router()) diff --git a/api/src/db/database.rs b/api/src/db/database.rs index 19f1a164..c5dade6a 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -83,6 +83,35 @@ impl Database { .await } + + #[instrument(name = "Database::create_change", skip(self), err)] + pub async fn create_change( + &self, + change_type: ChangeType, + package_id: String, + data: serde_json::Value, + ) -> Result { + sqlx::query_as!( + Change, + r#" + INSERT INTO changes (change_type, package_id, data) + VALUES ($1, $2, $3) + RETURNING + seq, + change_type as "change_type: ChangeType", + package_id, + data, + created_at + "#, + change_type as _, + package_id, + data.to_string() + ) + .fetch_one(&self.pool) + .await + } + + #[instrument(name = "Database::get_user_public", skip(self), err)] pub async fn get_user_public(&self, id: Uuid) -> Result> { sqlx::query_as!( diff --git a/api/src/publish.rs b/api/src/publish.rs index 85118fd9..c0d7f77e 100644 --- a/api/src/publish.rs +++ b/api/src/publish.rs @@ -5,6 +5,7 @@ use std::collections::HashSet; use crate::api::ApiError; use crate::buckets::Buckets; use crate::buckets::UploadTaskBody; +use crate::db::ChangeType; use crate::db::Database; use crate::db::DependencyKind; use crate::db::ExportsMap; @@ -234,6 +235,25 @@ async fn process_publishing_task( ); } + tokio::spawn({ + let db = db.clone(); + let scope = publishing_task.package_scope.clone(); + let name = publishing_task.package_name.clone(); + let version = publishing_task.package_version.clone(); + + async move { + if let Err(e) = db.create_change( + ChangeType::PackageVersionAdded, + format!("@{}/{}", scope, name), + serde_json::json!({ + "version": version.to_string(), + }), + ).await { + error!("Failed to create change record: {}", e); + } + } + }); + Ok(()) } From 09d057136b8d37604d52b3d4d2dd15ffb7badd0f Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Wed, 2 Apr 2025 16:19:31 +0800 Subject: [PATCH 3/7] fix: lint --- api/src/api/changes.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/api/changes.rs b/api/src/api/changes.rs index 0dfceebd..b0c402e7 100644 --- a/api/src/api/changes.rs +++ b/api/src/api/changes.rs @@ -1,3 +1,4 @@ +// Copyright 2024 the JSR authors. All rights reserved. MIT license. use hyper::{Body, Request}; use routerify::prelude::*; use serde::Serialize; From 52dcee6c4753560af2f163d02840232da916a905 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Wed, 2 Apr 2025 22:17:23 +0800 Subject: [PATCH 4/7] refact: split scope & name --- ...changes.sql => 20250402220510_changes.sql} | 5 +++-- api/src/api/changes.rs | 2 +- api/src/db/database.rs | 19 +++++++++++-------- api/src/db/models.rs | 6 ++++-- api/src/publish.rs | 3 ++- 5 files changed, 21 insertions(+), 14 deletions(-) rename api/migrations/{20250402143550_create_changes.sql => 20250402220510_changes.sql} (70%) diff --git a/api/migrations/20250402143550_create_changes.sql b/api/migrations/20250402220510_changes.sql similarity index 70% rename from api/migrations/20250402143550_create_changes.sql rename to api/migrations/20250402220510_changes.sql index 1265a53e..3079232f 100644 --- a/api/migrations/20250402143550_create_changes.sql +++ b/api/migrations/20250402220510_changes.sql @@ -6,10 +6,11 @@ CREATE TYPE change_type AS ENUM ( CREATE TABLE changes ( seq BIGSERIAL PRIMARY KEY, change_type change_type NOT NULL, - package_id VARCHAR(255) NOT NULL, + scope_name text NOT NULL, + package_name text NOT NULL, data TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -CREATE INDEX changes_package_id_idx ON changes (package_id); +CREATE INDEX changes_scope_name_idx ON changes (scope_name, package_name); CREATE INDEX changes_created_at_idx ON changes (created_at); diff --git a/api/src/api/changes.rs b/api/src/api/changes.rs index b0c402e7..aeb69c43 100644 --- a/api/src/api/changes.rs +++ b/api/src/api/changes.rs @@ -22,7 +22,7 @@ impl From for ApiChange { Self { seq: change.seq, r#type: change.change_type.to_string(), - id: change.package_id, + id: format!("@jsr/{}__{}", change.scope_name, change.package_name), changes: serde_json::from_str(&change.data).unwrap(), } } diff --git a/api/src/db/database.rs b/api/src/db/database.rs index c5dade6a..8a26cd69 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -56,6 +56,7 @@ impl Database { .await } + #[instrument(name = "Database::list_changes", skip(self), err)] pub async fn list_changes( &self, @@ -68,7 +69,8 @@ impl Database { SELECT seq, change_type as "change_type: ChangeType", - package_id, + scope_name as "scope_name: ScopeName", + package_name as "package_name: PackageName", data, created_at FROM changes @@ -83,35 +85,36 @@ impl Database { .await } - #[instrument(name = "Database::create_change", skip(self), err)] pub async fn create_change( &self, change_type: ChangeType, - package_id: String, + scope_name: &ScopeName, + package_name: &PackageName, data: serde_json::Value, ) -> Result { sqlx::query_as!( Change, r#" - INSERT INTO changes (change_type, package_id, data) - VALUES ($1, $2, $3) + INSERT INTO changes (change_type, scope_name, package_name, data) + VALUES ($1, $2, $3, $4) RETURNING seq, change_type as "change_type: ChangeType", - package_id, + scope_name as "scope_name: ScopeName", + package_name as "package_name: PackageName", data, created_at "#, change_type as _, - package_id, + scope_name as _, + package_name as _, data.to_string() ) .fetch_one(&self.pool) .await } - #[instrument(name = "Database::get_user_public", skip(self), err)] pub async fn get_user_public(&self, id: Uuid) -> Result> { sqlx::query_as!( diff --git a/api/src/db/models.rs b/api/src/db/models.rs index 8df38652..693df0e6 100644 --- a/api/src/db/models.rs +++ b/api/src/db/models.rs @@ -847,7 +847,8 @@ impl std::fmt::Display for ChangeType { pub struct Change { pub seq: i64, pub change_type: ChangeType, - pub package_id: String, + pub scope_name: ScopeName, + pub package_name: PackageName, pub data: String, pub created_at: DateTime, } @@ -855,6 +856,7 @@ pub struct Change { #[derive(Debug)] pub struct NewChange<'s> { pub change_type: ChangeType, - pub package_id: &'s str, + pub scope_name: &'s ScopeName, + pub package_name: &'s PackageName, pub data: &'s str, } diff --git a/api/src/publish.rs b/api/src/publish.rs index c0d7f77e..736dc9a0 100644 --- a/api/src/publish.rs +++ b/api/src/publish.rs @@ -244,7 +244,8 @@ async fn process_publishing_task( async move { if let Err(e) = db.create_change( ChangeType::PackageVersionAdded, - format!("@{}/{}", scope, name), + &scope, + &name, serde_json::json!({ "version": version.to_string(), }), From c1e44d9e00a3db20dd3f96cbfcca12b167bf02bb Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Wed, 2 Apr 2025 22:53:34 +0800 Subject: [PATCH 5/7] feat: testcase --- api/src/api/changes.rs | 127 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 3 deletions(-) diff --git a/api/src/api/changes.rs b/api/src/api/changes.rs index aeb69c43..203bd15b 100644 --- a/api/src/api/changes.rs +++ b/api/src/api/changes.rs @@ -1,15 +1,15 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. use hyper::{Body, Request}; use routerify::prelude::*; -use serde::Serialize; +use serde::{Serialize, Deserialize}; // 添加 Deserialize +use tracing::instrument; use crate::{ db::{Change, Database}, util::{pagination, ApiResult}, }; - -#[derive(Serialize)] +#[derive(Serialize, Deserialize)] // 添加 Deserialize pub struct ApiChange { pub seq: i64, pub r#type: String, @@ -28,9 +28,130 @@ impl From for ApiChange { } } +#[instrument(name = "GET /api/_changes", skip(req), err)] pub async fn list_changes(req: Request) -> ApiResult> { let db = req.data::().unwrap(); let (start, limit) = pagination(&req); let changes = db.list_changes(start, limit).await?; Ok(changes.into_iter().map(ApiChange::from).collect()) } + + +#[cfg(test)] +mod tests { + use super::ApiChange; + use crate::ids::PackageName; + use crate::ids::ScopeName; + use crate::util::test::ApiResultExt; + use crate::util::test::TestSetup; + use crate::db::ChangeType; + use serde_json::json; + + #[tokio::test] + async fn list_empty_changes() { + let mut t = TestSetup::new().await; + + let changes = t + .http() + .get("/api/_changes") + .call() + .await + .unwrap() + .expect_ok::>() + .await; + + assert!(changes.is_empty()); + } + + #[tokio::test] + async fn list_single_change() { + let mut t = TestSetup::new().await; + + t.ephemeral_database.create_change( + ChangeType::PackageVersionAdded, + &ScopeName::new("test-scope".to_string()).unwrap(), + &PackageName::new("test-package".to_string()).unwrap(), + json!({ + "version": "1.0.0" + }), + ).await.unwrap(); + + let changes = t + .http() + .get("/api/_changes") + .call() + .await + .unwrap() + .expect_ok::>() + .await; + + assert_eq!(changes.len(), 1); + let change = &changes[0]; + assert_eq!(change.r#type, ChangeType::PackageVersionAdded.to_string()); + assert_eq!(change.id, "@jsr/test-scope__test-package"); + assert_eq!(change.changes["version"], "1.0.0"); + } + + #[tokio::test] + async fn list_changes_pagination() { + let mut t = TestSetup::new().await; + + // Create two changes + t.ephemeral_database.create_change( + ChangeType::PackageVersionAdded, + &ScopeName::new("test-scope".to_string()).unwrap(), + &PackageName::new("test-package-1".to_string()).unwrap(), + json!({ + "name": "test-package-1", + }), + ).await.unwrap(); + + t.ephemeral_database.create_change( + ChangeType::PackageVersionAdded, + &ScopeName::new("test-scope".to_string()).unwrap(), + &PackageName::new("test-package-2".to_string()).unwrap(), + json!({ + "version": "1.0.0", + }), + ).await.unwrap(); + + // Test limit parameter + let changes = t + .http() + .get("/api/_changes?limit=1&since=0") + .call() + .await + .unwrap() + .expect_ok::>() + .await; + + assert_eq!(changes.len(), 1); + assert_eq!(changes[0].id, "@jsr/test-scope__test-package-1"); + + // Test since parameter + let changes = t + .http() + .get(&format!("/api/_changes?since={}", changes[0].seq)) + .call() + .await + .unwrap() + .expect_ok::>() + .await; + + assert_eq!(changes.len(), 1); + assert_eq!(changes[0].id, "@jsr/test-scope__test-package-2"); + + // Test since + limit combination + let changes = t + .http() + .get("/api/_changes?since=0&limit=1") + .call() + .await + .unwrap() + .expect_ok::>() + .await; + + assert_eq!(changes.len(), 1); + assert_eq!(changes[0].id, "@jsr/test-scope__test-package-1"); + } +} From 5171d1e410218ea36f8ce9c4730e9a183cab15bb Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Wed, 2 Apr 2025 22:59:41 +0800 Subject: [PATCH 6/7] chore: ci --- ...bf8d6ce7cb1668ca03cf474e744faa045e1f5.json | 63 ++++++++++++++++ ...53d705b326c4ecc20c7e8b87c0a41147011be.json | 75 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 api/.sqlx/query-02a53bed1dc404067dd50b4e600bf8d6ce7cb1668ca03cf474e744faa045e1f5.json create mode 100644 api/.sqlx/query-4b34f563b276542980b3b538dca53d705b326c4ecc20c7e8b87c0a41147011be.json diff --git a/api/.sqlx/query-02a53bed1dc404067dd50b4e600bf8d6ce7cb1668ca03cf474e744faa045e1f5.json b/api/.sqlx/query-02a53bed1dc404067dd50b4e600bf8d6ce7cb1668ca03cf474e744faa045e1f5.json new file mode 100644 index 00000000..38ad5a34 --- /dev/null +++ b/api/.sqlx/query-02a53bed1dc404067dd50b4e600bf8d6ce7cb1668ca03cf474e744faa045e1f5.json @@ -0,0 +1,63 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n seq,\n change_type as \"change_type: ChangeType\",\n scope_name as \"scope_name: ScopeName\",\n package_name as \"package_name: PackageName\",\n data,\n created_at\n FROM changes\n WHERE seq > $1\n ORDER BY seq ASC\n LIMIT $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "seq", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "change_type: ChangeType", + "type_info": { + "Custom": { + "name": "change_type", + "kind": { + "Enum": [ + "PACKAGE_VERSION_ADDED", + "PACKAGE_TAG_ADDED" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "scope_name: ScopeName", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "package_name: PackageName", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "data", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Int8", + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false + ] + }, + "hash": "02a53bed1dc404067dd50b4e600bf8d6ce7cb1668ca03cf474e744faa045e1f5" +} diff --git a/api/.sqlx/query-4b34f563b276542980b3b538dca53d705b326c4ecc20c7e8b87c0a41147011be.json b/api/.sqlx/query-4b34f563b276542980b3b538dca53d705b326c4ecc20c7e8b87c0a41147011be.json new file mode 100644 index 00000000..6358e8c7 --- /dev/null +++ b/api/.sqlx/query-4b34f563b276542980b3b538dca53d705b326c4ecc20c7e8b87c0a41147011be.json @@ -0,0 +1,75 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO changes (change_type, scope_name, package_name, data)\n VALUES ($1, $2, $3, $4)\n RETURNING\n seq,\n change_type as \"change_type: ChangeType\",\n scope_name as \"scope_name: ScopeName\",\n package_name as \"package_name: PackageName\",\n data,\n created_at\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "seq", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "change_type: ChangeType", + "type_info": { + "Custom": { + "name": "change_type", + "kind": { + "Enum": [ + "PACKAGE_VERSION_ADDED", + "PACKAGE_TAG_ADDED" + ] + } + } + } + }, + { + "ordinal": 2, + "name": "scope_name: ScopeName", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "package_name: PackageName", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "data", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + { + "Custom": { + "name": "change_type", + "kind": { + "Enum": [ + "PACKAGE_VERSION_ADDED", + "PACKAGE_TAG_ADDED" + ] + } + } + }, + "Text", + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false, + false, + false + ] + }, + "hash": "4b34f563b276542980b3b538dca53d705b326c4ecc20c7e8b87c0a41147011be" +} From 850314d3bed512dc93ea24c13a7c30a01068ea3f Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Wed, 2 Apr 2025 23:17:55 +0800 Subject: [PATCH 7/7] chore: lint --- api/src/api/changes.rs | 102 ++++++++++++++++++++++------------------- api/src/api/mod.rs | 5 +- api/src/db/database.rs | 23 +++++----- api/src/db/models.rs | 35 +++++++------- api/src/publish.rs | 19 ++++---- api/src/util.rs | 4 +- 6 files changed, 99 insertions(+), 89 deletions(-) diff --git a/api/src/api/changes.rs b/api/src/api/changes.rs index 203bd15b..30cbac37 100644 --- a/api/src/api/changes.rs +++ b/api/src/api/changes.rs @@ -1,50 +1,49 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. use hyper::{Body, Request}; use routerify::prelude::*; -use serde::{Serialize, Deserialize}; // 添加 Deserialize +use serde::{Deserialize, Serialize}; use tracing::instrument; use crate::{ - db::{Change, Database}, - util::{pagination, ApiResult}, + db::{Change, Database}, + util::{pagination, ApiResult}, }; -#[derive(Serialize, Deserialize)] // 添加 Deserialize +#[derive(Serialize, Deserialize)] pub struct ApiChange { - pub seq: i64, - pub r#type: String, - pub id: String, - pub changes: serde_json::Value, + pub seq: i64, + pub r#type: String, + pub id: String, + pub changes: serde_json::Value, } impl From for ApiChange { - fn from(change: Change) -> Self { - Self { - seq: change.seq, - r#type: change.change_type.to_string(), - id: format!("@jsr/{}__{}", change.scope_name, change.package_name), - changes: serde_json::from_str(&change.data).unwrap(), - } + fn from(change: Change) -> Self { + Self { + seq: change.seq, + r#type: change.change_type.to_string(), + id: format!("@jsr/{}__{}", change.scope_name, change.package_name), + changes: serde_json::from_str(&change.data).unwrap(), } + } } #[instrument(name = "GET /api/_changes", skip(req), err)] pub async fn list_changes(req: Request) -> ApiResult> { - let db = req.data::().unwrap(); - let (start, limit) = pagination(&req); - let changes = db.list_changes(start, limit).await?; - Ok(changes.into_iter().map(ApiChange::from).collect()) + let db = req.data::().unwrap(); + let (start, limit) = pagination(&req); + let changes = db.list_changes(start, limit).await?; + Ok(changes.into_iter().map(ApiChange::from).collect()) } - #[cfg(test)] mod tests { use super::ApiChange; + use crate::db::ChangeType; use crate::ids::PackageName; use crate::ids::ScopeName; use crate::util::test::ApiResultExt; use crate::util::test::TestSetup; - use crate::db::ChangeType; use serde_json::json; #[tokio::test] @@ -67,14 +66,17 @@ mod tests { async fn list_single_change() { let mut t = TestSetup::new().await; - t.ephemeral_database.create_change( - ChangeType::PackageVersionAdded, - &ScopeName::new("test-scope".to_string()).unwrap(), - &PackageName::new("test-package".to_string()).unwrap(), - json!({ - "version": "1.0.0" - }), - ).await.unwrap(); + t.ephemeral_database + .create_change( + ChangeType::PackageVersionAdded, + &ScopeName::new("test-scope".to_string()).unwrap(), + &PackageName::new("test-package".to_string()).unwrap(), + json!({ + "version": "1.0.0" + }), + ) + .await + .unwrap(); let changes = t .http() @@ -97,23 +99,29 @@ mod tests { let mut t = TestSetup::new().await; // Create two changes - t.ephemeral_database.create_change( - ChangeType::PackageVersionAdded, - &ScopeName::new("test-scope".to_string()).unwrap(), - &PackageName::new("test-package-1".to_string()).unwrap(), - json!({ - "name": "test-package-1", - }), - ).await.unwrap(); - - t.ephemeral_database.create_change( - ChangeType::PackageVersionAdded, - &ScopeName::new("test-scope".to_string()).unwrap(), - &PackageName::new("test-package-2".to_string()).unwrap(), - json!({ - "version": "1.0.0", - }), - ).await.unwrap(); + t.ephemeral_database + .create_change( + ChangeType::PackageVersionAdded, + &ScopeName::new("test-scope".to_string()).unwrap(), + &PackageName::new("test-package-1".to_string()).unwrap(), + json!({ + "name": "test-package-1", + }), + ) + .await + .unwrap(); + + t.ephemeral_database + .create_change( + ChangeType::PackageVersionAdded, + &ScopeName::new("test-scope".to_string()).unwrap(), + &PackageName::new("test-package-2".to_string()).unwrap(), + json!({ + "version": "1.0.0", + }), + ) + .await + .unwrap(); // Test limit parameter let changes = t @@ -131,7 +139,7 @@ mod tests { // Test since parameter let changes = t .http() - .get(&format!("/api/_changes?since={}", changes[0].seq)) + .get(format!("/api/_changes?since={}", changes[0].seq)) .call() .await .unwrap() diff --git a/api/src/api/mod.rs b/api/src/api/mod.rs index 146da77c..667c6542 100644 --- a/api/src/api/mod.rs +++ b/api/src/api/mod.rs @@ -1,6 +1,7 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. mod admin; mod authorization; +mod changes; mod errors; mod package; mod publishing_task; @@ -8,15 +9,13 @@ mod scope; mod self_user; mod types; mod users; -mod changes; - +use changes::list_changes; use hyper::Body; use hyper::Response; use package::global_list_handler; use package::global_metrics_handler; use package::global_stats_handler; -use changes::list_changes; use routerify::Middleware; use routerify::Router; diff --git a/api/src/db/database.rs b/api/src/db/database.rs index 8a26cd69..b765f345 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -56,16 +56,15 @@ impl Database { .await } - #[instrument(name = "Database::list_changes", skip(self), err)] pub async fn list_changes( - &self, - since: i64, - limit: i64, + &self, + since: i64, + limit: i64, ) -> Result> { - sqlx::query_as!( - Change, - r#" + sqlx::query_as!( + Change, + r#" SELECT seq, change_type as "change_type: ChangeType", @@ -78,11 +77,11 @@ impl Database { ORDER BY seq ASC LIMIT $2 "#, - since, - limit - ) - .fetch_all(&self.pool) - .await + since, + limit + ) + .fetch_all(&self.pool) + .await } #[instrument(name = "Database::create_change", skip(self), err)] diff --git a/api/src/db/models.rs b/api/src/db/models.rs index 693df0e6..9095d17b 100644 --- a/api/src/db/models.rs +++ b/api/src/db/models.rs @@ -829,34 +829,33 @@ impl sqlx::postgres::PgHasArrayType for DownloadKind { #[sqlx(type_name = "change_type", rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "snake_case")] pub enum ChangeType { - - PackageVersionAdded, - PackageTagAdded, + PackageVersionAdded, + PackageTagAdded, } impl std::fmt::Display for ChangeType { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::PackageVersionAdded => write!(f, "PACKAGE_VERSION_ADDED"), - Self::PackageTagAdded => write!(f, "PACKAGE_TAG_ADDED"), - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::PackageVersionAdded => write!(f, "PACKAGE_VERSION_ADDED"), + Self::PackageTagAdded => write!(f, "PACKAGE_TAG_ADDED"), } + } } #[derive(Debug, Clone)] pub struct Change { - pub seq: i64, - pub change_type: ChangeType, - pub scope_name: ScopeName, - pub package_name: PackageName, - pub data: String, - pub created_at: DateTime, + pub seq: i64, + pub change_type: ChangeType, + pub scope_name: ScopeName, + pub package_name: PackageName, + pub data: String, + pub created_at: DateTime, } #[derive(Debug)] pub struct NewChange<'s> { - pub change_type: ChangeType, - pub scope_name: &'s ScopeName, - pub package_name: &'s PackageName, - pub data: &'s str, + pub change_type: ChangeType, + pub scope_name: &'s ScopeName, + pub package_name: &'s PackageName, + pub data: &'s str, } diff --git a/api/src/publish.rs b/api/src/publish.rs index 736dc9a0..2ec09e83 100644 --- a/api/src/publish.rs +++ b/api/src/publish.rs @@ -242,14 +242,17 @@ async fn process_publishing_task( let version = publishing_task.package_version.clone(); async move { - if let Err(e) = db.create_change( - ChangeType::PackageVersionAdded, - &scope, - &name, - serde_json::json!({ - "version": version.to_string(), - }), - ).await { + if let Err(e) = db + .create_change( + ChangeType::PackageVersionAdded, + &scope, + &name, + serde_json::json!({ + "version": version.to_string(), + }), + ) + .await + { error!("Failed to create change record: {}", e); } } diff --git a/api/src/util.rs b/api/src/util.rs index 19e24c27..3277d1c8 100644 --- a/api/src/util.rs +++ b/api/src/util.rs @@ -284,7 +284,9 @@ pub fn pagination(req: &Request) -> (i64, i64) { .unwrap_or(100) .clamp(1, 100); - let start = if let Some(since) = req.query("since").and_then(|s| s.parse::().ok()) { + let start = if let Some(since) = + req.query("since").and_then(|s| s.parse::().ok()) + { since } else { let page = req