100 lines
2.6 KiB
TypeScript
100 lines
2.6 KiB
TypeScript
import type {
|
|||
|
|
TCourseImportDraftCreate,
|
||
|
|
TCourseImportDraftPublish,
|
||
|
|
TCourseImportDraftUpdate
|
||
|
|
} from '@cio/utils/validation/course-import';
|
||
|
|
|
||
|
|
import type { McpServerConfig } from './config.js';
|
||
|
|
|
||
|
|
type ApiSuccess<T> = {
|
||
|
|
success: true;
|
||
|
|
data: T;
|
||
|
|
};
|
||
|
|
|
||
|
|
type ApiFailure = {
|
||
|
|
success?: false;
|
||
|
|
error?: string;
|
||
|
|
message?: string;
|
||
|
|
code?: string;
|
||
|
|
field?: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
export class ClassroomIoApiError extends Error {
|
||
|
|
constructor(
|
||
|
|
message: string,
|
||
|
|
public status: number,
|
||
|
|
public code?: string,
|
||
|
|
public field?: string
|
||
|
|
) {
|
||
|
|
super(message);
|
||
|
|
this.name = 'ClassroomIoApiError';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export class ClassroomIoApiClient {
|
||
|
|
constructor(private readonly config: McpServerConfig) {}
|
||
|
|
|
||
|
|
async createCourseDraft(payload: TCourseImportDraftCreate) {
|
||
|
|
return this.request('/organization/course-import/drafts', {
|
||
|
|
method: 'POST',
|
||
|
|
body: payload
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async getCourseDraft(draftId: string) {
|
||
|
|
return this.request(`/organization/course-import/drafts/${draftId}`, {
|
||
|
|
method: 'GET'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async updateCourseDraft(draftId: string, payload: TCourseImportDraftUpdate) {
|
||
|
|
return this.request(`/organization/course-import/drafts/${draftId}`, {
|
||
|
|
method: 'PUT',
|
||
|
|
body: payload
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async publishCourseDraft(draftId: string, payload: TCourseImportDraftPublish) {
|
||
|
|
return this.request(`/organization/course-import/drafts/${draftId}/publish`, {
|
||
|
|
method: 'POST',
|
||
|
|
body: payload
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private async request<TResponse>(
|
||
|
|
path: string,
|
||
|
|
options: {
|
||
|
|
method: 'GET' | 'POST' | 'PUT';
|
||
|
|
body?: unknown;
|
||
|
|
}
|
||
|
|
): Promise<TResponse> {
|
||
|
|
const response = await fetch(new URL(path, this.config.CLASSROOMIO_API_URL), {
|
||
|
|
method: options.method,
|
||
|
|
headers: {
|
||
|
|
Authorization: `Bearer ${this.config.CLASSROOMIO_API_KEY}`,
|
||
|
|
'content-type': 'application/json',
|
||
|
|
'user-agent': this.config.CLASSROOMIO_USER_AGENT
|
||
|
|
},
|
||
|
|
body: options.body ? JSON.stringify(options.body) : undefined
|
||
|
|
});
|
||
|
|
|
||
|
|
const json = (await response.json().catch(() => null)) as ApiSuccess<TResponse> | ApiFailure | null;
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const errorPayload = json as ApiFailure | null;
|
||
|
|
throw new ClassroomIoApiError(
|
||
|
|
errorPayload?.error ?? errorPayload?.message ?? `ClassroomIO request failed with status ${response.status}`,
|
||
|
|
response.status,
|
||
|
|
errorPayload?.code,
|
||
|
|
errorPayload?.field
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!json || typeof json !== 'object' || !('success' in json) || !json.success) {
|
||
|
|
throw new ClassroomIoApiError('ClassroomIO returned an invalid response payload', response.status);
|
||
|
|
}
|
||
|
|
|
||
|
|
return json.data;
|
||
|
|
}
|
||
|
|
}
|