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?;