Files
classroomio/packages/core/src/services/lesson-language.ts
T
Rotimi Best d5b07d356e feat: HLS streaming, transcripts, agent course generation & landing-page course previews (#663)
New features:
- HLS video encoding/streaming (hls routes, hls-encoder) and a media
  transcripts pipeline (transcripts route + media-transcript queries)
- AI assistant chat vs. run model: agent-course-generation worker/queue,
  run-monitor pane, chat-changes card, content ask-ai bar
- Per-theme course-page previews across org landing-page themes (bold,
  classic, corporate, editorial, etc.) with a shared course token/component set
- View-as-student mode and login-link / link-invite invite flows

Refactor:
- Extract packages/core consolidating config (env, storage, dashboard-url),
  services and utils previously duplicated under apps/api/src/config
- Remove legacy agent service modules (chat-tools, chat-context,
  context-window, document, fetch-url, lesson-content)
- Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding
- Add format script to tenant-router so turbo run format covers it
2026-06-07 18:43:05 +01:00

172 lines
4.8 KiB
TypeScript

import { AppError, ErrorCodes } from '@cio/utils/errors';
import { sanitizeOptionalHtml } from '../utils/sanitize-html';
import type { TLessonLanguage, TLocale, TNewLessonLanguage } from '@cio/db/types';
import {
deleteLessonLanguage,
getLessonLanguageByLessonIdAndLocale,
getLessonLanguagesByLessonId,
updateLessonLanguage,
upsertLessonLanguage
} from '@cio/db/queries/lesson/language';
import { getLessonById } from '@cio/db/queries/lesson/lesson';
import { touchCourseUpdatedAt } from '@cio/db/queries/course';
/**
* Gets all language translations for a lesson
* @param lessonId - The lesson ID
* @returns Array of lesson languages
*/
export async function listLessonLanguages(lessonId: string): Promise<TLessonLanguage[]> {
try {
const languages = await getLessonLanguagesByLessonId(lessonId);
return languages;
} catch (error) {
throw new AppError(
error instanceof Error ? error.message : 'Failed to fetch lesson languages',
ErrorCodes.LESSON_LANGUAGE_FETCH_FAILED,
500
);
}
}
/**
* Gets a single lesson language by locale
* @param lessonId - The lesson ID
* @param locale - The locale
* @returns Lesson language or null if not found
*/
export async function getLessonLanguage(lessonId: string, locale: TLocale): Promise<TLessonLanguage | null> {
try {
const language = await getLessonLanguageByLessonIdAndLocale(lessonId, locale);
return language;
} catch (error) {
throw new AppError(
error instanceof Error ? error.message : 'Failed to fetch lesson language',
ErrorCodes.LESSON_LANGUAGE_FETCH_FAILED,
500
);
}
}
/**
* Creates or updates a lesson language translation
* @param lessonId - The lesson ID
* @param data - Lesson language data
* @returns Created or updated lesson language
*/
export async function upsertLessonLanguageService(
lessonId: string,
data: Omit<TNewLessonLanguage, 'lessonId'>
): Promise<TLessonLanguage> {
try {
// Verify lesson exists
const lesson = await getLessonById(lessonId);
if (!lesson) {
throw new AppError('Lesson not found', ErrorCodes.LESSON_NOT_FOUND, 404);
}
const language = await upsertLessonLanguage({
...data,
content: sanitizeOptionalHtml(data.content),
lessonId
});
if (lesson.courseId) {
await touchCourseUpdatedAt(lesson.courseId);
}
return language;
} catch (error) {
if (error instanceof AppError) {
throw error;
}
throw new AppError(
error instanceof Error ? error.message : 'Failed to create or update lesson language',
ErrorCodes.LESSON_LANGUAGE_CREATE_FAILED,
500
);
}
}
/**
* Updates a lesson language translation
* @param lessonId - The lesson ID
* @param locale - The locale
* @param data - Lesson language update data
* @returns Updated lesson language
*/
export async function updateLessonLanguageService(
lessonId: string,
locale: TLocale,
data: Partial<TNewLessonLanguage>
): Promise<TLessonLanguage> {
try {
// Verify lesson exists
const lesson = await getLessonById(lessonId);
if (!lesson) {
throw new AppError('Lesson not found', ErrorCodes.LESSON_NOT_FOUND, 404);
}
const existing = await getLessonLanguageByLessonIdAndLocale(lessonId, locale);
if (!existing) {
throw new AppError('Lesson language not found', ErrorCodes.LESSON_LANGUAGE_NOT_FOUND, 404);
}
const language = await updateLessonLanguage(lessonId, locale, {
...data,
content: sanitizeOptionalHtml(data.content)
});
if (lesson.courseId) {
await touchCourseUpdatedAt(lesson.courseId);
}
return language;
} catch (error) {
if (error instanceof AppError) {
throw error;
}
throw new AppError(
error instanceof Error ? error.message : 'Failed to update lesson language',
ErrorCodes.LESSON_LANGUAGE_UPDATE_FAILED,
500
);
}
}
export async function deleteLessonLanguageService(lessonId: string, locale: TLocale): Promise<TLessonLanguage> {
try {
const lesson = await getLessonById(lessonId);
if (!lesson) {
throw new AppError('Lesson not found', ErrorCodes.LESSON_NOT_FOUND, 404);
}
const existing = await getLessonLanguageByLessonIdAndLocale(lessonId, locale);
if (!existing) {
throw new AppError('Lesson language not found', ErrorCodes.LESSON_LANGUAGE_NOT_FOUND, 404);
}
const deleted = await deleteLessonLanguage(lessonId, locale);
if (!deleted) {
throw new AppError('Lesson language not found', ErrorCodes.LESSON_LANGUAGE_NOT_FOUND, 404);
}
if (lesson.courseId) {
await touchCourseUpdatedAt(lesson.courseId);
}
return deleted;
} catch (error) {
if (error instanceof AppError) {
throw error;
}
throw new AppError(
error instanceof Error ? error.message : 'Failed to delete lesson language',
ErrorCodes.LESSON_LANGUAGE_DELETE_FAILED,
500
);
}
}