This commit is contained in:
115
server/src/routes/courses.ts
Normal file
115
server/src/routes/courses.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { FastifyPluginAsync } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import { pool } from '../db/pool.js';
|
||||
|
||||
const paramsSchema = z.object({
|
||||
courseId: z.string().uuid(),
|
||||
});
|
||||
|
||||
export const courseSelect = (publicOnly = false) => `
|
||||
select
|
||||
c.id,
|
||||
c.slug,
|
||||
c.title,
|
||||
c.description,
|
||||
c.category,
|
||||
c.cover_image_url as "coverImageUrl",
|
||||
c.status,
|
||||
c.published_at as "publishedAt",
|
||||
jsonb_build_object(
|
||||
'id', u.id,
|
||||
'name', u.display_name
|
||||
) as instructor,
|
||||
coalesce((
|
||||
select jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', l.id,
|
||||
'title', l.title,
|
||||
'description', l.description,
|
||||
'position', l.position,
|
||||
'durationSeconds', l.duration_seconds,
|
||||
'accessLevel', l.access_level,
|
||||
'assets', coalesce((
|
||||
select jsonb_agg(jsonb_build_object(
|
||||
'id', a.id,
|
||||
'name', a.name,
|
||||
'kind', a.kind,
|
||||
'url', a.url,
|
||||
'description', a.description,
|
||||
'accessLevel', a.access_level
|
||||
) order by a.created_at)
|
||||
from assets a
|
||||
where a.lesson_id = l.id ${publicOnly ? "and a.access_level = 'public'" : ''}
|
||||
), '[]'::jsonb),
|
||||
'media', coalesce((
|
||||
select jsonb_agg(
|
||||
jsonb_build_object(
|
||||
'id', lm.id,
|
||||
'provider', lm.provider,
|
||||
'status', lm.status,
|
||||
'embedUrl', lm.embed_url,
|
||||
'playbackUrl', lm.playback_url,
|
||||
'durationSeconds', lm.duration_seconds
|
||||
) order by lm.created_at
|
||||
)
|
||||
from lesson_media lm
|
||||
where lm.lesson_id = l.id and lm.status = 'ready'
|
||||
), '[]'::jsonb)
|
||||
) order by l.position
|
||||
)
|
||||
from lessons l
|
||||
where l.course_id = c.id ${publicOnly ? "and l.access_level = 'public'" : ''}
|
||||
), '[]'::jsonb) as lessons
|
||||
, coalesce((
|
||||
select jsonb_agg(jsonb_build_object(
|
||||
'id', a.id,
|
||||
'name', a.name,
|
||||
'kind', a.kind,
|
||||
'url', a.url,
|
||||
'description', a.description,
|
||||
'accessLevel', a.access_level
|
||||
) order by a.created_at)
|
||||
from assets a
|
||||
where a.course_id = c.id ${publicOnly ? "and a.access_level = 'public'" : ''}
|
||||
), '[]'::jsonb) as assets
|
||||
from courses c
|
||||
join users u on u.id = c.instructor_id
|
||||
`;
|
||||
|
||||
export const courseRoutes: FastifyPluginAsync = async (app) => {
|
||||
const withoutProtectedMedia = (course: Record<string, unknown>) => ({
|
||||
...course,
|
||||
assets: [],
|
||||
lessons: Array.isArray(course.lessons)
|
||||
? course.lessons.map((lesson) => ({ ...lesson, media: [], assets: [] }))
|
||||
: [],
|
||||
});
|
||||
|
||||
const hasSession = async (request: { jwtVerify: () => Promise<unknown> }) => {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/', async (request) => {
|
||||
const authenticated = await hasSession(request);
|
||||
const result = await pool.query(`${courseSelect()} where c.status = 'published' order by c.published_at desc`);
|
||||
return { data: authenticated ? result.rows : result.rows.map(withoutProtectedMedia) };
|
||||
});
|
||||
|
||||
app.get('/:courseId', async (request, reply) => {
|
||||
const authenticated = await hasSession(request);
|
||||
const { courseId } = paramsSchema.parse(request.params);
|
||||
const result = await pool.query(`${courseSelect()} where c.id = $1 and c.status = 'published'`, [courseId]);
|
||||
const course = result.rows[0];
|
||||
|
||||
if (!course) {
|
||||
return reply.code(404).send({ error: 'Course not found' });
|
||||
}
|
||||
|
||||
return { data: authenticated ? course : withoutProtectedMedia(course) };
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user