162 lines
5.0 KiB
TypeScript
162 lines
5.0 KiB
TypeScript
import { BaseApiWithErrors, classroomio } from '$lib/utils/services/api';
|
|
import type {
|
|
GetOrgCoursesRequest,
|
|
GetOrgCoursesRequestQuery,
|
|
GetRecommendedCoursesRequest,
|
|
GetUserEnrolledCoursesRequest,
|
|
OrgCourses,
|
|
OrgCoursesPagination,
|
|
OrgCoursesQuery,
|
|
RecommendedCourses,
|
|
UserEnrolledCourses
|
|
} from '$features/course/types';
|
|
|
|
import { SvelteSet } from 'svelte/reactivity';
|
|
|
|
/**
|
|
* API class for course operations
|
|
*/
|
|
export class CoursesApi extends BaseApiWithErrors {
|
|
orgCourses = $state<OrgCourses>([]);
|
|
orgCoursesPagination = $state<OrgCoursesPagination | null>(null);
|
|
enrolledCourses = $state<UserEnrolledCourses>([]);
|
|
recommendedCourses = $state<RecommendedCourses>([]);
|
|
private activeOrgCoursesRequestController: AbortController | null = null;
|
|
|
|
cancelOrgCoursesRequest() {
|
|
this.activeOrgCoursesRequestController?.abort();
|
|
this.activeOrgCoursesRequestController = null;
|
|
}
|
|
|
|
/** Keeps list UIs in sync after a course is deleted (server data is updated separately on navigation). */
|
|
removeCourseFromLists(courseId: string) {
|
|
this.orgCourses = this.orgCourses.filter((c) => c.id !== courseId);
|
|
this.enrolledCourses = this.enrolledCourses.filter((c) => c.id !== courseId);
|
|
this.recommendedCourses = this.recommendedCourses.filter((c) => c.id !== courseId);
|
|
}
|
|
|
|
/**
|
|
* Fetches org courses for the current organization
|
|
* Org ID is automatically added from currentOrg store
|
|
*/
|
|
async getOrgCourses(tagSlugs: string[] = []) {
|
|
const allCourses: OrgCourses = [];
|
|
let page = 1;
|
|
let totalPages = 1;
|
|
let lastResponse: Awaited<ReturnType<CoursesApi['getOrgCoursesPage']>> | undefined;
|
|
|
|
while (page <= totalPages) {
|
|
const response = await this.getOrgCoursesPage({ page, limit: 100 }, tagSlugs);
|
|
if (!response) {
|
|
return response;
|
|
}
|
|
|
|
allCourses.push(...(response.data ?? []));
|
|
totalPages = response.pagination?.totalPages ?? 1;
|
|
lastResponse = response;
|
|
page += 1;
|
|
}
|
|
|
|
this.orgCourses = allCourses;
|
|
this.orgCoursesPagination = lastResponse?.pagination ?? null;
|
|
|
|
return lastResponse;
|
|
}
|
|
|
|
async getOrgCoursesPage(
|
|
query: Partial<OrgCoursesQuery> = {},
|
|
tagSlugs: string[] = [],
|
|
options: { abortPrevious?: boolean; signal?: AbortSignal } = {}
|
|
) {
|
|
const normalizedTagSlugs = Array.from(new SvelteSet(tagSlugs.map((tag) => tag.trim()).filter(Boolean)));
|
|
const requestQuery: GetOrgCoursesRequestQuery = {
|
|
page: String(query.page ?? 1),
|
|
limit: String(query.limit ?? 20),
|
|
search: query.search,
|
|
tags: normalizedTagSlugs.length > 0 ? normalizedTagSlugs.join(',') : undefined
|
|
};
|
|
|
|
let requestSignal = options.signal;
|
|
let requestController: AbortController | null = null;
|
|
|
|
if (options.abortPrevious) {
|
|
this.cancelOrgCoursesRequest();
|
|
requestController = new AbortController();
|
|
this.activeOrgCoursesRequestController = requestController;
|
|
requestSignal = requestController.signal;
|
|
}
|
|
|
|
const response = await this.execute<GetOrgCoursesRequest>({
|
|
requestFn: () =>
|
|
classroomio.organization.courses.$get(
|
|
{
|
|
query: requestQuery
|
|
},
|
|
{
|
|
init: {
|
|
signal: requestSignal
|
|
}
|
|
}
|
|
),
|
|
logContext: 'fetching org courses',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.orgCourses = response.data;
|
|
}
|
|
|
|
this.orgCoursesPagination = response.pagination;
|
|
}
|
|
});
|
|
|
|
if (requestController && this.activeOrgCoursesRequestController === requestController) {
|
|
this.activeOrgCoursesRequestController = null;
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Fetches user enrolled courses for the current organization
|
|
* Org ID is automatically added from currentOrg store
|
|
*/
|
|
async getEnrolledCourses() {
|
|
return this.execute<GetUserEnrolledCoursesRequest>({
|
|
requestFn: () => classroomio.organization.courses.enrolled.$get({}),
|
|
logContext: 'fetching enrolled courses',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.enrolledCourses = response.data;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetches recommended courses (published courses user isn't enrolled in) for the current organization
|
|
* Org ID is automatically added from currentOrg store
|
|
*/
|
|
async getRecommendedCourses() {
|
|
await this.execute<GetRecommendedCoursesRequest>({
|
|
requestFn: () => classroomio.organization.courses.recommended.$get({}),
|
|
logContext: 'fetching recommended courses',
|
|
onSuccess: (response) => {
|
|
if (response.data) {
|
|
this.recommendedCourses = response.data;
|
|
}
|
|
this.errors = {};
|
|
},
|
|
onError: (result) => {
|
|
if (typeof result === 'string') {
|
|
console.error('Failed to fetch recommended courses:', result);
|
|
return;
|
|
}
|
|
if ('error' in result) {
|
|
console.error('Failed to fetch recommended courses:', result.error);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
export const coursesApi = new CoursesApi();
|