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.tsxBlame173 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";
13
14const explore = new Hono<AuthEnv>();
15
16explore.use("*", softAuth);
17
18explore.get("/explore", async (c) => {
19 const user = c.get("user");
20 const q = c.req.query("q") || "";
21 const sort = c.req.query("sort") || "recent";
22 const topic = c.req.query("topic") || "";
23
24 let repoList: Array<{
25 repo: typeof repositories.$inferSelect;
26 ownerName: string;
27 }> = [];
28
29 if (q.trim()) {
30 // Search repos
31 const results = await db
32 .select({
33 repo: repositories,
34 ownerName: users.username,
35 })
36 .from(repositories)
37 .innerJoin(users, eq(repositories.ownerId, users.id))
38 .where(
39 and(
40 eq(repositories.isPrivate, false),
41 sql`(${repositories.name} ILIKE ${'%' + q + '%'} OR ${repositories.description} ILIKE ${'%' + q + '%'})`
42 )
43 )
44 .orderBy(desc(repositories.starCount))
45 .limit(50);
46
47 repoList = results.map((r) => ({
48 repo: r.repo,
49 ownerName: r.ownerName,
50 }));
51 } else if (topic) {
52 // Filter by topic
53 const results = await db
54 .select({
55 repo: repositories,
56 ownerName: users.username,
57 })
58 .from(repositories)
59 .innerJoin(users, eq(repositories.ownerId, users.id))
60 .innerJoin(repoTopics, eq(repoTopics.repositoryId, repositories.id))
61 .where(
62 and(
63 eq(repositories.isPrivate, false),
64 eq(repoTopics.topic, topic.toLowerCase())
65 )
66 )
67 .orderBy(desc(repositories.starCount))
68 .limit(50);
69
70 repoList = results.map((r) => ({
71 repo: r.repo,
72 ownerName: r.ownerName,
73 }));
74 } else {
75 // Default: recent or popular
76 const orderBy =
77 sort === "stars"
78 ? desc(repositories.starCount)
79 : sort === "forks"
80 ? desc(repositories.forkCount)
81 : desc(repositories.createdAt);
82
83 const results = await db
84 .select({
85 repo: repositories,
86 ownerName: users.username,
87 })
88 .from(repositories)
89 .innerJoin(users, eq(repositories.ownerId, users.id))
90 .where(eq(repositories.isPrivate, false))
91 .orderBy(orderBy)
92 .limit(50);
93
94 repoList = results.map((r) => ({
95 repo: r.repo,
96 ownerName: r.ownerName,
97 }));
98 }
99
100 return c.html(
101 <Layout title="Explore" user={user}>
102 <h2 style="margin-bottom: 16px">Explore repositories</h2>
103 <div style="display: flex; gap: 12px; margin-bottom: 24px; flex-wrap: wrap; align-items: center">
104 <form
9837657Claude105 method="get"
c81ab7aClaude106 action="/explore"
107 style="display: flex; gap: 8px; flex: 1; min-width: 250px"
108 >
109 <input
110 type="text"
111 name="q"
112 value={q}
113 placeholder="Search repositories..."
114 style="flex: 1; padding: 8px 12px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 14px"
115 />
116 <button type="submit" class="btn btn-primary">
117 Search
118 </button>
119 </form>
120 <div style="display: flex; gap: 8px">
121 <a
122 href="/explore?sort=recent"
123 class={`btn btn-sm ${sort === "recent" && !q ? "btn-primary" : ""}`}
124 >
125 Recent
126 </a>
127 <a
128 href="/explore?sort=stars"
129 class={`btn btn-sm ${sort === "stars" ? "btn-primary" : ""}`}
130 >
131 Most stars
132 </a>
133 <a
134 href="/explore?sort=forks"
135 class={`btn btn-sm ${sort === "forks" ? "btn-primary" : ""}`}
136 >
137 Most forks
138 </a>
139 </div>
140 </div>
141 {topic && (
142 <div style="margin-bottom: 16px">
143 <span class="badge" style="font-size: 14px; padding: 4px 12px">
144 Topic: {topic}
145 </span>
146 <a
147 href="/explore"
148 style="margin-left: 8px; font-size: 13px; color: var(--text-muted)"
149 >
150 Clear
151 </a>
152 </div>
153 )}
154 {repoList.length === 0 ? (
155 <div class="empty-state">
156 <p>
157 {q
158 ? `No repositories matching "${q}"`
159 : "No public repositories yet."}
160 </p>
161 </div>
162 ) : (
163 <div class="card-grid">
164 {repoList.map(({ repo, ownerName }) => (
165 <RepoCard repo={repo} ownerName={ownerName} />
166 ))}
167 </div>
168 )}
169 </Layout>
170 );
171});
172
173export default explore;