Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

explore.tsx

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

explore.tsxBlame185 lines · 1 contributor
c81ab7aClaude1/**
2 * Explore page — discover public repositories, search, trending.
3 */
4
5import { Hono } from "hono";
6import { eq, desc, sql, like, and } from "drizzle-orm";
7import { db } from "../db";
8import { repositories, users, repoTopics } from "../db/schema";
9import { Layout } from "../views/layout";
10import { RepoCard } from "../views/components";
11import { softAuth } from "../middleware/auth";
12import type { AuthEnv } from "../middleware/auth";
3e8f8e8Claude13import {
14 Flex,
15 Grid,
16 Badge,
17 Button,
18 LinkButton,
19 Input,
20 EmptyState,
21 PageHeader,
22} from "../views/ui";
c81ab7aClaude23
24const explore = new Hono<AuthEnv>();
25
26explore.use("*", softAuth);
27
28explore.get("/explore", async (c) => {
29 const user = c.get("user");
30 const q = c.req.query("q") || "";
31 const sort = c.req.query("sort") || "recent";
32 const topic = c.req.query("topic") || "";
33
34 let repoList: Array<{
35 repo: typeof repositories.$inferSelect;
36 ownerName: string;
37 }> = [];
38
39 if (q.trim()) {
40 // Search repos
41 const results = await db
42 .select({
43 repo: repositories,
44 ownerName: users.username,
45 })
46 .from(repositories)
47 .innerJoin(users, eq(repositories.ownerId, users.id))
48 .where(
49 and(
50 eq(repositories.isPrivate, false),
51 sql`(${repositories.name} ILIKE ${'%' + q + '%'} OR ${repositories.description} ILIKE ${'%' + q + '%'})`
52 )
53 )
54 .orderBy(desc(repositories.starCount))
55 .limit(50);
56
57 repoList = results.map((r) => ({
58 repo: r.repo,
59 ownerName: r.ownerName,
60 }));
61 } else if (topic) {
62 // Filter by topic
63 const results = await db
64 .select({
65 repo: repositories,
66 ownerName: users.username,
67 })
68 .from(repositories)
69 .innerJoin(users, eq(repositories.ownerId, users.id))
70 .innerJoin(repoTopics, eq(repoTopics.repositoryId, repositories.id))
71 .where(
72 and(
73 eq(repositories.isPrivate, false),
74 eq(repoTopics.topic, topic.toLowerCase())
75 )
76 )
77 .orderBy(desc(repositories.starCount))
78 .limit(50);
79
80 repoList = results.map((r) => ({
81 repo: r.repo,
82 ownerName: r.ownerName,
83 }));
84 } else {
85 // Default: recent or popular
86 const orderBy =
87 sort === "stars"
88 ? desc(repositories.starCount)
89 : sort === "forks"
90 ? desc(repositories.forkCount)
91 : desc(repositories.createdAt);
92
93 const results = await db
94 .select({
95 repo: repositories,
96 ownerName: users.username,
97 })
98 .from(repositories)
99 .innerJoin(users, eq(repositories.ownerId, users.id))
100 .where(eq(repositories.isPrivate, false))
101 .orderBy(orderBy)
102 .limit(50);
103
104 repoList = results.map((r) => ({
105 repo: r.repo,
106 ownerName: r.ownerName,
107 }));
108 }
109
110 return c.html(
111 <Layout title="Explore" user={user}>
3e8f8e8Claude112 <PageHeader title="Explore repositories" />
113 <Flex gap={12} wrap align="center" style="margin-bottom:24px">
c81ab7aClaude114 <form
45e31d0Claude115 method="get"
c81ab7aClaude116 action="/explore"
3e8f8e8Claude117 style="display:flex;gap:8px;flex:1;min-width:250px"
c81ab7aClaude118 >
3e8f8e8Claude119 <Input
c81ab7aClaude120 name="q"
121 value={q}
122 placeholder="Search repositories..."
3e8f8e8Claude123 style="flex:1;padding:8px 12px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);font-size:14px"
c81ab7aClaude124 />
3e8f8e8Claude125 <Button type="submit" variant="primary">
c81ab7aClaude126 Search
3e8f8e8Claude127 </Button>
c81ab7aClaude128 </form>
3e8f8e8Claude129 <Flex gap={8}>
130 <LinkButton
c81ab7aClaude131 href="/explore?sort=recent"
3e8f8e8Claude132 size="sm"
133 variant={sort === "recent" && !q ? "primary" : "default"}
c81ab7aClaude134 >
135 Recent
3e8f8e8Claude136 </LinkButton>
137 <LinkButton
c81ab7aClaude138 href="/explore?sort=stars"
3e8f8e8Claude139 size="sm"
140 variant={sort === "stars" ? "primary" : "default"}
c81ab7aClaude141 >
142 Most stars
3e8f8e8Claude143 </LinkButton>
144 <LinkButton
c81ab7aClaude145 href="/explore?sort=forks"
3e8f8e8Claude146 size="sm"
147 variant={sort === "forks" ? "primary" : "default"}
c81ab7aClaude148 >
149 Most forks
3e8f8e8Claude150 </LinkButton>
151 </Flex>
152 </Flex>
c81ab7aClaude153 {topic && (
3e8f8e8Claude154 <div style="margin-bottom:16px">
155 <Badge style="font-size:14px;padding:4px 12px">
c81ab7aClaude156 Topic: {topic}
3e8f8e8Claude157 </Badge>
c81ab7aClaude158 <a
159 href="/explore"
3e8f8e8Claude160 style="margin-left:8px;font-size:13px;color:var(--text-muted)"
c81ab7aClaude161 >
162 Clear
163 </a>
164 </div>
165 )}
166 {repoList.length === 0 ? (
3e8f8e8Claude167 <EmptyState>
c81ab7aClaude168 <p>
169 {q
170 ? `No repositories matching "${q}"`
171 : "No public repositories yet."}
172 </p>
3e8f8e8Claude173 </EmptyState>
c81ab7aClaude174 ) : (
3e8f8e8Claude175 <Grid>
c81ab7aClaude176 {repoList.map(({ repo, ownerName }) => (
177 <RepoCard repo={repo} ownerName={ownerName} />
178 ))}
3e8f8e8Claude179 </Grid>
c81ab7aClaude180 )}
181 </Layout>
182 );
183});
184
185export default explore;