diff --git a/config.json b/config.json deleted file mode 100644 index abdcffd..0000000 --- a/config.json +++ /dev/null @@ -1,1178 +0,0 @@ -{ - "/login/token.php": [ - { - "path": "/auth", - "method": "GET", - "function": "auth", - "description": "Get Moodle token for API calls.", - "tags": ["Authentication"], - "query_params": [ - { - "name": "username", - "type": "str", - "required": true, - "description": "Moodle instance username" - }, - { - "name": "password", - "type": "str", - "required": true, - "description": "Moodle instance password" - }, - { - "name": "service", - "type": "str", - "required": false, - "default": "moodle_mobile_app", - "description": "Web service name" - } - ], - "responses": { - "200": { - "description": "Token response", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "token": { "type": "string" }, - "privatetoken": { "type": "string" } - } - }, - "example": { - "token": "abcdef1234567890", - "privatetoken": "abcdef1234567890_priv" - } - } - } - }, - "422": { - "description": "Invalid Credentials", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { "type": "string" }, - "errorcode": { "type": "string" }, - "stacktrace": { "type": ["string", "null"] }, - "debuginfo": { "type": ["string", "null"] }, - "reproductionlink": { "type": ["string", "null"] } - } - }, - "example": { - "error": "Invalid login", - "errorcode": "invalidlogin", - "stacktrace": null, - "debuginfo": null, - "reproductionlink": null - } - } - } - } - } - } - ], - "/webservice/rest/server.php": [ - { - "path": "/core_webservice_get_site_info", - "method": "GET", - "function": "core_webservice_get_site_info", - "description": "Get Moodle site information & user information", - "tags": ["Core"], - "query_params": [], - "responses": { - "200": { - "description": "Site and user information", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "sitename": { "type": "string" }, - "username": { "type": "string" }, - "firstname": { "type": "string" }, - "lastname": { "type": "string" }, - "fullname": { "type": "string" }, - "lang": { "type": "string" }, - "userid": { "type": "integer" }, - "siteurl": { "type": "string" }, - "userpictureurl": { "type": "string" }, - "usercanmanageownfiles": { "type": "boolean" }, - "userquota": { "type": "integer" }, - "usermaxuploadfilesize": { "type": "integer" }, - "userhomepage": { "type": "integer" }, - "userprivateaccesskey": { "type": "string" }, - "siteid": { "type": "integer" }, - "sitecalendartype": { "type": "string" }, - "usercalendartype": { "type": "string" }, - "userissiteadmin": { "type": "boolean" }, - "theme": { "type": "string" }, - "limitconcurrentlogins": { "type": "integer" }, - "policyagreed": { "type": "integer" } - } - }, - "example": { - "sitename": "School XYZ", - "username": "jdoe", - "firstname": "John", - "lastname": "Doe", - "fullname": "John Doe", - "lang": "en", - "userid": 42, - "siteurl": "https://moodle.example.com", - "userpictureurl": "https://moodle.example.com/theme/image.php/boost/core/1690000000/u/f1", - "usercanmanageownfiles": true, - "userquota": 104857600, - "usermaxuploadfilesize": 10485760, - "userhomepage": 0, - "userprivateaccesskey": "abcdef1234567890key", - "siteid": 1, - "sitecalendartype": "gregorian", - "usercalendartype": "gregorian", - "userissiteadmin": false, - "theme": "boost", - "limitconcurrentlogins": 1, - "policyagreed": 1 - } - } - } - }, - "422": { - "description": "Invalid Request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "error": { "type": "string" }, - "errorcode": { "type": "string" }, - "stacktrace": { "type": ["string", "null"] }, - "debuginfo": { "type": ["string", "null"] }, - "reproductionlink": { "type": ["string", "null"] } - } - }, - "example": { - "error": "Invalid parameter value detected", - "errorcode": "invalidparameter", - "stacktrace": null, - "debuginfo": null, - "reproductionlink": null - } - } - } - } - } - }, - - { - "path": "/core_course_get_contents", - "method": "GET", - "function": "core_course_get_contents", - "description": "Get course contents (sections and activities)", - "tags": ["Courses"], - "query_params": [ - { - "name": "courseid", - "type": "int", - "required": true, - "description": "Course ID" - } - ] - }, - - { - "path": "/core_course_search_courses", - "method": "GET", - "function": "core_course_search_courses", - "description": "Search courses by criteria", - "tags": ["Courses"], - "query_params": [ - { - "name": "criterianame", - "type": "str", - "required": false, - "default": "search", - "description": "Search criteria name" - }, - { - "name": "criteriavalue", - "type": "str", - "required": true, - "description": "Search term" - }, - { - "name": "page", - "type": "int", - "required": false, - "default": 0, - "description": "Page number" - }, - { - "name": "perpage", - "type": "int", - "required": false, - "default": 100, - "description": "Number of results per page" - }, - { - "name": "limittoenrolled", - "type": "int", - "required": false, - "default": 0, - "description": "Limit to enrolled courses only" - } - ] - }, - - { - "path": "/core_course_get_courses_by_field", - "method": "GET", - "function": "core_course_get_courses_by_field", - "description": "Get courses by field (id, shortname, fullname, etc.)", - "tags": ["Courses"], - "query_params": [ - { - "name": "field", - "type": "str", - "required": true, - "description": "Field to search by" - }, - { - "name": "value", - "type": "str", - "required": true, - "description": "Value to search for" - } - ] - }, - - { - "path": "/core_course_get_categories", - "method": "GET", - "function": "core_course_get_categories", - "description": "Get course categories", - "tags": ["Courses"], - "query_params": [], - "responses": { - "200": { - "description": "List of course categories", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": {"type": "integer"}, - "name": {"type": "string"}, - "description": {"type": "string"}, - "descriptionformat": {"type": "integer"}, - "parent": {"type": "integer"}, - "sortorder": {"type": "integer"}, - "coursecount": {"type": "integer"}, - "depth": {"type": "integer"}, - "path": {"type": "string"} - } - } - }, - "example": [ - { - "id": 60, - "name": "ABU-SOL", - "description": "

ABU-Moodle-Kurse des Projekts nachhaltige Lernorganisationsformen, in denen begleitetes selbstorganisisertes Lernen (SOL) und eine Einführung des selbstgesteuerten Lernen in einem Pilotversuch getestet wird.

", - "descriptionformat": 1, - "parent": 0, - "sortorder": 10000, - "coursecount": 4, - "depth": 1, - "path": "/60" - }, - { - "id": 48, - "name": "Automatikmonteur", - "description": "Sammelgefäss für alle Kurse des Berufs Automatikmonteur", - "descriptionformat": 1, - "parent": 0, - "sortorder": 100000, - "coursecount": 10, - "depth": 1, - "path": "/48" - }, - { - "id": 17, - "name": "Automobil", - "description": "", - "descriptionformat": 1, - "parent": 0, - "sortorder": 110000, - "coursecount": 0, - "depth": 1, - "path": "/17" - } - ] - } - } - } - } - }, - - { - "path": "/core_enrol_get_enrolled_users", - "method": "GET", - "function": "core_enrol_get_enrolled_users", - "description": "Get users enrolled in a course", - "tags": ["Enrollment"], - "query_params": [ - { - "name": "courseid", - "type": "int", - "required": true, - "description": "Course ID" - } - ] - }, - - { - "path": "/core_enrol_get_users_courses", - "method": "GET", - "function": "core_enrol_get_users_courses", - "description": "Get courses the user is enrolled in", - "tags": ["Enrollment"], - "query_params": [ - { - "name": "userid", - "type": "int", - "required": false, - "default": 0, - "description": "User ID (0 for current user)" - } - ] - }, - - { - "path": "/core_files_get_files", - "method": "GET", - "function": "core_files_get_files", - "description": "Get files from specified context", - "tags": ["Files"], - "query_params": [ - { - "name": "contextid", - "type": "int", - "required": true, - "description": "Context ID" - }, - { - "name": "component", - "type": "str", - "required": true, - "description": "Component name" - }, - { - "name": "filearea", - "type": "str", - "required": true, - "description": "File area" - }, - { - "name": "itemid", - "type": "int", - "required": false, - "default": 0, - "description": "Item ID" - }, - { - "name": "filepath", - "type": "str", - "required": false, - "default": "/", - "description": "File path" - }, - { - "name": "filename", - "type": "str", - "required": false, - "default": "", - "description": "File name" - } - ] - }, - - { - "path": "/core_message_get_messages", - "method": "GET", - "function": "core_message_get_messages", - "description": "Get messages", - "tags": ["Messages"], - "query_params": [ - { - "name": "useridto", - "type": "int", - "required": true, - "description": "User ID to (recipient)" - }, - { - "name": "useridfrom", - "type": "int", - "required": false, - "description": "User ID from (sender)" - }, - { - "name": "type", - "type": "str", - "required": false, - "default": "both", - "description": "Message type (sent, received, both)" - }, - { - "name": "read", - "type": "bool", - "required": false, - "description": "Read status" - }, - { - "name": "newestfirst", - "type": "bool", - "required": false, - "default": true, - "description": "Sort newest first" - }, - { - "name": "limitfrom", - "type": "int", - "required": false, - "default": 0, - "description": "Limit from" - }, - { - "name": "limitnum", - "type": "int", - "required": false, - "default": 100, - "description": "Number of messages" - } - ] - }, - - { - "path": "/core_user_get_course_user_profiles", - "method": "GET", - "function": "core_user_get_course_user_profiles", - "description": "Get user profiles for users in a course", - "tags": ["Users"], - "query_params": [ - { - "name": "courseid", - "type": "int", - "required": true, - "description": "Course ID" - } - ] - }, - - { - "path": "/core_user_get_users_by_field", - "method": "GET", - "function": "core_user_get_users_by_field", - "description": "Get users by field (id, username, email, etc.)", - "tags": ["Users"], - "query_params": [ - { - "name": "field", - "type": "str", - "required": true, - "description": "Field to search by" - }, - { - "name": "values", - "type": "list", - "required": true, - "description": "Values to search for" - } - ] - }, - - { - "path": "/core_user_get_user_preferences", - "method": "GET", - "function": "core_user_get_user_preferences", - "description": "Get user preferences", - "tags": ["Users"], - "query_params": [ - { - "name": "userid", - "type": "int", - "required": false, - "description": "User ID (optional)" - }, - { - "name": "name", - "type": "str", - "required": false, - "description": "Preference name" - } - ] - }, - - { - "path": "/enrol_self_enrol_user", - "method": "GET", - "function": "enrol_self_enrol_user", - "description": "Self-enrol user in a course | unenrol by using function on enrolled course", - "tags": ["Enrollment"], - "query_params": [ - { - "name": "courseid", - "type": "int", - "required": true, - "description": "Course ID" - } - ] - }, - - { - "path": "/gradereport_user_get_grade_items", - "method": "GET", - "function": "gradereport_user_get_grade_items", - "description": "Get grade items for a course", - "tags": ["Grades"], - "query_params": [ - { - "name": "courseid", - "type": "int", - "required": true, - "description": "Course ID" - }, - { - "name": "userid", - "type": "int", - "required": false, - "default": 0, - "description": "User ID" - } - ] - }, - - { - "path": "/mod_assign_get_assignments", - "method": "GET", - "function": "mod_assign_get_assignments", - "description": "Get assignments from specified courses", - "tags": ["Assignments"], - "query_params": [ - { - "name": "courseids", - "type": "list", - "required": true, - "description": "List of course IDs" - } - ] - }, - - { - "path": "/mod_assign_get_submissions", - "method": "GET", - "function": "mod_assign_get_submissions", - "description": "Get assignment submissions", - "tags": ["Assignments"], - "query_params": [ - { - "name": "assignmentids", - "type": "list", - "required": true, - "description": "Assignment IDs" - } - ] - }, - - { - "path": "/mod_assign_get_submission_status", - "method": "GET", - "function": "mod_assign_get_submission_status", - "description": "Get assignment submission status", - "tags": ["Assignments"], - "query_params": [ - { - "name": "assignid", - "type": "int", - "required": true, - "description": "Assignment ID" - }, - { - "name": "userid", - "type": "int", - "required": false, - "description": "User ID (optional)" - } - ] - }, - - { - "path": "/mod_forum_get_forums_by_courses", - "method": "GET", - "function": "mod_forum_get_forums_by_courses", - "description": "Get forums in specified courses", - "tags": ["Forums"], - "query_params": [ - { - "name": "courseids", - "type": "list", - "required": true, - "description": "List of course IDs" - } - ] - }, - - { - "path": "/mod_forum_get_forum_discussions", - "method": "GET", - "function": "mod_forum_get_forum_discussions", - "description": "Get discussions in a forum", - "tags": ["Forums"], - "query_params": [ - { - "name": "forumid", - "type": "int", - "required": true, - "description": "Forum ID" - } - ] - }, - - { - "path": "/core_calendar_get_calendar_events", - "method": "GET", - "function": "core_calendar_get_calendar_events", - "description": "Get calendar events", - "tags": ["Calendar"], - "query_params": [ - { - "name": "courseid", - "type": "int", - "required": false, - "description": "Course ID (optional)" - } - ] - }, - - { - "path": "/core_calendar_get_action_events_by_course", - "method": "GET", - "function": "core_calendar_get_action_events_by_course", - "description": "Get action events (assignments, quizzes) by course", - "tags": ["Calendar"], - "query_params": [ - { - "name": "courseid", - "type": "int", - "required": true, - "description": "Course ID" - }, - { - "name": "timesortfrom", - "type": "int", - "required": false, - "description": "Time sort from (timestamp)" - }, - { - "name": "timesortto", - "type": "int", - "required": false, - "description": "Time sort to (timestamp)" - }, - { - "name": "aftereventid", - "type": "int", - "required": false, - "description": "After event ID" - }, - { - "name": "limitnum", - "type": "int", - "required": false, - "default": 20, - "description": "Number of events to return" - } - ] - }, - - { - "path": "/mod_quiz_get_quizzes_by_courses", - "method": "GET", - "function": "mod_quiz_get_quizzes_by_courses", - "description": "Get quizzes in specified courses", - "tags": ["Quizzes"], - "query_params": [ - { - "name": "courseids", - "type": "list", - "required": true, - "description": "List of course IDs" - } - ], - "responses": { - "200": { - "description": "List of quizzes for the given courses", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "quizzes": { "type": "array", "items": { "type": "object" } }, - "warnings": { "type": "array", "items": { "type": "object" } } - }, - "required": ["quizzes"] - }, - "example": { - "quizzes": [ - { - "id": 1001, - "coursemodule": 2001, - "course": 42, - "name": "Chemistry Homework Quiz", - "intro": "

Please answer all questions.

", - "introformat": 1, - "introfiles": [], - "section": 1, - "visible": true, - "groupmode": 0, - "groupingid": 0, - "lang": "en", - "timeopen": 0, - "timeclose": 0, - "timelimit": 0, - "overduehandling": "autoabandon", - "graceperiod": 0, - "preferredbehaviour": "deferredfeedback", - "canredoquestions": 0, - "attempts": 0, - "attemptonlast": 0, - "grademethod": 1, - "decimalpoints": 2, - "questiondecimalpoints": -1, - "reviewattempt": 69904, - "reviewcorrectness": 4368, - "reviewmaxmarks": 69904, - "reviewmarks": 4368, - "reviewspecificfeedback": 4368, - "reviewgeneralfeedback": 4368, - "reviewrightanswer": 4368, - "reviewoverallfeedback": 4368, - "questionsperpage": 1, - "navmethod": "free", - "sumgrades": 5, - "grade": 5, - "browsersecurity": "-", - "delay1": 0, - "delay2": 0, - "showuserpicture": 0, - "showblocks": 0, - "completionattemptsexhausted": 0, - "completionpass": 0, - "allowofflineattempts": 0, - "autosaveperiod": 60, - "hasfeedback": 0, - "hasquestions": 1 - }, - { - "id": 1002, - "coursemodule": 2002, - "course": 42, - "name": "Math Practice Quiz", - "intro": "

Practice questions for the exam.

", - "introformat": 1, - "introfiles": [], - "section": 1, - "visible": true, - "groupmode": 0, - "groupingid": 0, - "lang": "en", - "timeopen": 0, - "timeclose": 0, - "timelimit": 900, - "preferredbehaviour": "deferredfeedback", - "attempts": 2, - "grademethod": 4, - "decimalpoints": 2, - "questiondecimalpoints": -1, - "sumgrades": 4, - "grade": 4, - "hasfeedback": 0 - }, - { - "id": 1003, - "coursemodule": 2003, - "course": 42, - "name": "Biology Final Quiz", - "intro": "

Final quiz for the course.

", - "introformat": 1, - "introfiles": [], - "section": 2, - "visible": true, - "groupmode": 0, - "groupingid": 0, - "lang": "en", - "timeopen": 1710000000, - "timeclose": 1710600000, - "timelimit": 2400, - "preferredbehaviour": "immediatefeedback", - "attempts": 1, - "grademethod": 1, - "decimalpoints": 2, - "questiondecimalpoints": -1, - "sumgrades": 10, - "grade": 10, - "hasfeedback": 0 - } - ], - "warnings": [] - } - } - } - }, - "422": { - "description": "Invalid Request", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { "error": "invalidparameter", "errorcode": "invalidparameter" } - } - } - } - } - }, - - { - "path": "/mod_quiz_get_quiz_access_information", - "method": "GET", - "function": "mod_quiz_get_quiz_access_information", - "description": "Get quiz access information", - "tags": ["Quizzes"], - "query_params": [ - { - "name": "quizid", - "type": "int", - "required": true, - "description": "Quiz ID" - } - ], - "responses": { - "200": { - "description": "Quiz access info for current user", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "canattempt": {"type": "boolean"}, - "canmanage": {"type": "boolean"}, - "canpreview": {"type": "boolean"}, - "canreviewmyattempts": {"type": "boolean"}, - "canviewreports": {"type": "boolean"}, - "accessrules": {"type": "array", "items": {"type": "string"}}, - "activerulenames": {"type": "array", "items": {"type": "string"}}, - "preventaccessreasons": {"type": "array", "items": {"type": "string"}}, - "warnings": {"type": "array", "items": {"type": "object"}} - }, - "required": ["canattempt", "accessrules", "warnings"] - }, - "example": { - "canattempt": true, - "canmanage": false, - "canpreview": false, - "canreviewmyattempts": true, - "canviewreports": false, - "accessrules": [], - "activerulenames": ["quizaccess_openclosedate"], - "preventaccessreasons": [], - "warnings": [] - } - } - } - } - } - }, - - { - "path": "/mod_quiz_view_quiz", - "method": "GET", - "function": "mod_quiz_view_quiz", - "description": "Log that a user viewed the quiz (for analytics).", - "tags": ["Quizzes"], - "query_params": [ - { "name": "quizid", "type": "int", "required": true, "description": "Quiz ID" } - ], - "responses": { - "200": { - "description": "View logged", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { - "status": true, - "warnings": [] - } - } - } - } - } - }, - - { - "path": "/mod_quiz_start_attempt", - "method": "GET", - "function": "mod_quiz_start_attempt", - "description": "Start a new quiz attempt", - "tags": ["Quizzes"], - "query_params": [ - { "name": "quizid", "type": "int", "required": true, "description": "Quiz ID" }, - { - "name": "preflightdata", - "type": "list", - "required": false, - "default": [], - "description": "Pre-flight data as list of {name, value} objects (JSON or CSV)." - }, - { "name": "forcenew", "type": "bool", "required": false, "default": false, "description": "Force new attempt." } - ], - "responses": { - "200": { - "description": "Attempt started", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { - "attempt": { "id": 101, "quiz": 15, "state": "inprogress", "timestart": 1694350000, "layout": "1,2,3,0" }, - "warnings": [] - } - } - } - } - } - }, - - { - "path": "/mod_quiz_get_attempt_data", - "method": "GET", - "function": "mod_quiz_get_attempt_data", - "description": "Get quiz attempt data including questions", - "tags": ["Quizzes"], - "query_params": [ - { "name": "attemptid", "type": "int", "required": true, "description": "Attempt ID" }, - { "name": "page", "type": "int", "required": false, "default": 0, "description": "Page number (-1 for all pages)" }, - { - "name": "preflightdata", - "type": "list", - "required": false, - "default": [], - "description": "Pre-flight data as list of {name, value} objects (JSON)." - } - ], - "responses": { - "200": { - "description": "Attempt data including question states and HTML.", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { - "attempt": { "id": 101, "state": "inprogress" }, - "questions": [ - { "slot": 1, "type": "multichoice", "html": "
Question 1 HTML
", "name": "Q1" } - ], - "warnings": [] - } - } - } - } - } - }, - - { - "path": "/mod_quiz_save_attempt", - "method": "GET", - "function": "mod_quiz_save_attempt", - "description": "Save quiz attempt responses", - "tags": ["Quizzes"], - "query_params": [ - { "name": "attemptid", "type": "int", "required": true, "description": "Attempt ID" }, - { - "name": "data", - "type": "list", - "required": true, - "description": "Responses as list of {name, value} objects, e.g. [{\"name\":\"q1:1_answer\",\"value\":\"2\"}]." - }, - { - "name": "preflightdata", - "type": "list", - "required": false, - "default": [], - "description": "Pre-flight data as list of {name, value} objects" - } - ], - "responses": { - "200": { - "description": "Save result", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { "status": true, "warnings": [] } - } - } - } - } - }, - - { - "path": "/mod_quiz_process_attempt", - "method": "GET", - "function": "mod_quiz_process_attempt", - "description": "Process and optionally finish a quiz attempt", - "tags": ["Quizzes"], - "query_params": [ - { "name": "attemptid", "type": "int", "required": true, "description": "Attempt ID" }, - { - "name": "data", - "type": "list", - "required": false, - "default": [], - "description": "Responses as list of {name, value} objects" - }, - { "name": "finishattempt", "type": "bool", "required": false, "default": false, "description": "Finish the attempt" }, - { "name": "timeup", "type": "bool", "required": false, "default": false, "description": "Time is up" }, - { - "name": "preflightdata", - "type": "list", - "required": false, - "default": [], - "description": "Pre-flight data as list of {name, value} objects" - } - ], - "responses": { - "200": { - "description": "Processing result", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { - "state": "finished", - "attempt": { "id": 101, "sumgrades": 8.5, "state": "finished" }, - "message": "The attempt has been submitted", - "warnings": [] - } - } - } - } - } - }, - - { - "path": "/mod_quiz_get_attempt_summary", - "method": "GET", - "function": "mod_quiz_get_attempt_summary", - "description": "Get quiz attempt summary", - "tags": ["Quizzes"], - "query_params": [ - { "name": "attemptid", "type": "int", "required": true, "description": "Attempt ID" }, - { - "name": "preflightdata", - "type": "list", - "required": false, - "default": [], - "description": "Pre-flight data as list of {name, value} objects" - } - ], - "responses": { - "200": { - "description": "Attempt summary including question statuses.", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { - "attempt": { "id": 101, "state": "finished" }, - "questions": [ { "slot": 1, "status": "Answered" } ], - "warnings": [] - } - } - } - } - } - }, - - { - "path": "/mod_quiz_get_user_attempts", - "method": "GET", - "function": "mod_quiz_get_user_attempts", - "description": "Get quiz attempts for a user (defaults to current user)", - "tags": ["Quizzes"], - "query_params": [ - { "name": "quizid", "type": "int", "required": true, "description": "Quiz ID" }, - { "name": "userid", "type": "int", "required": false, "description": "User ID (optional)" } - ], - "responses": { - "200": { - "description": "List of attempts", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { - "attempts": [ - { "id": 101, "attempt": 1, "state": "finished", "sumgrades": 8.5 }, - { "id": 102, "attempt": 2, "state": "inprogress" } - ], - "warnings": [] - } - } - } - } - } - }, - - { - "path": "/mod_quiz_view_attempt", - "method": "GET", - "function": "mod_quiz_view_attempt", - "description": "Log that a user viewed an attempt (for analytics).", - "tags": ["Quizzes"], - "query_params": [ - { "name": "attemptid", "type": "int", "required": true, "description": "Attempt ID" }, - { "name": "page", "type": "int", "required": false, "default": 0, "description": "Page number" } - ], - "responses": { - "200": { - "description": "View logged", - "content": { "application/json": { "schema": { "type": "object" }, "example": { "status": true } } } - } - } - }, - - { - "path": "/mod_quiz_get_attempt_review", - "method": "GET", - "function": "mod_quiz_get_attempt_review", - "description": "Get the review of a finished attempt, including question feedback.", - "tags": ["Quizzes"], - "query_params": [ - { "name": "attemptid", "type": "int", "required": true, "description": "Attempt ID" }, - { "name": "page", "type": "int", "required": false, "default": -1, "description": "Page (-1 for all)" } - ], - "responses": { - "200": { - "description": "Attempt review", - "content": { - "application/json": { - "schema": { "type": "object" }, - "example": { - "attempt": { "id": 101, "state": "finished", "sumgrades": 8.5 }, - "questions": [ - { "slot": 1, "status": "Correct", "feedback": "Well done." } - ], - "warnings": [] - } - } - } - } - } - } - ] -} \ No newline at end of file diff --git a/src/app.py b/src/app.py index cec8b68..773b0ec 100644 --- a/src/app.py +++ b/src/app.py @@ -52,7 +52,11 @@ async def add_request_id(request: Request, call_next: Callable): # Optional HTTP Bearer security for Swagger Authorize http_bearer = HTTPBearer(auto_error=False) -config = load_config("config.json") +# Load configuration from organized structure +config_dir = os.path.join(os.path.dirname(__file__), "config") +logger.info(f"Loading config from: {config_dir}") +config = load_config(config_dir) +logger.info(f"Loaded {len(config)} endpoint(s)") for endpoint_path, functions in config.items(): logger.debug(f"Processing endpoint: {endpoint_path}") diff --git a/src/config/_login_token-php/Authentication/auth.json b/src/config/_login_token-php/Authentication/auth.json new file mode 100644 index 0000000..6972e53 --- /dev/null +++ b/src/config/_login_token-php/Authentication/auth.json @@ -0,0 +1,92 @@ +{ + "method": "GET", + "description": "Get Moodle token for API calls.", + "query_params": [ + { + "name": "username", + "type": "str", + "required": true, + "description": "Moodle instance username" + }, + { + "name": "password", + "type": "str", + "required": true, + "description": "Moodle instance password" + }, + { + "name": "service", + "type": "str", + "required": false, + "default": "moodle_mobile_app", + "description": "Web service name" + } + ], + "responses": { + "200": { + "description": "Token response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "privatetoken": { + "type": "string" + } + } + }, + "example": { + "token": "abcdef1234567890", + "privatetoken": "abcdef1234567890_priv" + } + } + } + }, + "422": { + "description": "Invalid Credentials", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "errorcode": { + "type": "string" + }, + "stacktrace": { + "type": [ + "string", + "null" + ] + }, + "debuginfo": { + "type": [ + "string", + "null" + ] + }, + "reproductionlink": { + "type": [ + "string", + "null" + ] + } + } + }, + "example": { + "error": "Invalid login", + "errorcode": "invalidlogin", + "stacktrace": null, + "debuginfo": null, + "reproductionlink": null + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_assignments.json b/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_assignments.json new file mode 100644 index 0000000..a189f9c --- /dev/null +++ b/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_assignments.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Get assignments from specified courses", + "query_params": [ + { + "name": "courseids", + "type": "list", + "required": true, + "description": "List of course IDs" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_submission_status.json b/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_submission_status.json new file mode 100644 index 0000000..b8d3a07 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_submission_status.json @@ -0,0 +1,18 @@ +{ + "method": "GET", + "description": "Get assignment submission status", + "query_params": [ + { + "name": "assignid", + "type": "int", + "required": true, + "description": "Assignment ID" + }, + { + "name": "userid", + "type": "int", + "required": false, + "description": "User ID (optional)" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_submissions.json b/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_submissions.json new file mode 100644 index 0000000..b4afa9f --- /dev/null +++ b/src/config/_webservice_rest_server-php/Assignments/mod_assign_get_submissions.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Get assignment submissions", + "query_params": [ + { + "name": "assignmentids", + "type": "list", + "required": true, + "description": "Assignment IDs" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Calendar/core_calendar_get_action_events_by_course.json b/src/config/_webservice_rest_server-php/Calendar/core_calendar_get_action_events_by_course.json new file mode 100644 index 0000000..5f33878 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Calendar/core_calendar_get_action_events_by_course.json @@ -0,0 +1,37 @@ +{ + "method": "GET", + "description": "Get action events (assignments, quizzes) by course", + "query_params": [ + { + "name": "courseid", + "type": "int", + "required": true, + "description": "Course ID" + }, + { + "name": "timesortfrom", + "type": "int", + "required": false, + "description": "Time sort from (timestamp)" + }, + { + "name": "timesortto", + "type": "int", + "required": false, + "description": "Time sort to (timestamp)" + }, + { + "name": "aftereventid", + "type": "int", + "required": false, + "description": "After event ID" + }, + { + "name": "limitnum", + "type": "int", + "required": false, + "default": 20, + "description": "Number of events to return" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Calendar/core_calendar_get_calendar_events.json b/src/config/_webservice_rest_server-php/Calendar/core_calendar_get_calendar_events.json new file mode 100644 index 0000000..b17811b --- /dev/null +++ b/src/config/_webservice_rest_server-php/Calendar/core_calendar_get_calendar_events.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Get calendar events", + "query_params": [ + { + "name": "courseid", + "type": "int", + "required": false, + "description": "Course ID (optional)" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Core/core_webservice_get_site_info.json b/src/config/_webservice_rest_server-php/Core/core_webservice_get_site_info.json new file mode 100644 index 0000000..58379f7 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Core/core_webservice_get_site_info.json @@ -0,0 +1,148 @@ +{ + "method": "GET", + "description": "Get Moodle site information & user information", + "query_params": [], + "responses": { + "200": { + "description": "Site and user information", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "sitename": { + "type": "string" + }, + "username": { + "type": "string" + }, + "firstname": { + "type": "string" + }, + "lastname": { + "type": "string" + }, + "fullname": { + "type": "string" + }, + "lang": { + "type": "string" + }, + "userid": { + "type": "integer" + }, + "siteurl": { + "type": "string" + }, + "userpictureurl": { + "type": "string" + }, + "usercanmanageownfiles": { + "type": "boolean" + }, + "userquota": { + "type": "integer" + }, + "usermaxuploadfilesize": { + "type": "integer" + }, + "userhomepage": { + "type": "integer" + }, + "userprivateaccesskey": { + "type": "string" + }, + "siteid": { + "type": "integer" + }, + "sitecalendartype": { + "type": "string" + }, + "usercalendartype": { + "type": "string" + }, + "userissiteadmin": { + "type": "boolean" + }, + "theme": { + "type": "string" + }, + "limitconcurrentlogins": { + "type": "integer" + }, + "policyagreed": { + "type": "integer" + } + } + }, + "example": { + "sitename": "School XYZ", + "username": "jdoe", + "firstname": "John", + "lastname": "Doe", + "fullname": "John Doe", + "lang": "en", + "userid": 42, + "siteurl": "https://moodle.example.com", + "userpictureurl": "https://moodle.example.com/theme/image.php/boost/core/1690000000/u/f1", + "usercanmanageownfiles": true, + "userquota": 104857600, + "usermaxuploadfilesize": 10485760, + "userhomepage": 0, + "userprivateaccesskey": "abcdef1234567890key", + "siteid": 1, + "sitecalendartype": "gregorian", + "usercalendartype": "gregorian", + "userissiteadmin": false, + "theme": "boost", + "limitconcurrentlogins": 1, + "policyagreed": 1 + } + } + } + }, + "422": { + "description": "Invalid Request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "errorcode": { + "type": "string" + }, + "stacktrace": { + "type": [ + "string", + "null" + ] + }, + "debuginfo": { + "type": [ + "string", + "null" + ] + }, + "reproductionlink": { + "type": [ + "string", + "null" + ] + } + } + }, + "example": { + "error": "Invalid parameter value detected", + "errorcode": "invalidparameter", + "stacktrace": null, + "debuginfo": null, + "reproductionlink": null + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Courses/core_course_get_categories.json b/src/config/_webservice_rest_server-php/Courses/core_course_get_categories.json new file mode 100644 index 0000000..82c9b75 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Courses/core_course_get_categories.json @@ -0,0 +1,84 @@ +{ + "method": "GET", + "description": "Get course categories", + "query_params": [], + "responses": { + "200": { + "description": "List of course categories", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "descriptionformat": { + "type": "integer" + }, + "parent": { + "type": "integer" + }, + "sortorder": { + "type": "integer" + }, + "coursecount": { + "type": "integer" + }, + "depth": { + "type": "integer" + }, + "path": { + "type": "string" + } + } + } + }, + "example": [ + { + "id": 60, + "name": "ABU-SOL", + "description": "

