--- import BaseLayout from "../layouts/BaseLayout.astro"; import { Header } from "../components/layout/Header"; import { Input } from "../components/ui/input"; import { Button } from "../components/ui/button"; import { Card, CardContent } from "../components/ui/card"; import { Badge } from "../components/ui/badge"; import { Search, Filter, Star, GitFork, BookOpen, Copy, Globe, ExternalLink, Users, Sparkles, Code2, X, TrendingUp, Clock, Hash, ArrowUpDown } from "lucide-react"; import { verifyToken } from "../lib/auth"; import { getDb } from "../lib/db"; import { desc, like, or, eq, sql, and, inArray } from "drizzle-orm"; import { cachedRepos, cachedUsers, instances } from "../lib/db/schema"; const token = Astro.cookies.get("community_session")?.value; const user = token ? await verifyToken(token) : null; const q = Astro.url.searchParams.get("q") || ""; const selectedLang = Astro.url.searchParams.get("lang") || ""; const selectedTopic = Astro.url.searchParams.get("topic") || ""; const sortBy = Astro.url.searchParams.get("sort") || "stars"; const page = Math.max(1, parseInt(Astro.url.searchParams.get("page") || "1")); const perPage = 20; const offset = (page - 1) * perPage; const db = getDb() as any; // Dynamic language list from actual data const allLangs = await db .select({ language: cachedRepos.language, count: sql`count(*)` }) .from(cachedRepos) .where(sql`${cachedRepos.language} IS NOT NULL AND ${cachedRepos.language} != ''`) .groupBy(cachedRepos.language) .orderBy(desc(sql`count(*)`)) .limit(15) .catch(() => []); const popularLanguages = allLangs.filter((l: any) => l.language).map((l: any) => l.language); // Dynamic topic list from actual data const allTopicsRaw = await db.select({ topics: cachedRepos.topics }).from(cachedRepos).where(sql`${cachedRepos.topics} IS NOT NULL AND ${cachedRepos.topics} != '[]'`).catch(() => []); const topicCounts = new Map(); for (const row of allTopicsRaw) { try { const parsed = typeof row.topics === "string" ? JSON.parse(row.topics) : row.topics; if (Array.isArray(parsed)) { for (const t of parsed) { const topic = String(t).trim().toLowerCase(); if (topic) topicCounts.set(topic, (topicCounts.get(topic) || 0) + 1); } } } catch {} } const popularTopics = [...topicCounts.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 12) .map(([topic, count]) => ({ topic, count })); const langColors: Record = { TypeScript: "#3178c6", JavaScript: "#f1e05a", Python: "#3572A5", Go: "#00ADD8", Rust: "#dea584", Java: "#b07219", "C++": "#f34b7d", C: "#555555", Ruby: "#701516", PHP: "#4F5D95", Swift: "#F05138", Kotlin: "#A97BFF", Dart: "#00B4AB", Shell: "#89e051", HTML: "#e34c26", CSS: "#563d7c", Vue: "#41b883", Astro: "#ff5a03", Scala: "#c22d40", Haskell: "#5e5086", Elixir: "#6e4a7e", Lua: "#000080", R: "#198CE7", MATLAB: "#e16737", Julia: "#a270ba", Zig: "#ec915c", }; // Build where clause — fix: combine lang+text properly const searchPattern = `%${q}%`; const conditions = []; if (q) { conditions.push(or(like(cachedRepos.fullName, searchPattern), like(cachedRepos.description, searchPattern))); } if (selectedLang) { conditions.push(eq(cachedRepos.language, selectedLang)); } if (selectedTopic) { conditions.push(sql`${cachedRepos.topics} LIKE ${`%${selectedTopic.toLowerCase()}%`}`); } const repoWhere = conditions.length > 0 ? conditions.length === 1 ? conditions[0] : and(...conditions) : undefined; // Sorting let orderBy; switch (sortBy) { case "newest": orderBy = [desc(cachedRepos.cachedAt)]; break; case "updated": orderBy = [desc(cachedRepos.updatedAt)]; break; case "forks": orderBy = [desc(cachedRepos.forkCount), desc(cachedRepos.starCount)]; break; case "stars": default: orderBy = [desc(cachedRepos.starCount), desc(cachedRepos.updatedAt)]; break; } // Fetch repos with count const [repos, countResult] = await Promise.all([ db.query.cachedRepos.findMany({ where: repoWhere, orderBy, limit: perPage, offset, with: { instance: true }, }).catch(() => []), db.select({ count: sql`count(*)` }).from(cachedRepos).where(repoWhere).then((r: any) => r[0]?.count || 0).catch(() => 0), ]); const totalPages = Math.ceil(countResult / perPage); // Fetch profiles const profiles = await db.query.cachedUsers.findMany({ where: q ? or(like(cachedUsers.username, searchPattern), like(cachedUsers.displayName, searchPattern)) : undefined, orderBy: [desc(cachedUsers.cachedAt)], limit: 6, with: { instance: true }, }).catch(() => []); // Network stats const [totalRepos, totalUsers, totalInstances] = await Promise.all([ db.select({ count: sql`count(*)` }).from(cachedRepos).then((r: any) => r[0]?.count || 0).catch(() => 0), db.select({ count: sql`count(*)` }).from(cachedUsers).then((r: any) => r[0]?.count || 0).catch(() => 0), db.select({ count: sql`count(*)` }).from(instances).then((r: any) => r[0]?.count || 0).catch(() => 0), ]); function buildUrl(params: Record) { const sp = new URLSearchParams(); if (q) sp.set("q", q); if (selectedLang) sp.set("lang", selectedLang); if (selectedTopic) sp.set("topic", selectedTopic); if (sortBy !== "stars") sp.set("sort", sortBy); for (const [k, v] of Object.entries(params)) { if (v === "") sp.delete(k); else sp.set(k, v); } const qs = sp.toString(); return `/explore${qs ? `?${qs}` : ""}`; } ---

