diff --git a/api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json b/api/.sqlx/query-b51c548092ff640f6d210365a4fb79d8d22a7868b9da6896b068e0cc0fa1298d.json
new file mode 100644
index 00000000..37ccfc0f
--- /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 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 d5b5eaf4..75795e24 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 2162c25f..c2f93252 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,
},
};
},