Skip to content

Commit d9fee94

Browse files
authored
Switch from /snippets/list_latest to /packages/list_latest (#1061)
* Switch from `/snippets/list_latest` to `/packages/list_latest` * format
1 parent f042000 commit d9fee94

4 files changed

Lines changed: 218 additions & 15 deletions

File tree

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { getTestServer } from "bun-tests/fake-snippets-api/fixtures/get-test-server"
2+
import { expect, test } from "bun:test"
3+
4+
test("list latest packages", async () => {
5+
const { axios, db } = await getTestServer()
6+
7+
// Add some test packages
8+
const packages = [
9+
{
10+
name: "Package1",
11+
unscoped_name: "Package1",
12+
owner_github_username: "user1",
13+
creator_account_id: "creator1",
14+
created_at: "2023-01-01T00:00:00Z",
15+
updated_at: "2023-01-01T00:00:00Z",
16+
description: "Description 1",
17+
ai_description: "AI Description 1",
18+
ai_usage_instructions: "Usage instructions 1",
19+
owner_org_id: "org1",
20+
latest_version: "1.0.0",
21+
license: "MIT",
22+
is_source_from_github: true,
23+
is_snippet: false,
24+
},
25+
{
26+
name: "Package2",
27+
unscoped_name: "Package2",
28+
owner_github_username: "user2",
29+
creator_account_id: "creator2",
30+
created_at: "2023-01-02T00:00:00Z",
31+
updated_at: "2023-01-02T00:00:00Z",
32+
description: "Description 2",
33+
ai_description: "AI Description 2",
34+
ai_usage_instructions: "Usage instructions 2",
35+
owner_org_id: "org2",
36+
latest_version: "1.0.0",
37+
license: "MIT",
38+
is_source_from_github: true,
39+
is_snippet: false,
40+
},
41+
{
42+
name: "Package3",
43+
unscoped_name: "Package3",
44+
owner_github_username: "user3",
45+
creator_account_id: "creator3",
46+
created_at: "2023-01-03T00:00:00Z",
47+
updated_at: "2023-01-03T00:00:00Z",
48+
description: "Description 3",
49+
ai_description: "AI Description 3",
50+
ai_usage_instructions: "Usage instructions 3",
51+
owner_org_id: "org3",
52+
latest_version: "1.0.0",
53+
license: "MIT",
54+
is_source_from_github: true,
55+
is_snippet: false,
56+
},
57+
// Add a snippet to verify it's filtered out
58+
{
59+
name: "Snippet1",
60+
unscoped_name: "Snippet1",
61+
owner_github_username: "user4",
62+
creator_account_id: "creator4",
63+
created_at: "2023-01-04T00:00:00Z",
64+
updated_at: "2023-01-04T00:00:00Z",
65+
description: "Snippet Description",
66+
ai_description: "AI Description",
67+
ai_usage_instructions: "Usage instructions",
68+
owner_org_id: "org4",
69+
latest_version: "1.0.0",
70+
license: "MIT",
71+
is_source_from_github: true,
72+
is_snippet: true,
73+
},
74+
]
75+
76+
// Add packages to the database
77+
for (const pkg of packages) {
78+
db.addPackage(pkg as any)
79+
}
80+
81+
// Test the latest endpoint
82+
const { data } = await axios.get("/api/packages/list_latest")
83+
84+
// Verify response structure
85+
expect(Array.isArray(data.packages)).toBe(true)
86+
expect(data.packages.length).toBe(3) // Should only return non-snippet packages
87+
88+
// Verify that packages are sorted by creation date (newest first)
89+
for (let i = 1; i < data.packages.length; i++) {
90+
expect(
91+
new Date(data.packages[i - 1].created_at).getTime(),
92+
).toBeGreaterThanOrEqual(new Date(data.packages[i].created_at).getTime())
93+
}
94+
95+
// Verify that all returned packages have the required fields
96+
data.packages.forEach((pkg: any) => {
97+
expect(pkg).toHaveProperty("name")
98+
expect(pkg).toHaveProperty("unscoped_name")
99+
expect(pkg).toHaveProperty("owner_github_username")
100+
expect(pkg).toHaveProperty("creator_account_id")
101+
expect(pkg).toHaveProperty("latest_package_release_id")
102+
expect(pkg.is_snippet).toBe(false)
103+
})
104+
105+
// Test with limit parameter
106+
const { data: limitedData } = await axios.get(
107+
"/api/packages/list_latest?limit=2",
108+
)
109+
expect(limitedData.packages.length).toBe(2)
110+
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { withRouteSpec } from "fake-snippets-api/lib/middleware/with-winter-spec"
2+
import { z } from "zod"
3+
import { packageSchema } from "fake-snippets-api/lib/db/schema"
4+
import { publicMapPackage } from "fake-snippets-api/lib/public-mapping/public-map-package"
5+
6+
export default withRouteSpec({
7+
methods: ["GET"],
8+
auth: "none",
9+
queryParams: z.object({
10+
limit: z.string().regex(/^\d+$/).transform(Number).optional(),
11+
}),
12+
jsonResponse: z.object({
13+
packages: z.array(packageSchema),
14+
}),
15+
})(async (req, ctx) => {
16+
const limit = req.query.limit || 50
17+
18+
// Get all packages that are not snippets
19+
const packages = ctx.db.packages
20+
.filter((pkg) => !pkg.is_snippet)
21+
.sort(
22+
(a, b) =>
23+
new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
24+
)
25+
.slice(0, limit)
26+
.map((pkg) => publicMapPackage(pkg))
27+
28+
return ctx.json({ packages })
29+
})

src/components/PackagesList.tsx

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { Button } from "@/components/ui/button"
2+
import { ChevronDown, ChevronUp, Star } from "lucide-react"
3+
import { Link } from "wouter"
4+
import { Package } from "fake-snippets-api/lib/db/schema"
5+
6+
interface PackageListProps {
7+
title: string
8+
packages?: Package[]
9+
showAll?: boolean
10+
onToggleShowAll?: () => void
11+
maxItems?: number
12+
}
13+
14+
export const PackageList = ({
15+
title,
16+
packages = [],
17+
showAll = false,
18+
onToggleShowAll,
19+
maxItems = 5,
20+
}: PackageListProps) => {
21+
const displayedPackages = showAll ? packages : packages.slice(0, maxItems)
22+
23+
return (
24+
<div>
25+
<div className="flex items-center justify-between">
26+
<h2 className="text-sm font-bold text-gray-700">{title}</h2>
27+
{packages.length > maxItems && onToggleShowAll && (
28+
<Button
29+
variant="ghost"
30+
size="sm"
31+
onClick={onToggleShowAll}
32+
className="text-blue-600 hover:text-blue-700 hover:bg-transparent"
33+
>
34+
{showAll ? (
35+
<>
36+
Show less <ChevronUp className="w-3 h-3 ml-1" />
37+
</>
38+
) : (
39+
<>
40+
Show more <ChevronDown className="w-3 h-3 ml-1" />
41+
</>
42+
)}
43+
</Button>
44+
)}
45+
</div>
46+
<div className="border-b border-gray-200" />
47+
{packages && (
48+
<ul className="space-y-1 mt-2">
49+
{displayedPackages.map((pkg) => (
50+
<li key={pkg.package_id}>
51+
<div className="flex items-center">
52+
<Link
53+
href={`/${pkg.owner_github_username}/${pkg.unscoped_name}`}
54+
className="text-blue-600 hover:underline text-sm"
55+
>
56+
{pkg.owner_github_username}/{pkg.unscoped_name}
57+
</Link>
58+
{pkg.star_count > 0 && (
59+
<span className="ml-2 text-gray-500 text-xs flex items-center">
60+
<Star className="w-3 h-3 mr-1" />
61+
{pkg.star_count}
62+
</span>
63+
)}
64+
</div>
65+
</li>
66+
))}
67+
</ul>
68+
)}
69+
</div>
70+
)
71+
}

src/pages/dashboard.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,11 @@ import Header from "@/components/Header"
55
import Footer from "@/components/Footer"
66
import { Package, Snippet } from "fake-snippets-api/lib/db/schema"
77
import { Link } from "wouter"
8-
import { CreateNewSnippetWithAiHero } from "@/components/CreateNewSnippetWithAiHero"
9-
import {
10-
Edit2,
11-
Star,
12-
ChevronDown,
13-
ChevronUp,
14-
Key,
15-
KeyRound,
16-
} from "lucide-react"
8+
import { Edit2, KeyRound } from "lucide-react"
179
import { Button } from "@/components/ui/button"
1810
import { useGlobalStore } from "@/hooks/use-global-store"
1911
import { PrefetchPageLink } from "@/components/PrefetchPageLink"
12+
import { PackageList } from "@/components/PackagesList"
2013
import { SnippetList } from "@/components/SnippetList"
2114
import { Helmet } from "react-helmet-async"
2215
import { useSignIn } from "@/hooks/use-sign-in"
@@ -67,11 +60,11 @@ export const DashboardPage = () => {
6760
},
6861
)
6962

70-
const { data: latestSnippets } = useQuery<Snippet[]>(
71-
"latestSnippets",
63+
const { data: latestPackages } = useQuery<Package[]>(
64+
"latestPackages",
7265
async () => {
73-
const response = await axios.get("/snippets/list_latest")
74-
return response.data.snippets
66+
const response = await axios.get("/packages/list_latest")
67+
return response.data.packages
7568
},
7669
)
7770

@@ -189,9 +182,9 @@ export const DashboardPage = () => {
189182
onToggleShowAll={() => setShowAllTrending(!showAllTrending)}
190183
/>
191184
<div className="mt-8">
192-
<SnippetList
185+
<PackageList
193186
title="Latest Packages"
194-
snippets={latestSnippets}
187+
packages={latestPackages}
195188
showAll={showAllLatest}
196189
onToggleShowAll={() => setShowAllLatest(!showAllLatest)}
197190
/>

0 commit comments

Comments
 (0)