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}"