diff --git a/api/src/api/users.rs b/api/src/api/users.rs index 975cfb4a..3b394168 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 06af6a26..30ff4433 100644 --- a/api/src/db/database.rs +++ b/api/src/db/database.rs @@ -3215,6 +3215,39 @@ impl Database { .fetch_all(&self.pool) .await } + + 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 f1c22c7d..45cc840f 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,26 @@ export default define.page(function UserPage({ data, state }) { )} - { - /*
- Recently published -
- TODO: all packages recently published by this user -
-
*/ - } + {data.packages.length > 0 + ? ( + ({ + value: `@${pkg.scope}/${pkg.name}`, + //@am/neuralnetwork + href: `/${pkg.scope}/${pkg.name}`, + }))} + /> + ) + : ( +
+ {state.user?.id === data.user.id ? "You have" : "This user has"} + {" "} + not published any packages recently. +
+ )} ); @@ -47,10 +59,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 +75,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 +89,7 @@ export const handler = define.handlers({ data: { user, scopes: scopesRes.data, + packages: packagesRes.data, }, }; },