-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathapi.ts
More file actions
93 lines (82 loc) · 2.76 KB
/
api.ts
File metadata and controls
93 lines (82 loc) · 2.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { marked } from 'marked';
import type { GitHubIssue, BlogPost, BlogListResponse } from './types.js';
const GITHUB_REPO = 'Local-Connectivity-Lab/Local-Connectivity-Lab.github.io';
const BLOG_LABEL = 'blog';
// Configure marked for safe HTML output
marked.setOptions({
gfm: true,
breaks: true,
sanitize: false, // We trust GitHub issue content
smartypants: true
});
/**
* Generate a URL-friendly slug from a blog post title
*/
export function generateSlug(title: string): string {
return title
.toLowerCase()
.replace(/[^\w\s-]/g, '') // Remove special characters
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Replace multiple hyphens with single
.replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
}
/**
* Transform a GitHub issue into a blog post
*/
export function transformIssueToBlogPost(issue: GitHubIssue): BlogPost {
const content = issue.body || '';
const htmlContent = marked.parse(content) as string;
return {
id: issue.id,
number: issue.number,
title: issue.title,
content,
htmlContent,
slug: generateSlug(issue.title),
publishedAt: issue.created_at,
updatedAt: issue.updated_at,
author: {
name: issue.user.login,
avatar: issue.user.avatar_url,
url: issue.user.html_url
},
githubUrl: issue.html_url,
labels: issue.labels.map(label => label.name)
};
}
/**
* Fetch blog posts from GitHub issues with the blog label
*/
export async function fetchBlogPosts(fetchFn: typeof fetch = fetch): Promise<BlogListResponse> {
const url = `https://api.github.com/repos/${GITHUB_REPO}/issues?labels=${BLOG_LABEL}&state=open&sort=created&direction=desc`;
try {
const response = await fetchFn(url, {
headers: {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'Local-Connectivity-Lab-Website'
}
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
const issues: GitHubIssue[] = await response.json();
const posts = issues.map(transformIssueToBlogPost);
return {
posts,
total: posts.length
};
} catch (error) {
console.error('Error fetching blog posts:', error);
return {
posts: [],
total: 0
};
}
}
/**
* Find a blog post by its slug
*/
export async function findBlogPostBySlug(slug: string, fetchFn: typeof fetch = fetch): Promise<BlogPost | null> {
const { posts } = await fetchBlogPosts(fetchFn);
return posts.find(post => post.slug === slug) || null;
}