From a24deab3566043aef928dce8838a90cd684b721a Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Mon, 10 Feb 2025 21:32:56 +0100 Subject: [PATCH 1/4] feat(user page): Recently published --- ...b79d8d22a7868b9da6896b068e0cc0fa1298d.json | 82 +++++++++++++++++++ api/src/api/users.rs | 24 ++++++ api/src/db/database.rs | 33 ++++++++ frontend/routes/user/[id].tsx | 36 +++++--- 4 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json diff --git a/api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json b/api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json new file mode 100644 index 000000000..37ccfc0f2 --- /dev/null +++ b/api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json @@ -0,0 +1,82 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT DISTINCT ON (packages.scope, packages.name)\n packages.scope as \"scope: ScopeName\",\n packages.name as \"name: PackageName\",\n packages.description,\n packages.github_repository_id,\n packages.runtime_compat as \"runtime_compat: RuntimeCompat\",\n packages.when_featured,\n packages.is_archived,\n packages.updated_at,\n packages.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 FROM packages\n JOIN scope_members ON packages.scope = scope_members.scope\n WHERE scope_members.user_id = $1\n ORDER BY packages.scope, packages.name, packages.created_at DESC\n LIMIT 10;\n ", + "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": "when_featured", + "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "is_archived", + "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "updated_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 8, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 9, + "name": "version_count!", + "type_info": "Int8" + }, + { + "ordinal": 10, + "name": "latest_version", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + true, + false, + false, + false, + null, + null + ] + }, + "hash": "b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d" +} diff --git a/api/src/api/users.rs b/api/src/api/users.rs index 975cfb4a6..3b394168a 100644 --- a/api/src/api/users.rs +++ b/api/src/api/users.rs @@ -13,6 +13,7 @@ use crate::util::ApiResult; use crate::util::RequestIdExt; use super::ApiError; +use super::ApiPackage; use super::ApiScope; use super::ApiUser; @@ -20,6 +21,7 @@ pub fn users_router() -> Router { Router::builder() .get("/:id", util::json(get_handler)) .get("/:id/scopes", util::json(get_scopes_handler)) + .get("/:id/packages", util::json(get_packages_handler)) .build() .unwrap() } @@ -54,3 +56,25 @@ pub async fn get_scopes_handler( Ok(scopes.into_iter().map(ApiScope::from).collect()) } + +#[instrument(name = "GET /api/users/:id/packages", skip(req), err, fields(id))] +pub async fn get_packages_handler( + req: Request, +) -> ApiResult> { + let id = req.param_uuid("id")?; + Span::current().record("id", field::display(id)); + + let db = req.data::().unwrap(); + db.get_user_public(id) + .await? + .ok_or(ApiError::UserNotFound)?; + + let packages = db.get_recent_packages_by_user(&id).await?; + + Ok( + packages + .into_iter() + .map(|package| ApiPackage::from((package, None, Default::default()))) + .collect(), + ) +} diff --git a/api/src/db/database.rs b/api/src/db/database.rs index d5b5eaf4a..75795e24f 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -4858,6 +4858,39 @@ impl Database { Ok((total_scopes as usize, scopes)) } + + pub async fn get_recent_packages_by_user( + &self, + user_id: &uuid::Uuid, + ) -> Result> { + let packages = sqlx::query_as!( + Package, + r#" + SELECT DISTINCT ON (packages.scope, packages.name) + packages.scope as "scope: ScopeName", + packages.name as "name: PackageName", + packages.description, + packages.github_repository_id, + packages.runtime_compat as "runtime_compat: RuntimeCompat", + packages.when_featured, + packages.is_archived, + packages.updated_at, + packages.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" + FROM packages + JOIN scope_members ON packages.scope = scope_members.scope + WHERE scope_members.user_id = $1 + ORDER BY packages.scope, packages.name, packages.created_at DESC + LIMIT 10; + "#, + user_id + ) + .fetch_all(&self.pool) + .await?; + + Ok(packages) + } } async fn finalize_package_creation( diff --git a/frontend/routes/user/[id].tsx b/frontend/routes/user/[id].tsx index 2162c25f5..c2f932525 100644 --- a/frontend/routes/user/[id].tsx +++ b/frontend/routes/user/[id].tsx @@ -2,7 +2,7 @@ import { HttpError } from "fresh"; import { define } from "../../util.ts"; import { path } from "../../utils/api.ts"; -import { FullUser, Scope, User } from "../../utils/api_types.ts"; +import { FullUser, Package, Scope, User } from "../../utils/api_types.ts"; import { ListPanel } from "../../components/ListPanel.tsx"; import { AccountLayout } from "../account/(_components)/AccountLayout.tsx"; @@ -32,14 +32,27 @@ export default define.page(function UserPage({ data, state }) { )} - { - /*
- Recently published -
-
*/ - } + {data.packages.length > 0 + ? ( + ({ + value: `@${pkg.scope}/${pkg.name}`, + href: `/@${pkg.scope}/${pkg.name}`, + }))} + /> + ) + : ( +
+ {state.user?.id === data.user.id ? "You have" : "This user has"} + {" "} + not published any packages recently. +
+ )}
); @@ -47,10 +60,11 @@ export default define.page(function UserPage({ data, state }) { export const handler = define.handlers({ async GET(ctx) { - const [currentUser, userRes, scopesRes] = await Promise.all([ + const [currentUser, userRes, scopesRes, packagesRes] = await Promise.all([ ctx.state.userPromise, ctx.state.api.get(path`/users/${ctx.params.id}`), ctx.state.api.get(path`/users/${ctx.params.id}/scopes`), + ctx.state.api.get(path`/users/${ctx.params.id}/packages`), ]); if (currentUser instanceof Response) return currentUser; @@ -62,6 +76,7 @@ export const handler = define.handlers({ throw userRes; // gracefully handle errors } if (!scopesRes.ok) throw scopesRes; // gracefully handle errors + if (!packagesRes.ok) throw packagesRes; // gracefully handle errors let user: User | FullUser = userRes.data; if (ctx.params.id === currentUser?.id) { @@ -75,6 +90,7 @@ export const handler = define.handlers({ data: { user, scopes: scopesRes.data, + packages: packagesRes.data, }, }; }, From ea88d4cd53c0f24960ac99104bf26a6cc83bc804 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:18:41 +0200 Subject: [PATCH 2/4] fix --- ...0a4e975ac1451fdc55fa6510c6c3bd6b82f5.json} | 26 +++++++++++++++---- api/src/db/database.rs | 1 + 2 files changed, 22 insertions(+), 5 deletions(-) rename api/.sqlx/{query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json => query-fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5.json} (61%) diff --git a/api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json b/api/.sqlx/query-fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5.json similarity index 61% rename from api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json rename to api/.sqlx/query-fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5.json index 37ccfc0f2..5961135b3 100644 --- a/api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json +++ b/api/.sqlx/query-fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT DISTINCT ON (packages.scope, packages.name)\n packages.scope as \"scope: ScopeName\",\n packages.name as \"name: PackageName\",\n packages.description,\n packages.github_repository_id,\n packages.runtime_compat as \"runtime_compat: RuntimeCompat\",\n packages.when_featured,\n packages.is_archived,\n packages.updated_at,\n packages.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 FROM packages\n JOIN scope_members ON packages.scope = scope_members.scope\n WHERE scope_members.user_id = $1\n ORDER BY packages.scope, packages.name, packages.created_at DESC\n LIMIT 10;\n ", + "query": "\n SELECT DISTINCT ON (packages.scope, packages.name)\n packages.scope as \"scope: ScopeName\",\n packages.name as \"name: PackageName\",\n packages.description,\n packages.github_repository_id,\n packages.runtime_compat as \"runtime_compat: RuntimeCompat\",\n packages.when_featured,\n packages.is_archived,\n packages.readme_source as \"readme_source: ReadmeSource\",\n packages.updated_at,\n packages.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 FROM packages\n JOIN scope_members ON packages.scope = scope_members.scope\n WHERE scope_members.user_id = $1\n ORDER BY packages.scope, packages.name, packages.created_at DESC\n LIMIT 10;\n ", "describe": { "columns": [ { @@ -40,21 +40,36 @@ }, { "ordinal": 7, + "name": "readme_source: ReadmeSource", + "type_info": { + "Custom": { + "name": "package_readme_source", + "kind": { + "Enum": [ + "readme", + "jsdoc" + ] + } + } + } + }, + { + "ordinal": 8, "name": "updated_at", "type_info": "Timestamptz" }, { - "ordinal": 8, + "ordinal": 9, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 9, + "ordinal": 10, "name": "version_count!", "type_info": "Int8" }, { - "ordinal": 10, + "ordinal": 11, "name": "latest_version", "type_info": "Text" } @@ -74,9 +89,10 @@ false, false, false, + false, null, null ] }, - "hash": "b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d" + "hash": "fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5" } diff --git a/api/src/db/database.rs b/api/src/db/database.rs index 45165c33d..b2c3419bd 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -4856,6 +4856,7 @@ impl Database { packages.runtime_compat as "runtime_compat: RuntimeCompat", packages.when_featured, packages.is_archived, + packages.readme_source as "readme_source: ReadmeSource", packages.updated_at, packages.created_at, (SELECT COUNT(created_at) FROM package_versions WHERE scope = packages.scope AND name = packages.name) as "version_count!", From 8cd66230672a6594c87da082e2f4660fe30fc172 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 13 Jun 2025 12:29:25 +0200 Subject: [PATCH 3/4] Update api.yml --- api/src/api.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/api/src/api.yml b/api/src/api.yml index 1b51c638b..96397b435 100644 --- a/api/src/api.yml +++ b/api/src/api.yml @@ -1299,6 +1299,34 @@ paths: schema: $ref: "#/components/schemas/Error" + /users/{id}/packages: + get: + summary: List recently published packages by user + description: Returns a list of packages that a user has recently published + operationId: listUserPackages + parameters: + - name: id + in: path + description: The ID of the user + required: true + schema: + $ref: "#/components/schemas/UserId" + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Package" + "404": + description: User not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /authorizations: post: summary: Create authorization From c54d244afd548661066937292fc17e5511500d42 Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Fri, 13 Jun 2025 14:29:30 +0200 Subject: [PATCH 4/4] update from feedback --- ...57b93a1abe7ea2a4f196c061d063d86de412.json} | 4 +- api/src/db/database.rs | 53 ++++++++++++------- 2 files changed, 36 insertions(+), 21 deletions(-) rename api/.sqlx/{query-fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5.json => query-76aa80d3f0b1f22bbbc97a37a7b757b93a1abe7ea2a4f196c061d063d86de412.json} (55%) diff --git a/api/.sqlx/query-fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5.json b/api/.sqlx/query-76aa80d3f0b1f22bbbc97a37a7b757b93a1abe7ea2a4f196c061d063d86de412.json similarity index 55% rename from api/.sqlx/query-fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5.json rename to api/.sqlx/query-76aa80d3f0b1f22bbbc97a37a7b757b93a1abe7ea2a4f196c061d063d86de412.json index 5961135b3..de59828a4 100644 --- a/api/.sqlx/query-fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5.json +++ b/api/.sqlx/query-76aa80d3f0b1f22bbbc97a37a7b757b93a1abe7ea2a4f196c061d063d86de412.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT DISTINCT ON (packages.scope, packages.name)\n packages.scope as \"scope: ScopeName\",\n packages.name as \"name: PackageName\",\n packages.description,\n packages.github_repository_id,\n packages.runtime_compat as \"runtime_compat: RuntimeCompat\",\n packages.when_featured,\n packages.is_archived,\n packages.readme_source as \"readme_source: ReadmeSource\",\n packages.updated_at,\n packages.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 FROM packages\n JOIN scope_members ON packages.scope = scope_members.scope\n WHERE scope_members.user_id = $1\n ORDER BY packages.scope, packages.name, packages.created_at DESC\n LIMIT 10;\n ", + "query": "\n SELECT DISTINCT ON (packages.scope, packages.name)\n packages.scope as \"scope: ScopeName\",\n packages.name as \"name: PackageName\",\n packages.description,\n packages.github_repository_id,\n packages.runtime_compat as \"runtime_compat: RuntimeCompat\",\n packages.when_featured,\n packages.is_archived,\n packages.readme_source as \"readme_source: ReadmeSource\",\n packages.updated_at,\n packages.created_at,\n (\n SELECT COUNT(created_at)\n FROM package_versions\n WHERE scope = packages.scope AND name = packages.name\n ) as \"version_count!\",\n (\n SELECT version\n FROM package_versions\n WHERE scope = packages.scope\n AND name = packages.name\n AND version NOT LIKE '%-%'\n AND is_yanked = false\n ORDER BY version DESC LIMIT 1\n ) as \"latest_version\"\n FROM publishing_tasks\n JOIN packages\n ON packages.scope = publishing_tasks.package_scope\n AND packages.name = publishing_tasks.package_name\n WHERE publishing_tasks.user_id = $1\n AND publishing_tasks.status = 'success'\n ORDER BY packages.scope, packages.name, publishing_tasks.created_at DESC\n LIMIT 10;\n ", "describe": { "columns": [ { @@ -94,5 +94,5 @@ null ] }, - "hash": "fa420c32653cf7f1b5a4c123ff380a4e975ac1451fdc55fa6510c6c3bd6b82f5" + "hash": "76aa80d3f0b1f22bbbc97a37a7b757b93a1abe7ea2a4f196c061d063d86de412" } diff --git a/api/src/db/database.rs b/api/src/db/database.rs index b2c3419bd..e36ae01c6 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -4846,28 +4846,43 @@ impl Database { user_id: &uuid::Uuid, ) -> Result> { let packages = sqlx::query_as!( - Package, - r#" + Package, + r#" SELECT DISTINCT ON (packages.scope, packages.name) - packages.scope as "scope: ScopeName", - packages.name as "name: PackageName", - packages.description, - packages.github_repository_id, - packages.runtime_compat as "runtime_compat: RuntimeCompat", - packages.when_featured, - packages.is_archived, - packages.readme_source as "readme_source: ReadmeSource", - packages.updated_at, - packages.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" - FROM packages - JOIN scope_members ON packages.scope = scope_members.scope - WHERE scope_members.user_id = $1 - ORDER BY packages.scope, packages.name, packages.created_at DESC + packages.scope as "scope: ScopeName", + packages.name as "name: PackageName", + packages.description, + packages.github_repository_id, + packages.runtime_compat as "runtime_compat: RuntimeCompat", + packages.when_featured, + packages.is_archived, + packages.readme_source as "readme_source: ReadmeSource", + packages.updated_at, + packages.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" + FROM publishing_tasks + JOIN packages + ON packages.scope = publishing_tasks.package_scope + AND packages.name = publishing_tasks.package_name + WHERE publishing_tasks.user_id = $1 + AND publishing_tasks.status = 'success' + ORDER BY packages.scope, packages.name, publishing_tasks.created_at DESC LIMIT 10; "#, - user_id + user_id ) .fetch_all(&self.pool) .await?;