Skip to content

Commit e679ece

Browse files
authored
Merge pull request #6 from cse23-mora/api
feat: add json endpoint for the site
2 parents fb17e55 + 54ef470 commit e679ece

File tree

4 files changed

+466
-0
lines changed

4 files changed

+466
-0
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
---
2+
import { getCollection, type CollectionEntry } from 'astro:content';
3+
4+
export async function getStaticPaths() {
5+
const docs = await getCollection('docs');
6+
7+
return docs
8+
.filter(doc => !doc.id.includes('/index'))
9+
.map((doc) => ({
10+
params: { slug: doc.id },
11+
props: { doc },
12+
}));
13+
}
14+
15+
type Props = {
16+
doc: CollectionEntry<'docs'>;
17+
};
18+
19+
const { doc } = Astro.props;
20+
const { Content } = await doc.render();
21+
22+
// Parse the path to get lesson info
23+
const pathParts = doc.id.split('/');
24+
const lessonTitle = doc.data.title || pathParts[pathParts.length - 1]
25+
.replace(/\.(md|mdx)$/, '')
26+
.split('-')
27+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
28+
.join(' ');
29+
---
30+
31+
<!DOCTYPE html>
32+
<html lang="en">
33+
<head>
34+
<meta charset="UTF-8">
35+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
36+
<title>{lessonTitle}</title>
37+
</head>
38+
<body>
39+
<div class="lesson-content">
40+
<h1>{lessonTitle}</h1>
41+
<Content />
42+
</div>
43+
</body>
44+
</html>
45+
46+
<style>
47+
body {
48+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
49+
line-height: 1.6;
50+
color: #333;
51+
max-width: 800px;
52+
margin: 0 auto;
53+
padding: 2rem;
54+
background: #fff;
55+
}
56+
57+
.lesson-content h1 {
58+
font-size: 2rem;
59+
margin-bottom: 2rem;
60+
color: #1a202c;
61+
border-bottom: 2px solid #f7fafc;
62+
padding-bottom: 0.5rem;
63+
}
64+
65+
:global(.lesson-content h2) {
66+
font-size: 1.5rem;
67+
font-weight: 600;
68+
margin: 1.75rem 0 0.75rem 0;
69+
color: #2d3748;
70+
}
71+
72+
:global(.lesson-content h3) {
73+
font-size: 1.25rem;
74+
font-weight: 600;
75+
margin: 1.5rem 0 0.5rem 0;
76+
color: #4a5568;
77+
}
78+
79+
:global(.lesson-content h4) {
80+
font-size: 1.1rem;
81+
font-weight: 600;
82+
margin: 1.25rem 0 0.5rem 0;
83+
color: #4a5568;
84+
}
85+
86+
:global(.lesson-content p) {
87+
margin: 1rem 0;
88+
color: #2d3748;
89+
}
90+
91+
:global(.lesson-content ul),
92+
:global(.lesson-content ol) {
93+
margin: 1rem 0;
94+
padding-left: 1.5rem;
95+
}
96+
97+
:global(.lesson-content li) {
98+
margin: 0.5rem 0;
99+
color: #2d3748;
100+
}
101+
102+
:global(.lesson-content code) {
103+
background: #f7fafc;
104+
padding: 0.2rem 0.4rem;
105+
border-radius: 3px;
106+
font-size: 0.9em;
107+
color: #e53e3e;
108+
font-family: 'Courier New', monospace;
109+
}
110+
111+
:global(.lesson-content pre) {
112+
background: #1a202c;
113+
color: #f7fafc;
114+
padding: 1rem;
115+
border-radius: 6px;
116+
overflow-x: auto;
117+
margin: 1.5rem 0;
118+
}
119+
120+
:global(.lesson-content pre code) {
121+
background: none;
122+
padding: 0;
123+
color: inherit;
124+
}
125+
126+
:global(.lesson-content blockquote) {
127+
border-left: 4px solid #667eea;
128+
background: #f8fafc;
129+
padding: 1rem;
130+
margin: 1.5rem 0;
131+
font-style: italic;
132+
}
133+
134+
:global(.lesson-content img) {
135+
max-width: 100%;
136+
height: auto;
137+
margin: 1.5rem 0;
138+
border-radius: 6px;
139+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
140+
}
141+
142+
:global(.lesson-content table) {
143+
width: 100%;
144+
border-collapse: collapse;
145+
margin: 1.5rem 0;
146+
background: white;
147+
border: 1px solid #e2e8f0;
148+
border-radius: 6px;
149+
overflow: hidden;
150+
}
151+
152+
:global(.lesson-content th),
153+
:global(.lesson-content td) {
154+
padding: 0.75rem;
155+
text-align: left;
156+
border-bottom: 1px solid #e2e8f0;
157+
}
158+
159+
:global(.lesson-content th) {
160+
background: #f8fafc;
161+
font-weight: 600;
162+
color: #1a202c;
163+
}
164+
165+
:global(.lesson-content a) {
166+
color: #667eea;
167+
text-decoration: none;
168+
}
169+
170+
:global(.lesson-content a:hover) {
171+
text-decoration: underline;
172+
}
173+
174+
:global(.lesson-content strong) {
175+
font-weight: 600;
176+
color: #1a202c;
177+
}
178+
179+
:global(.lesson-content em) {
180+
font-style: italic;
181+
}
182+
183+
@media (max-width: 768px) {
184+
body {
185+
padding: 1rem;
186+
}
187+
188+
.lesson-content h1 {
189+
font-size: 1.75rem;
190+
}
191+
192+
:global(.lesson-content h2) {
193+
font-size: 1.25rem;
194+
}
195+
196+
:global(.lesson-content h3) {
197+
font-size: 1.1rem;
198+
}
199+
200+
:global(.lesson-content pre) {
201+
font-size: 0.85rem;
202+
}
203+
204+
:global(.lesson-content table) {
205+
font-size: 0.9rem;
206+
}
207+
}
208+
</style>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import type { APIRoute } from 'astro';
2+
import { getCollection } from 'astro:content';
3+
4+
export const GET: APIRoute = async ({ params }) => {
5+
try {
6+
const { semester, subject } = params;
7+
8+
if (!semester || !subject) {
9+
return new Response(JSON.stringify({ error: 'Semester and subject parameters are required' }), {
10+
status: 400,
11+
headers: {
12+
'Content-Type': 'application/json'
13+
}
14+
});
15+
}
16+
17+
const docs = await getCollection('docs');
18+
const semesterFilter = `${semester}-semester`;
19+
20+
const lessons = docs
21+
.filter(doc => {
22+
const pathParts = doc.id.split('/');
23+
return pathParts.length > 1 &&
24+
pathParts[0] === semesterFilter &&
25+
pathParts[1] === subject &&
26+
!doc.id.includes('/index');
27+
})
28+
.map(doc => {
29+
const pathParts = doc.id.split('/');
30+
const fileName = pathParts[pathParts.length - 1]
31+
.replace(/\.(md|mdx)$/, '');
32+
33+
const lessonTitle = doc.data.title || fileName
34+
.split('-')
35+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
36+
.join(' ');
37+
38+
// Remove .md extension from URLs
39+
const cleanId = doc.id.replace(/\.md$/, '');
40+
41+
return {
42+
id: doc.id,
43+
slug: cleanId,
44+
title: lessonTitle,
45+
description: doc.data.description || '',
46+
htmlUrl: `/api/lesson/${doc.id}`,
47+
webUrl: `/lesson/${doc.id}`
48+
};
49+
})
50+
.sort((a, b) => a.title.localeCompare(b.title));
51+
52+
// Subject names mapping
53+
const subjectNames: Record<string, string> = {
54+
'data-structures-algorithms': 'Data Structures & Algorithms',
55+
'computer-organization': 'Computer Organization and Digital Design',
56+
'methods-of-mathematics': 'Methods of Mathematics',
57+
'program-construction': 'Program Construction',
58+
'theory-of-electricity': 'Theory of Electricity',
59+
'communication-skills': 'Communication Skills',
60+
'ai': 'AI (Artificial Intelligence)',
61+
'computer-architecture': 'Computer Architecture',
62+
'thermodynamics': 'Thermodynamics',
63+
'database-systems': 'Database Systems',
64+
'differential-equations': 'Differential Equations (DE)',
65+
'operating-systems': 'Operating Systems',
66+
'data-communication-networking': 'Data Communication & Networking',
67+
'applied-statistics': 'Applied Statistics'
68+
};
69+
70+
return new Response(JSON.stringify({
71+
semester: {
72+
id: semesterFilter,
73+
name: semester === '2nd' ? '2nd Semester' : '3rd Semester'
74+
},
75+
subject: {
76+
id: subject,
77+
name: subjectNames[subject] || subject
78+
},
79+
lessons: lessons
80+
}), {
81+
status: 200,
82+
headers: {
83+
'Content-Type': 'application/json'
84+
}
85+
});
86+
} catch (error) {
87+
return new Response(JSON.stringify({ error: 'Failed to fetch lessons' }), {
88+
status: 500,
89+
headers: {
90+
'Content-Type': 'application/json'
91+
}
92+
});
93+
}
94+
};
95+
96+
export async function getStaticPaths() {
97+
const docs = await getCollection('docs');
98+
const semesterSubjects = new Map<string, Set<string>>();
99+
100+
docs.forEach(doc => {
101+
const pathParts = doc.id.split('/');
102+
if (pathParts.length > 1 && pathParts[0].includes('-semester')) {
103+
const semester = pathParts[0].replace('-semester', '');
104+
const subject = pathParts[1];
105+
106+
if (!semesterSubjects.has(semester)) {
107+
semesterSubjects.set(semester, new Set());
108+
}
109+
semesterSubjects.get(semester)!.add(subject);
110+
}
111+
});
112+
113+
const paths: { params: { semester: string; subject: string } }[] = [];
114+
115+
semesterSubjects.forEach((subjects, semester) => {
116+
subjects.forEach(subject => {
117+
paths.push({ params: { semester, subject } });
118+
});
119+
});
120+
121+
return paths;
122+
}

