feat: integrate Moodle API services, add RichTextEditor component, and configure environment variables for course management.

This commit is contained in:
sebvtl728
2026-04-09 16:37:09 +02:00
parent a264f9b430
commit 3fabebeb85
18 changed files with 2059 additions and 301 deletions
+193
View File
@@ -0,0 +1,193 @@
import axios from "axios";
import { MOODLE_API_URL, MOODLE_TOKEN } from "../config/moodle";
const ensureConfig = () => {
if (!MOODLE_API_URL || !MOODLE_TOKEN) {
throw new Error(
"Configuration Moodle manquante. Vérifiez VITE_MOODLE_API_URL et VITE_MOODLE_TOKEN."
);
}
};
const appendParam = (searchParams, value, key) => {
if (value === undefined || value === null || key === undefined) {
return;
}
if (Array.isArray(value)) {
value.forEach((entry, index) => {
appendParam(searchParams, entry, `${key}[${index}]`);
});
return;
}
if (typeof value === "object" && !(value instanceof Date)) {
Object.entries(value).forEach(([childKey, childValue]) => {
const nextKey = key ? `${key}[${childKey}]` : childKey;
appendParam(searchParams, childValue, nextKey);
});
return;
}
const normalizedValue =
value instanceof Date ? value.getTime().toString() : String(value);
searchParams.append(key, normalizedValue);
};
export const callMoodle = async (wsfunction, payload = {}, options = {}) => {
ensureConfig();
const params = new URLSearchParams();
params.append("wstoken", MOODLE_TOKEN);
params.append("wsfunction", wsfunction);
params.append("moodlewsrestformat", "json");
Object.entries(payload).forEach(([key, value]) =>
appendParam(params, value, key)
);
const response = await axios.post(MOODLE_API_URL, params, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
timeout: options.timeout ?? 20000,
...options.axios,
});
if (response.data?.exception || response.data?.errorcode) {
const message =
response.data.message ||
response.data.debuginfo ||
response.data.errorcode;
throw new Error(message || "Erreur Moodle inconnue.");
}
return response.data;
};
export const MoodleApi = {
listCourses: (searchValue = "") => {
if (searchValue) {
return callMoodle("core_course_get_courses_by_field", {
field: "search",
value: searchValue,
}).then((data) => data.courses || []);
}
return callMoodle("core_course_get_courses").then(
(data) => data || []
);
},
getCourseById: async (courseId) => {
const response = await callMoodle("core_course_get_courses_by_field", {
field: "id",
value: Number(courseId),
});
return response?.courses?.[0] || null;
},
createCourse: (coursePayload) => {
const normalized = {
summaryformat: 1,
format: "topics",
...coursePayload,
};
if (normalized.categoryid !== undefined) {
normalized.categoryid = Number(normalized.categoryid);
}
return callMoodle("core_course_create_courses", {
courses: [normalized],
});
},
updateCourse: (coursePayload) => {
const normalized = {
summaryformat: 1,
...coursePayload,
};
if (normalized.id !== undefined) {
normalized.id = Number(normalized.id);
}
if (normalized.categoryid !== undefined) {
normalized.categoryid = Number(normalized.categoryid);
}
return callMoodle("core_course_update_courses", {
courses: [normalized],
});
},
deleteCourses: (courseIds) =>
callMoodle("core_course_delete_courses", {
courseids: courseIds,
}),
getCourseContents: (courseId) =>
callMoodle("core_course_get_contents", { courseid: Number(courseId) }),
getPagesByCourse: (courseId) =>
callMoodle("mod_page_get_pages_by_courses", {
courseids: [Number(courseId)],
}).then((data) => data.pages || []),
getAssignmentsByCourse: (courseId) =>
callMoodle("mod_assign_get_assignments", {
courseids: [Number(courseId)],
}).then((data) => {
const normalizedId = Number(courseId);
const course = data?.courses?.find((c) => c.id === normalizedId);
return course?.assignments || [];
}),
getH5PActivitiesByCourse: (courseId) =>
callMoodle("mod_h5pactivity_get_h5pactivities_by_courses", {
courseids: [Number(courseId)],
}).then((data) => data.h5pactivities || []),
getQuizzesByCourse: (courseId) =>
callMoodle("mod_quiz_get_quizzes_by_courses", {
courseids: [Number(courseId)],
}).then((data) => data.quizzes || []),
createSection: (sectionPayload) =>
callMoodle("core_course_create_sections", {
sections: [
{
summaryformat: 1,
...sectionPayload,
},
],
}),
updateSection: (sectionPayload) => {
const payload = {
summaryformat: 1,
...sectionPayload,
};
if (payload.id !== undefined) {
payload.id = Number(payload.id);
}
if (payload.courseid !== undefined) {
payload.courseid = Number(payload.courseid);
}
if (payload.section !== undefined && payload.section !== null) {
const normalizedSection = Number(payload.section);
payload.section = Number.isNaN(normalizedSection)
? undefined
: normalizedSection;
}
return callMoodle("core_course_update_sections", {
sections: [payload],
});
},
deleteSections: (courseId, sectionIds, sectionNumbers) => {
const payload = {
courseid: Number(courseId),
};
if (sectionIds?.length) {
payload.sectionids = sectionIds.map((id) => Number(id));
}
if (sectionNumbers?.length) {
payload.sectionnumbers = sectionNumbers.map((value) => Number(value));
}
return callMoodle("core_course_delete_sections", payload);
},
moveModuleToSection: (moduleId, sectionNumber) => {
const payload = {
cmid: Number(moduleId),
};
if (sectionNumber !== null && sectionNumber !== undefined) {
const normalized = Number(sectionNumber);
if (!Number.isNaN(normalized)) {
payload.section = normalized;
}
}
return callMoodle("core_course_edit_module", payload);
},
};
export default MoodleApi;