| name | qf-record-pending-uploads |
| description | Watches a Google Drive folder for new quiz files, maps them to QuizFactor courses or categories using the AI routes collection, creates a course when none matches, then records pending quiz uploads. Use when importing new quiz source files from Drive into QuizFactor. |
| metadata | {"clawdbot":{"emoji":"🧾"}} |
qf-record-pending-uploads
Monitor a Google Drive folder for new quiz files, infer the target QuizFactor course or category from the filename, and record each file as a pending upload in QuizFactor.
When no course in GET /api/ai/courses matches the file, the agent must create a course first (POST /api/ai/create-course), then call POST /api/ai/pending-quiz-uploads with the new courseUuid. Do not insert a pending upload with courseUuid = null if create-course can succeed.
This skill focuses on discovery and classification only – it does not parse questions or create quizzes. That is handled by qf-process-pending-uploads.
Prerequisites
-
Google Drive
-
QuizFactor API
Configuration
Always derive config from files rather than hard-coding:
QF_BASE_URL=$(cat ~/.config/quizfactor/base_url)
QF_DRIVE_FOLDER_IDS=$(cat ~/.config/quizfactor/drive_folder_ids)
QF_GOOGLE_CREDS_PATH=$(cat ~/.config/quizfactor/google_drive_credentials)
The agent may use either Google client libraries (Python/Node) or raw HTTP with OAuth2, but must honor these paths.
QF_DRIVE_FOLDER_IDS is read as text and should be treated as an array of folder IDs, with one ID per line.
Create Course (from Postman) — use before pending-quiz-uploads when no course matches
Request
- Method:
POST
- URL:
$QF_BASE_URL/api/ai/create-course
- Headers:
Content-Type: application/json
- Body (example matches Create Course in the collection):
{
"categoryUuid": "dc3850a3-0065-43ed-b399-9dea5e9afe7a",
"level": "beginner",
"duration": 40,
"imageUrl": "https://example.com/images/algebra-course.jpg",
"translations": [
{
"languageCode": "en",
"title": "Introduction to Algebra",
"description": "Learn variables, expressions, and basic equations."
}
]
}
categoryUuid — required; obtain from an existing courseCategories[] match or from POST /api/ai/create-course-category → data.category.uuid.
level, duration, imageUrl — use sensible defaults when unknown (e.g. intermediate, 30, null or omit imageUrl if the API allows).
translations — at least en with title and description derived from the inferred course name and a short description.
Success response (201 Created)
{
"status": "00",
"data": {
"message": "Course created successfully",
"course": {
"id": 128,
"uuid": "0a737479-375c-4edc-a376-ae226af45da4",
"categoryUuid": "dc3850a3-0065-43ed-b399-9dea5e9afe7a",
"level": "beginner",
"duration": 40,
"slug": null,
"imageUrl": "https://example.com/images/algebra-course.jpg",
"isActive": true,
"totalQuizzes": 0,
"createdAt": "2026-03-13T22:38:21.210Z",
"updatedAt": "2026-03-13T22:38:21.210Z",
"translations": []
}
}
}
- Use
data.course.uuid as courseUuid for POST /api/ai/pending-quiz-uploads.
Failure (e.g. 404 Not Found) — invalid categoryUuid per collection example. If create-course cannot succeed, you may record the pending upload with courseUuid: null and a clear courseName, and log for follow-up.
High-level Workflow
-
Iterate drive folder IDs
- Split
QF_DRIVE_FOLDER_IDS by newline into an array of folder IDs.
- For each folder id
FOLDER_ID in the array, list files in that folder.
-
List files in the current Google Drive folder
- Use Drive API
files.list with:
q filtering by parent folder FOLDER_ID and trashed = false.
- Fields including
id, name, mimeType, and timestamps.
-
Fetch QuizFactor course and category metadata
-
Normalize filenames
- For each Drive file, derive a normalized name:
- Strip extension, punctuation.
- Lowercase.
- Replace spaces/underscores with
-.
- Keep both the original filename and the normalized variant.
-
Match file to an existing course
- Matching strategy (in order):
- Exact match between normalized filename and
course.slug.
- Case-insensitive equality between a prettified filename (spaces restored) and
course.title.
- Substring/keyword match where filename contains course title or slug tokens.
- If multiple candidates match, prefer:
- The one whose
categoryUuid best matches any category keywords in the filename.
- If a course matches: note its
uuid and go to step 8 (pending upload) with that courseUuid — skip create-course.
-
No course match — resolve category
- If no course matches
data.courses[]:
- Try matching against
courseCategories by name (normalized) and tagName.
- If no category fits: create one with
POST /api/ai/create-course-category (see collection), then use data.category.uuid as categoryUuid for create-course.
-
No course match — create course (mandatory before pending upload)
- With a resolved
categoryUuid (from step 6), call POST /api/ai/create-course as documented in Create Course (from Postman) above.
- On 201 success, read
data.course.uuid — this is the courseUuid for the pending upload.
- Only if create-course fails after category resolution: optionally call
POST /api/ai/pending-quiz-uploads with courseUuid: null, courseName set to the inferred title, and log the error.
-
Record the pending upload in QuizFactor
- For each new Drive file, after you have a
courseUuid (from step 5 match or step 7 create-course), call Create Pending Quiz Upload (see Postman collection):
curl -X POST "$QF_BASE_URL/api/ai/pending-quiz-uploads" \
-H "Content-Type: application/json" \
-d '{
"courseUuid": "COURSE_UUID_FROM_MATCH_OR_CREATE_COURSE",
"courseName": "Resolved or inferred course name",
"filename": "original-file-name.ext",
"driveFileId": "GOOGLE_DRIVE_FILE_ID",
"status": "pending"
}'
- Response includes
data.uuid, data.status, data.createdAt, etc. Use for idempotency or logging.
Agent Checklist