# Audio Practice (Speaking Practice) — Admin Panel Integration Guide ## Overview The Yimaru backend **fully supports** audio/speaking practices. This guide covers the steps needed to integrate the feature into the **admin panel frontend**. --- ## Backend Status (✅ Already Complete) | Layer | File | What It Does | |-------|------|--------------| | Domain | `internal/domain/questions.go` | `QuestionTypeAudio = "AUDIO"` constant, `VoicePrompt`, `SampleAnswerVoicePrompt`, `AudioCorrectAnswerText` fields | | Handler | `internal/web_server/handlers/questions.go` | `CreateQuestion` accepts `question_type: "AUDIO"` with audio-specific fields | | Handler | `internal/web_server/handlers/file_handler.go` | `UploadAudio` (general audio upload) and `SubmitAudioAnswer` (learner recording) | | Migration | `db/migrations/000022_audio_questions.up.sql` | `AUDIO` type constraint + `question_audio_answers` table | | Migration | `db/migrations/000028_user_audio_responses.up.sql` | `user_audio_responses` table for learner recordings | | SQL | `db/query/question_audio_answers.sql` | CRUD for correct answer text per audio question | | SQL | `db/query/user_audio_responses.sql` | CRUD for learner audio submissions | | Routes | `internal/web_server/routes.go` | `POST /files/audio`, `POST /questions/audio-answer` | --- ## Admin Panel Frontend Steps ### Step 1: Add "AUDIO" Option to the Question Type Selector When creating/editing a question, the admin should be able to select `AUDIO` as a question type alongside `MCQ`, `TRUE_FALSE`, and `SHORT_ANSWER`. --- ### Step 2: Build the AUDIO Question Form When `question_type = "AUDIO"` is selected, render these fields: | Field | API Field | Type | Required | Description | |-------|-----------|------|----------|-------------| | Question Text | `question_text` | `string` | ✅ | The prompt/instruction text shown to the learner | | Voice Prompt | `voice_prompt` | `string` (URL) | Optional | Audio file URL — the question read aloud or a listening prompt | | Sample Answer Voice | `sample_answer_voice_prompt` | `string` (URL) | Optional | Audio URL of a model/reference answer | | Correct Answer Text | `audio_correct_answer_text` | `string` | Optional | Expected textual answer (used for grading/comparison) | | Image | `image_url` | `string` (URL) | Optional | Supporting image for the question | | Explanation | `explanation` | `string` | Optional | Shown to the learner after answering | | Tips | `tips` | `string` | Optional | Hints shown before/during the question | | Difficulty Level | `difficulty_level` | `string` | Optional | `EASY`, `MEDIUM`, or `HARD` | | Points | `points` | `int` | Optional | Score value (defaults to 1) | > **Note:** Hide the `options` and `short_answers` fields when AUDIO is selected — they are not used for this type. --- ### Step 3: Build an Audio Uploader Component Create a reusable audio uploader used for both `voice_prompt` and `sample_answer_voice_prompt`. **API Call:** ``` POST /files/audio Content-Type: multipart/form-data Form field: "file" — the audio file ``` **Constraints:** - Max file size: **50 MB** - Allowed formats: `mp3`, `wav`, `ogg`, `m4a`, `aac`, `webm`, `flac` **Response:** ```json { "message": "Audio file uploaded successfully", "data": { "object_key": "audio/1710000000_recording.mp3", "url": "https://...", "content_type": "audio/mpeg" }, "success": true } ``` **Usage:** 1. Admin clicks "Upload Voice Prompt" → file picker opens 2. File is uploaded via `POST /files/audio` 3. Store the returned `object_key` as the value for `voice_prompt` or `sample_answer_voice_prompt` 4. Display the audio player preview (see Step 4) --- ### Step 4: Build an Audio Player / Preview Component After uploading, let admins preview the audio. **To get a playable URL from an object key:** ``` GET /files/url?key= ``` **Frontend implementation:** ```html ``` This component should be shown: - Next to the voice prompt upload field (after upload) - Next to the sample answer voice prompt upload field (after upload) - When viewing/editing an existing AUDIO question --- ### Step 5: Create a Practice with Audio Questions The full admin workflow: #### 5.1 Create the Practice Set (Question Set) ``` POST /question-sets { "title": "Speaking Practice - Lesson 1", "description": "Practice your pronunciation", "set_type": "PRACTICE", "owner_type": "SUB_COURSE", "owner_id": 42, "status": "DRAFT" } ``` #### 5.2 Upload Audio Files ``` POST /files/audio → voice_prompt object_key POST /files/audio → sample_answer_voice_prompt object_key ``` #### 5.3 Create AUDIO Questions ``` POST /questions { "question_text": "Listen and repeat the following phrase", "question_type": "AUDIO", "voice_prompt": "minio://audio/1710000000_prompt.mp3", "sample_answer_voice_prompt": "minio://audio/1710000000_sample.mp3", "audio_correct_answer_text": "Hello, how are you?", "difficulty_level": "EASY", "points": 10 } ``` #### 5.4 Link Questions to the Practice Set ``` POST /question-sets/:setId/questions { "question_id": 123, "display_order": 1 } ``` #### 5.5 (Optional) Add Personas ``` POST /question-sets/:setId/personas { "user_id": 5, "display_order": 1 } ``` #### 5.6 (Optional) Reorder Practices in a Sub-course ``` PUT /course-management/practices/reorder { "sub_course_id": 42, "ordered_ids": [10, 11, 12] } ``` --- ### Step 6: Display Audio Questions in the Question List When listing questions (`GET /questions?question_type=AUDIO`), show: - An 🔊 audio icon or "AUDIO" badge for the question type - A mini audio player if `voice_prompt` is set - The `audio_correct_answer_text` value (if present) When viewing a single question (`GET /questions/:id`), the response includes: ```json { "question_type": "AUDIO", "voice_prompt": "minio://audio/...", "sample_answer_voice_prompt": "minio://audio/...", "audio_correct_answer_text": "Hello, how are you?" } ``` --- ### Step 7: (Optional) Admin Review of Learner Audio Submissions > **⚠️ Not yet built** — requires a new backend endpoint if needed. Currently, learner audio submissions are stored in `user_audio_responses` but there's no admin-facing endpoint to list/review them. **If this is needed, add:** 1. **New SQL query** in `db/query/user_audio_responses.sql`: ```sql -- name: ListAudioResponsesByQuestionSet :many SELECT uar.*, u.first_name, u.last_name FROM user_audio_responses uar JOIN users u ON u.id = uar.user_id WHERE uar.question_set_id = $1 ORDER BY uar.created_at DESC LIMIT $2 OFFSET $3; ``` 2. **New endpoint** in `routes.go`: ``` GET /admin/question-sets/:setId/audio-responses ``` 3. **Admin UI**: A table showing learner name, audio player for their recording, timestamp, and the correct answer text for comparison. --- ## API Flow Summary ``` ┌─────────────────────────────────────────────────────────┐ │ ADMIN CREATES PRACTICE │ ├─────────────────────────────────────────────────────────┤ │ │ │ POST /question-sets │ │ → Creates practice shell (set_type: "PRACTICE") │ │ │ │ POST /files/audio │ │ → Uploads voice_prompt audio file │ │ │ │ POST /files/audio │ │ → Uploads sample_answer_voice_prompt audio file │ │ │ │ POST /questions │ │ → Creates AUDIO question with voice prompt URLs │ │ and correct_answer_text │ │ │ │ POST /question-sets/:id/questions │ │ → Links question to practice set │ │ │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ LEARNER COMPLETES PRACTICE │ ├─────────────────────────────────────────────────────────┤ │ │ │ GET /files/url?key=... │ │ → Streams voice prompt audio │ │ │ │ POST /questions/audio-answer │ │ → Submits learner's audio recording │ │ │ │ POST /progress/practices/:id/complete │ │ → Marks practice as completed │ │ │ └─────────────────────────────────────────────────────────┘ ``` --- ## Required RBAC Permissions Ensure the admin role has these permissions: | Permission | Used By | |------------|---------| | `questions.create` | Creating AUDIO questions | | `questions.update` | Editing AUDIO questions | | `questions.list` | Listing questions (filter by AUDIO) | | `questions.get` | Viewing a single question | | `questions.delete` | Deleting questions | | `question_sets.create` | Creating practice sets | | `question_sets.update` | Updating practice sets | | `question_set_items.add` | Adding questions to a set | | `question_set_items.list` | Listing questions in a set | | `question_set_items.remove` | Removing questions from a set | | `question_set_items.update_order` | Reordering questions | | `question_set_personas.add` | Adding personas | | `practices.reorder` | Reordering practices in a sub-course |