ABU-Moodle-Kurse des Projekts nachhaltige Lernorganisationsformen, in denen begleitetes selbstorganisisertes Lernen (SOL) und eine Einführung des selbstgesteuerten Lernen in einem Pilotversuch getestet wird.

", + "descriptionformat": 1, + "parent": 0, + "sortorder": 10000, + "coursecount": 4, + "depth": 1, + "path": "/60" + }, + { + "id": 48, + "name": "Automatikmonteur", + "description": "Sammelgefäss für alle Kurse des Berufs Automatikmonteur", + "descriptionformat": 1, + "parent": 0, + "sortorder": 100000, + "coursecount": 10, + "depth": 1, + "path": "/48" + }, + { + "id": 17, + "name": "Automobil", + "description": "", + "descriptionformat": 1, + "parent": 0, + "sortorder": 110000, + "coursecount": 0, + "depth": 1, + "path": "/17" + } + ] + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Courses/core_course_get_contents.json b/src/config/_webservice_rest_server-php/Courses/core_course_get_contents.json new file mode 100644 index 0000000..78eb640 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Courses/core_course_get_contents.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Get course contents (sections and activities)", + "query_params": [ + { + "name": "courseid", + "type": "int", + "required": true, + "description": "Course ID" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Courses/core_course_get_courses_by_field.json b/src/config/_webservice_rest_server-php/Courses/core_course_get_courses_by_field.json new file mode 100644 index 0000000..073e62d --- /dev/null +++ b/src/config/_webservice_rest_server-php/Courses/core_course_get_courses_by_field.json @@ -0,0 +1,18 @@ +{ + "method": "GET", + "description": "Get courses by field (id, shortname, fullname, etc.)", + "query_params": [ + { + "name": "field", + "type": "str", + "required": true, + "description": "Field to search by" + }, + { + "name": "value", + "type": "str", + "required": true, + "description": "Value to search for" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Courses/core_course_search_courses.json b/src/config/_webservice_rest_server-php/Courses/core_course_search_courses.json new file mode 100644 index 0000000..3d345bb --- /dev/null +++ b/src/config/_webservice_rest_server-php/Courses/core_course_search_courses.json @@ -0,0 +1,40 @@ +{ + "method": "GET", + "description": "Search courses by criteria", + "query_params": [ + { + "name": "criterianame", + "type": "str", + "required": false, + "default": "search", + "description": "Search criteria name" + }, + { + "name": "criteriavalue", + "type": "str", + "required": true, + "description": "Search term" + }, + { + "name": "page", + "type": "int", + "required": false, + "default": 0, + "description": "Page number" + }, + { + "name": "perpage", + "type": "int", + "required": false, + "default": 100, + "description": "Number of results per page" + }, + { + "name": "limittoenrolled", + "type": "int", + "required": false, + "default": 0, + "description": "Limit to enrolled courses only" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Enrollment/core_enrol_get_enrolled_users.json b/src/config/_webservice_rest_server-php/Enrollment/core_enrol_get_enrolled_users.json new file mode 100644 index 0000000..d061467 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Enrollment/core_enrol_get_enrolled_users.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Get users enrolled in a course", + "query_params": [ + { + "name": "courseid", + "type": "int", + "required": true, + "description": "Course ID" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Enrollment/core_enrol_get_users_courses.json b/src/config/_webservice_rest_server-php/Enrollment/core_enrol_get_users_courses.json new file mode 100644 index 0000000..ab6e4ef --- /dev/null +++ b/src/config/_webservice_rest_server-php/Enrollment/core_enrol_get_users_courses.json @@ -0,0 +1,13 @@ +{ + "method": "GET", + "description": "Get courses the user is enrolled in", + "query_params": [ + { + "name": "userid", + "type": "int", + "required": false, + "default": 0, + "description": "User ID (0 for current user)" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Enrollment/enrol_self_enrol_user.json b/src/config/_webservice_rest_server-php/Enrollment/enrol_self_enrol_user.json new file mode 100644 index 0000000..701e254 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Enrollment/enrol_self_enrol_user.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Self-enrol user in a course | unenrol by using function on enrolled course", + "query_params": [ + { + "name": "courseid", + "type": "int", + "required": true, + "description": "Course ID" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Files/core_files_get_files.json b/src/config/_webservice_rest_server-php/Files/core_files_get_files.json new file mode 100644 index 0000000..c0f5355 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Files/core_files_get_files.json @@ -0,0 +1,45 @@ +{ + "method": "GET", + "description": "Get files from specified context", + "query_params": [ + { + "name": "contextid", + "type": "int", + "required": true, + "description": "Context ID" + }, + { + "name": "component", + "type": "str", + "required": true, + "description": "Component name" + }, + { + "name": "filearea", + "type": "str", + "required": true, + "description": "File area" + }, + { + "name": "itemid", + "type": "int", + "required": false, + "default": 0, + "description": "Item ID" + }, + { + "name": "filepath", + "type": "str", + "required": false, + "default": "/", + "description": "File path" + }, + { + "name": "filename", + "type": "str", + "required": false, + "default": "", + "description": "File name" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Forums/mod_forum_get_forum_discussions.json b/src/config/_webservice_rest_server-php/Forums/mod_forum_get_forum_discussions.json new file mode 100644 index 0000000..dce3cf4 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Forums/mod_forum_get_forum_discussions.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Get discussions in a forum", + "query_params": [ + { + "name": "forumid", + "type": "int", + "required": true, + "description": "Forum ID" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Forums/mod_forum_get_forums_by_courses.json b/src/config/_webservice_rest_server-php/Forums/mod_forum_get_forums_by_courses.json new file mode 100644 index 0000000..5328c41 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Forums/mod_forum_get_forums_by_courses.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Get forums in specified courses", + "query_params": [ + { + "name": "courseids", + "type": "list", + "required": true, + "description": "List of course IDs" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Grades/gradereport_user_get_grade_items.json b/src/config/_webservice_rest_server-php/Grades/gradereport_user_get_grade_items.json new file mode 100644 index 0000000..dbe1e96 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Grades/gradereport_user_get_grade_items.json @@ -0,0 +1,19 @@ +{ + "method": "GET", + "description": "Get grade items for a course", + "query_params": [ + { + "name": "courseid", + "type": "int", + "required": true, + "description": "Course ID" + }, + { + "name": "userid", + "type": "int", + "required": false, + "default": 0, + "description": "User ID" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Messages/core_message_get_messages.json b/src/config/_webservice_rest_server-php/Messages/core_message_get_messages.json new file mode 100644 index 0000000..101b4a0 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Messages/core_message_get_messages.json @@ -0,0 +1,52 @@ +{ + "method": "GET", + "description": "Get messages", + "query_params": [ + { + "name": "useridto", + "type": "int", + "required": true, + "description": "User ID to (recipient)" + }, + { + "name": "useridfrom", + "type": "int", + "required": false, + "description": "User ID from (sender)" + }, + { + "name": "type", + "type": "str", + "required": false, + "default": "both", + "description": "Message type (sent, received, both)" + }, + { + "name": "read", + "type": "bool", + "required": false, + "description": "Read status" + }, + { + "name": "newestfirst", + "type": "bool", + "required": false, + "default": true, + "description": "Sort newest first" + }, + { + "name": "limitfrom", + "type": "int", + "required": false, + "default": 0, + "description": "Limit from" + }, + { + "name": "limitnum", + "type": "int", + "required": false, + "default": 100, + "description": "Number of messages" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_data.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_data.json new file mode 100644 index 0000000..2cca739 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_data.json @@ -0,0 +1,53 @@ +{ + "method": "GET", + "description": "Get quiz attempt data including questions", + "query_params": [ + { + "name": "attemptid", + "type": "int", + "required": true, + "description": "Attempt ID" + }, + { + "name": "page", + "type": "int", + "required": false, + "default": 0, + "description": "Page number (-1 for all pages)" + }, + { + "name": "preflightdata", + "type": "list", + "required": false, + "default": [], + "description": "Pre-flight data as list of {name, value} objects (JSON)." + } + ], + "responses": { + "200": { + "description": "Attempt data including question states and HTML.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "attempt": { + "id": 101, + "state": "inprogress" + }, + "questions": [ + { + "slot": 1, + "type": "multichoice", + "html": "
Question 1 HTML
", + "name": "Q1" + } + ], + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_review.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_review.json new file mode 100644 index 0000000..fd8d9e0 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_review.json @@ -0,0 +1,46 @@ +{ + "method": "GET", + "description": "Get the review of a finished attempt, including question feedback.", + "query_params": [ + { + "name": "attemptid", + "type": "int", + "required": true, + "description": "Attempt ID" + }, + { + "name": "page", + "type": "int", + "required": false, + "default": -1, + "description": "Page (-1 for all)" + } + ], + "responses": { + "200": { + "description": "Attempt review", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "attempt": { + "id": 101, + "state": "finished", + "sumgrades": 8.5 + }, + "questions": [ + { + "slot": 1, + "status": "Correct", + "feedback": "Well done." + } + ], + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_summary.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_summary.json new file mode 100644 index 0000000..56288e2 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_attempt_summary.json @@ -0,0 +1,44 @@ +{ + "method": "GET", + "description": "Get quiz attempt summary", + "query_params": [ + { + "name": "attemptid", + "type": "int", + "required": true, + "description": "Attempt ID" + }, + { + "name": "preflightdata", + "type": "list", + "required": false, + "default": [], + "description": "Pre-flight data as list of {name, value} objects" + } + ], + "responses": { + "200": { + "description": "Attempt summary including question statuses.", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "attempt": { + "id": 101, + "state": "finished" + }, + "questions": [ + { + "slot": 1, + "status": "Answered" + } + ], + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_quiz_access_information.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_quiz_access_information.json new file mode 100644 index 0000000..bd7db86 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_quiz_access_information.json @@ -0,0 +1,83 @@ +{ + "method": "GET", + "description": "Get quiz access information", + "query_params": [ + { + "name": "quizid", + "type": "int", + "required": true, + "description": "Quiz ID" + } + ], + "responses": { + "200": { + "description": "Quiz access info for current user", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "canattempt": { + "type": "boolean" + }, + "canmanage": { + "type": "boolean" + }, + "canpreview": { + "type": "boolean" + }, + "canreviewmyattempts": { + "type": "boolean" + }, + "canviewreports": { + "type": "boolean" + }, + "accessrules": { + "type": "array", + "items": { + "type": "string" + } + }, + "activerulenames": { + "type": "array", + "items": { + "type": "string" + } + }, + "preventaccessreasons": { + "type": "array", + "items": { + "type": "string" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": [ + "canattempt", + "accessrules", + "warnings" + ] + }, + "example": { + "canattempt": true, + "canmanage": false, + "canpreview": false, + "canreviewmyattempts": true, + "canviewreports": false, + "accessrules": [], + "activerulenames": [ + "quizaccess_openclosedate" + ], + "preventaccessreasons": [], + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_quizzes_by_courses.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_quizzes_by_courses.json new file mode 100644 index 0000000..8c41d18 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_quizzes_by_courses.json @@ -0,0 +1,159 @@ +{ + "method": "GET", + "description": "Get quizzes in specified courses", + "query_params": [ + { + "name": "courseids", + "type": "list", + "required": true, + "description": "List of course IDs" + } + ], + "responses": { + "200": { + "description": "List of quizzes for the given courses", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "quizzes": { + "type": "array", + "items": { + "type": "object" + } + }, + "warnings": { + "type": "array", + "items": { + "type": "object" + } + } + }, + "required": [ + "quizzes" + ] + }, + "example": { + "quizzes": [ + { + "id": 1001, + "coursemodule": 2001, + "course": 42, + "name": "Chemistry Homework Quiz", + "intro": "

Please answer all questions.

", + "introformat": 1, + "introfiles": [], + "section": 1, + "visible": true, + "groupmode": 0, + "groupingid": 0, + "lang": "en", + "timeopen": 0, + "timeclose": 0, + "timelimit": 0, + "overduehandling": "autoabandon", + "graceperiod": 0, + "preferredbehaviour": "deferredfeedback", + "canredoquestions": 0, + "attempts": 0, + "attemptonlast": 0, + "grademethod": 1, + "decimalpoints": 2, + "questiondecimalpoints": -1, + "reviewattempt": 69904, + "reviewcorrectness": 4368, + "reviewmaxmarks": 69904, + "reviewmarks": 4368, + "reviewspecificfeedback": 4368, + "reviewgeneralfeedback": 4368, + "reviewrightanswer": 4368, + "reviewoverallfeedback": 4368, + "questionsperpage": 1, + "navmethod": "free", + "sumgrades": 5, + "grade": 5, + "browsersecurity": "-", + "delay1": 0, + "delay2": 0, + "showuserpicture": 0, + "showblocks": 0, + "completionattemptsexhausted": 0, + "completionpass": 0, + "allowofflineattempts": 0, + "autosaveperiod": 60, + "hasfeedback": 0, + "hasquestions": 1 + }, + { + "id": 1002, + "coursemodule": 2002, + "course": 42, + "name": "Math Practice Quiz", + "intro": "

Practice questions for the exam.

", + "introformat": 1, + "introfiles": [], + "section": 1, + "visible": true, + "groupmode": 0, + "groupingid": 0, + "lang": "en", + "timeopen": 0, + "timeclose": 0, + "timelimit": 900, + "preferredbehaviour": "deferredfeedback", + "attempts": 2, + "grademethod": 4, + "decimalpoints": 2, + "questiondecimalpoints": -1, + "sumgrades": 4, + "grade": 4, + "hasfeedback": 0 + }, + { + "id": 1003, + "coursemodule": 2003, + "course": 42, + "name": "Biology Final Quiz", + "intro": "

Final quiz for the course.

", + "introformat": 1, + "introfiles": [], + "section": 2, + "visible": true, + "groupmode": 0, + "groupingid": 0, + "lang": "en", + "timeopen": 1710000000, + "timeclose": 1710600000, + "timelimit": 2400, + "preferredbehaviour": "immediatefeedback", + "attempts": 1, + "grademethod": 1, + "decimalpoints": 2, + "questiondecimalpoints": -1, + "sumgrades": 10, + "grade": 10, + "hasfeedback": 0 + } + ], + "warnings": [] + } + } + } + }, + "422": { + "description": "Invalid Request", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "error": "invalidparameter", + "errorcode": "invalidparameter" + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_user_attempts.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_user_attempts.json new file mode 100644 index 0000000..3c734ac --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_get_user_attempts.json @@ -0,0 +1,46 @@ +{ + "method": "GET", + "description": "Get quiz attempts for a user (defaults to current user)", + "query_params": [ + { + "name": "quizid", + "type": "int", + "required": true, + "description": "Quiz ID" + }, + { + "name": "userid", + "type": "int", + "required": false, + "description": "User ID (optional)" + } + ], + "responses": { + "200": { + "description": "List of attempts", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "attempts": [ + { + "id": 101, + "attempt": 1, + "state": "finished", + "sumgrades": 8.5 + }, + { + "id": 102, + "attempt": 2, + "state": "inprogress" + } + ], + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_process_attempt.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_process_attempt.json new file mode 100644 index 0000000..f530873 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_process_attempt.json @@ -0,0 +1,62 @@ +{ + "method": "GET", + "description": "Process and optionally finish a quiz attempt", + "query_params": [ + { + "name": "attemptid", + "type": "int", + "required": true, + "description": "Attempt ID" + }, + { + "name": "data", + "type": "list", + "required": false, + "default": [], + "description": "Responses as list of {name, value} objects" + }, + { + "name": "finishattempt", + "type": "bool", + "required": false, + "default": false, + "description": "Finish the attempt" + }, + { + "name": "timeup", + "type": "bool", + "required": false, + "default": false, + "description": "Time is up" + }, + { + "name": "preflightdata", + "type": "list", + "required": false, + "default": [], + "description": "Pre-flight data as list of {name, value} objects" + } + ], + "responses": { + "200": { + "description": "Processing result", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "state": "finished", + "attempt": { + "id": 101, + "sumgrades": 8.5, + "state": "finished" + }, + "message": "The attempt has been submitted", + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_save_attempt.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_save_attempt.json new file mode 100644 index 0000000..ee245d6 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_save_attempt.json @@ -0,0 +1,41 @@ +{ + "method": "GET", + "description": "Save quiz attempt responses", + "query_params": [ + { + "name": "attemptid", + "type": "int", + "required": true, + "description": "Attempt ID" + }, + { + "name": "data", + "type": "list", + "required": true, + "description": "Responses as list of {name, value} objects, e.g. [{\"name\":\"q1:1_answer\",\"value\":\"2\"}]." + }, + { + "name": "preflightdata", + "type": "list", + "required": false, + "default": [], + "description": "Pre-flight data as list of {name, value} objects" + } + ], + "responses": { + "200": { + "description": "Save result", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "status": true, + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_start_attempt.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_start_attempt.json new file mode 100644 index 0000000..d380335 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_start_attempt.json @@ -0,0 +1,48 @@ +{ + "method": "GET", + "description": "Start a new quiz attempt", + "query_params": [ + { + "name": "quizid", + "type": "int", + "required": true, + "description": "Quiz ID" + }, + { + "name": "preflightdata", + "type": "list", + "required": false, + "default": [], + "description": "Pre-flight data as list of {name, value} objects (JSON or CSV)." + }, + { + "name": "forcenew", + "type": "bool", + "required": false, + "default": false, + "description": "Force new attempt." + } + ], + "responses": { + "200": { + "description": "Attempt started", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "attempt": { + "id": 101, + "quiz": 15, + "state": "inprogress", + "timestart": 1694350000, + "layout": "1,2,3,0" + }, + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_view_attempt.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_view_attempt.json new file mode 100644 index 0000000..6d7984c --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_view_attempt.json @@ -0,0 +1,34 @@ +{ + "method": "GET", + "description": "Log that a user viewed an attempt (for analytics).", + "query_params": [ + { + "name": "attemptid", + "type": "int", + "required": true, + "description": "Attempt ID" + }, + { + "name": "page", + "type": "int", + "required": false, + "default": 0, + "description": "Page number" + } + ], + "responses": { + "200": { + "description": "View logged", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "status": true + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_view_quiz.json b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_view_quiz.json new file mode 100644 index 0000000..87952d7 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Quizzes/mod_quiz_view_quiz.json @@ -0,0 +1,28 @@ +{ + "method": "GET", + "description": "Log that a user viewed the quiz (for analytics).", + "query_params": [ + { + "name": "quizid", + "type": "int", + "required": true, + "description": "Quiz ID" + } + ], + "responses": { + "200": { + "description": "View logged", + "content": { + "application/json": { + "schema": { + "type": "object" + }, + "example": { + "status": true, + "warnings": [] + } + } + } + } + } +} diff --git a/src/config/_webservice_rest_server-php/Users/core_user_get_course_user_profiles.json b/src/config/_webservice_rest_server-php/Users/core_user_get_course_user_profiles.json new file mode 100644 index 0000000..0094144 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Users/core_user_get_course_user_profiles.json @@ -0,0 +1,12 @@ +{ + "method": "GET", + "description": "Get user profiles for users in a course", + "query_params": [ + { + "name": "courseid", + "type": "int", + "required": true, + "description": "Course ID" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Users/core_user_get_user_preferences.json b/src/config/_webservice_rest_server-php/Users/core_user_get_user_preferences.json new file mode 100644 index 0000000..b581b77 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Users/core_user_get_user_preferences.json @@ -0,0 +1,18 @@ +{ + "method": "GET", + "description": "Get user preferences", + "query_params": [ + { + "name": "userid", + "type": "int", + "required": false, + "description": "User ID (optional)" + }, + { + "name": "name", + "type": "str", + "required": false, + "description": "Preference name" + } + ] +} diff --git a/src/config/_webservice_rest_server-php/Users/core_user_get_users_by_field.json b/src/config/_webservice_rest_server-php/Users/core_user_get_users_by_field.json new file mode 100644 index 0000000..7bfd6b4 --- /dev/null +++ b/src/config/_webservice_rest_server-php/Users/core_user_get_users_by_field.json @@ -0,0 +1,18 @@ +{ + "method": "GET", + "description": "Get users by field (id, username, email, etc.)", + "query_params": [ + { + "name": "field", + "type": "str", + "required": true, + "description": "Field to search by" + }, + { + "name": "values", + "type": "list", + "required": true, + "description": "Values to search for" + } + ] +} diff --git a/src/mw_utils/config.py b/src/mw_utils/config.py index 4b18082..29d275d 100644 --- a/src/mw_utils/config.py +++ b/src/mw_utils/config.py @@ -1,12 +1,119 @@ import json +from pathlib import Path +from typing import Dict, List, Any -def load_config(file_path: str) -> dict: - """Load JSON config file or raise a clear error.""" - try: - with open(file_path, 'r', encoding='utf-8') as f: - return json.load(f) - except FileNotFoundError as e: - raise RuntimeError(f"Config not found: {file_path}") from e - except json.JSONDecodeError as e: - raise RuntimeError(f"Invalid JSON in config: {file_path}") from e +def load_config(config_dir: str = "config") -> Dict[str, List[Dict[str, Any]]]: + """Load config from organized folder structure. + + Args: + config_dir: Path to the config directory containing organized endpoint folders + + Returns: + Dictionary mapping endpoint paths to their function configurations: + { + "/endpoint/path": [ + { + "path": "/api_path", + "method": "GET", + "function": "function_name", + "description": "...", + "tags": ["Tag"], + "query_params": [...], + "responses": {...} + } + ] + } + """ + config_path = Path(config_dir) + + if not config_path.exists(): + raise RuntimeError(f"Config directory not found: {config_dir}") + + result = {} + + # Process each endpoint directory + endpoint_dirs = list(config_path.iterdir()) + endpoint_dirs = [d for d in endpoint_dirs if d.is_dir()] + + # Sort endpoint directories, but prioritize token.php (auth) endpoint first + def endpoint_sort_key(endpoint_dir): + # Force token.php endpoint to come first (priority 0), others use priority 1 + if "token" in endpoint_dir.name.lower(): + return (0, endpoint_dir.name.lower()) + return (1, endpoint_dir.name.lower()) + + endpoint_dirs.sort(key=endpoint_sort_key) + + for endpoint_dir in endpoint_dirs: + + # Convert folder name back to endpoint path + # _login_token-php -> /login/token.php + # _webservice_rest_server-php -> /webservice/rest/server.php + endpoint_path = _folder_name_to_endpoint_path(endpoint_dir.name) + + endpoint_functions = [] + + # Process each tag directory within the endpoint (sorted alphabetically) + tag_dirs = sorted([d for d in endpoint_dir.iterdir() if d.is_dir()], key=lambda x: x.name.lower()) + for tag_dir in tag_dirs: + tag_name = tag_dir.name + + # Process each function JSON file within the tag directory (sorted alphabetically) + function_files = sorted(tag_dir.glob("*.json"), key=lambda x: x.name.lower()) + for function_file in function_files: + function_name = function_file.stem # filename without .json extension + + try: + with open(function_file, 'r', encoding='utf-8') as f: + function_config = json.load(f) + + # Reconstruct the full function config + full_config = { + "path": _generate_api_path(function_name, endpoint_path), + "function": function_name, + "tags": [tag_name], + **function_config # This includes method, description, query_params, responses + } + + endpoint_functions.append(full_config) + + except (json.JSONDecodeError, FileNotFoundError) as e: + raise RuntimeError(f"Error loading function config {function_file}: {e}") + + if endpoint_functions: + result[endpoint_path] = endpoint_functions + + return result + + +def _folder_name_to_endpoint_path(folder_name: str) -> str: + """Convert folder name back to endpoint path. + + Examples: + _login_token-php -> /login/token.php + _webservice_rest_server-php -> /webservice/rest/server.php + """ + # Remove leading underscore + path = folder_name[1:] if folder_name.startswith('_') else folder_name + + # Replace underscores with slashes + path = path.replace('_', '/') + + # Replace dashes with dots (for file extensions) + path = path.replace('-', '.') + + # Add leading slash + return f"/{path}" + + +def _generate_api_path(function_name: str, endpoint_path: str) -> str: + """Generate API path for a function. + + For auth functions, use /auth + For other functions, use the function name as path + """ + if function_name == "auth": + return "/auth" + + return f"/{function_name}"