src/pages/api/semesters.json.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { APIRoute } from 'astro';
2+
import { getCollection } from 'astro:content';
3+
4+
export const GET: APIRoute = async () => {
5+
try {
6+
const docs = await getCollection('docs');
7+
8+
const semesters = new Set<string>();
9+
10+
docs.forEach(doc => {
11+
const pathParts = doc.id.split('/');
12+
if (pathParts.length > 0 && pathParts[0].includes('-semester')) {
13+
semesters.add(pathParts[0]);
14+
}
15+
});
16+
17+
const semesterList = Array.from(semesters)
18+
.sort()
19+
.map(semester => {
20+
const semesterId = semester.replace('-semester', '');
21+
return {
22+
id: semester,
23+
name: semester === '2nd-semester' ? '2nd Semester' : '3rd Semester',
24+
apiUrl: `/api/subjects/${semesterId}.json`
25+
};
26+
});
27+
28+
return new Response(JSON.stringify({
29+
semesters: semesterList
30+
}), {
31+
status: 200,
32+
headers: {
33+
'Content-Type': 'application/json'
34+
}
35+
});
36+
} catch (error) {
37+
return new Response(JSON.stringify({ error: 'Failed to fetch semesters' }), {
38+
status: 500,
39+
headers: {
40+
'Content-Type': 'application/json'
41+
}
42+
});
43+
}
44+
};

0 commit comments

Comments
 (0)