Explore the Network

Discover public projects and developers from {totalInstances} federated {totalInstances === 1 ? "node" : "nodes"} · {totalRepos} repos · {totalUsers} users

{selectedLang && } {selectedTopic && } {sortBy !== "stars" && }
{q && ( )}
{popularLanguages.length > 0 && (
Languages: All {popularLanguages.slice(0, 10).map((lang: string) => { const isActive = selectedLang.toLowerCase() === lang.toLowerCase(); return ( {lang} ); })}
)} {popularTopics.length > 0 && (
Topics: {popularTopics.map(({ topic, count }) => { const isActive = selectedTopic.toLowerCase() === topic.toLowerCase(); return ( #{topic} {count} ); })}
)} {(q || selectedLang || selectedTopic) && (
Active filters: {q && "{q}" } {selectedLang && {selectedLang} } {selectedTopic && #{selectedTopic} } Clear all
)}

{q ? `Results for "${q}"` : selectedLang ? `${selectedLang} Repos` : selectedTopic ? `#${selectedTopic}` : sortBy === "newest" ? "Recently Added" : sortBy === "updated" ? "Recently Updated" : "Trending Repositories"} {countResult} {countResult === 1 ? "repo" : "repos"}

{[ { key: "stars", label: "Stars", icon: Star }, { key: "updated", label: "Updated", icon: Clock }, { key: "newest", label: "Newest", icon: TrendingUp }, { key: "forks", label: "Forks", icon: GitFork }, ].map(({ key, label, icon: Icon }) => ( {label} ))}
{repos.length === 0 ? (

No repositories found

{q || selectedLang || selectedTopic ? "Try different search terms or remove filters." : "Submit your OpenCodeHub instance to start aggregating repos."}

) : ( repos.map((repo: any) => { const langColor = langColors[repo.language] || "#888"; let topics: string[] = []; try { topics = typeof repo.topics === "string" ? JSON.parse(repo.topics) : repo.topics || []; } catch {} return (
{repo.fullName} {repo.visibility || "public"} {repo.instance?.name && {repo.instance.name}}

{repo.description || "No description provided."}

{topics.length > 0 && (
{topics.slice(0, 5).map((t: string) => ( #{t} ))}
)}
{repo.language && {repo.language}} {repo.starCount || 0} {repo.forkCount || 0} {repo.updatedAt && Updated {new Date(repo.updatedAt).toLocaleDateString()}}
); }) )} {totalPages > 1 && (
{page > 1 && Prev} {Array.from({ length: Math.min(totalPages, 7) }, (_, i) => { const p = i + 1; return {p}; })} {page < totalPages && Next}
)}

Network Stats

{totalInstances}
Nodes
{totalRepos}
Repos
{totalUsers}
Users

{q ? "Matching Profiles" : "Community Profiles"}

{profiles.length === 0 ? (

Profiles appear as nodes synchronize.

) : (
{profiles.map((profile: any) => (
@{profile.username} {profile.instance?.name || "Federated User"}
))}
)}

How to Contribute

  1. Copy the project's httpCloneUrl
  2. Clone locally to your own workstation
  3. Push to your personal node and open a cross-node PR!
Browse all nodes