Skip to content

Commit ff3f5bf

Browse files
committed
fix(api): lazy-fetch full SKILL.md content for seeded skills with stubs
Skills seeded from SkillsMP had stub content (title+desc+author only). When stub content is detected, fetches real SKILL.md from GitHub via source_url and persists it back to D1 for future requests.
1 parent e9d5ae6 commit ff3f5bf

1 file changed

Lines changed: 43 additions & 0 deletions

File tree

apps/web/app/routes/api.skill-detail.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,36 @@ import { skills, ratings, reviews, favorites } from "~/lib/db/schema";
44
import { eq, desc, count, avg } from "drizzle-orm";
55
import { getSession } from "~/lib/auth/session-helpers";
66

7+
/** Detect stub content: short + ends with "## Author\n{author}" */
8+
function isStubContent(content: string, author: string): boolean {
9+
return content.length < 300 && content.trimEnd().endsWith(`## Author\n${author}`);
10+
}
11+
12+
/** Derive raw SKILL.md URL from a GitHub source_url like
13+
* https://github.com/{owner}/{repo}/tree/{branch}/{path} */
14+
function toRawSkillMdUrl(sourceUrl: string): string | null {
15+
const m = sourceUrl.match(/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.*)/);
16+
if (!m) return null;
17+
return `https://raw.githubusercontent.com/${m[1]}/${m[2]}/${m[3]}/${m[4]}/SKILL.md`;
18+
}
19+
20+
/** Fetch real SKILL.md content from GitHub. Returns null on failure. */
21+
async function fetchRealContent(sourceUrl: string): Promise<string | null> {
22+
const rawUrl = toRawSkillMdUrl(sourceUrl);
23+
if (!rawUrl) return null;
24+
25+
try {
26+
const res = await fetch(rawUrl, {
27+
headers: { "User-Agent": "SkillX/1.0" },
28+
});
29+
if (!res.ok) return null;
30+
const text = await res.text();
31+
return text.trim().length > 20 ? text : null;
32+
} catch {
33+
return null;
34+
}
35+
}
36+
737
export async function loader({ params, request, context }: LoaderFunctionArgs) {
838
try {
939
const slug = params.slug;
@@ -25,6 +55,19 @@ export async function loader({ params, request, context }: LoaderFunctionArgs) {
2555
return Response.json({ error: "Skill not found" }, { status: 404 });
2656
}
2757

58+
// Lazy content fetch: if DB has stub content, pull real SKILL.md from GitHub
59+
if (skill.source_url && isStubContent(skill.content, skill.author)) {
60+
const realContent = await fetchRealContent(skill.source_url);
61+
if (realContent) {
62+
skill.content = realContent;
63+
// Persist to DB so future requests are fast (fire-and-forget)
64+
db.update(skills)
65+
.set({ content: realContent, updated_at: new Date() })
66+
.where(eq(skills.id, skill.id))
67+
.catch(() => {});
68+
}
69+
}
70+
2871
// Fetch reviews with limit
2972
const skillReviews = await db
3073
.select()

0 commit comments

Comments
 (0)