Overview

📝

What

Allows a student to edit only their bio via PATCH /api/v1/students/me. The profile picture is explicitly immutable — any attempt to change picture or imagealt is rejected with a 422. The bio is persisted to Moodle's user.description column through a dedicated, narrowly-scoped write model.

⏰

When

Triggered by an authenticated student sending a PATCH request. Rate-limited to 10 requests per minute (sensitive write endpoint). The StudentProfileUpdated event is dispatched synchronously; the Moodle forwarding listener runs on the queue (async) so it never blocks the HTTP response.

💡

Why

PM requested "edit profile" functionality for students, explicitly scoped to the bio field. Writing to Moodle's core user table is forbidden by default per the architecture rules — this is a PM-approved exception using a dedicated write model with explicit column enumeration, ensuring no other column (especially picture) can be touched.

📁

Where

Lives in the Student module (app/Modules/Student/). The write model is in Shared (app/Shared/Models/MoodleUserProfile.php) since it touches Moodle's core user table. The event, service, repository, DTO, request, and controller all reside within the Student module boundary.

⚙

How

The request flows through the standard architecture layers: UpdateProfileRequest validates input (requires bio, prohibits picture/imagealt) → UpdateProfileController (invokable, thin) delegates to StudentProfileService::updateProfile() → the service calls ProfileWriteRepository::updateBio() which delegates to MoodleUserProfile::updateBio() — an explicit query that touches exactly description, descriptionformat, and timemodified via an enumerated column update (no mass assignment). The service then dispatches StudentProfileUpdated (maps to Moodle's \core\event\user_updated), reloads the user model, and returns a StudentProfileDTO. The controller wraps it in the existing StudentResource.

Sequence Diagram

sequenceDiagram
    participant B as Browser
    participant R as Route / Middleware
    participant RQ as UpdateProfileRequest
    participant C as UpdateProfileController
    participant S as StudentProfileService
    participant RP as ProfileWriteRepository
    participant M as MoodleUserProfile
    participant DB as MySQL (user table)
    participant E as Event Dispatcher
    participant L as ForwardEventToMoodle
    participant MO as Moodle Plugin (REST)

    B->>R: PATCH /api/v1/students/me
    R->>R: auth:sanctum + moodle.active + throttle:10,1
    R->>RQ: Validate input
    alt bio missing / too long
        RQ-->>B: 422 Validation Error
    end
    alt picture or imagealt present
        RQ-->>B: 422 Picture editing not allowed
    end
    RQ->>C: toDTO() produces UpdateProfileDTO
    C->>S: updateProfile(dto, user)
    S->>RP: updateBio(studentId, bio)
    RP->>M: updateBio(userId, bio)
    M->>DB: UPDATE user SET description, descriptionformat, timemodified WHERE id = ?
    DB-->>M: OK
    S->>E: dispatch(StudentProfileUpdated)
    E->>L: handle(event) [queued]
    L->>MO: POST /event (user_updated) [async]
    S->>DB: user refresh
    DB-->>S: Fresh user row
    S-->>C: StudentProfileDTO
    C-->>B: 200 JSON envelope with updated profile
      

Flowchart

flowchart TD
    A[PATCH /api/v1/students/me] --> B{Authenticated?}
    B -- No --> C[401 Unauthenticated]
    B -- Yes --> D{Rate limit OK?}
    D -- No --> E[429 Too Many Requests]
    D -- Yes --> F{Validate input}
    F -- bio missing --> G[422 bio is required]
    F -- bio exceeds 65535 chars --> H[422 bio too long]
    F -- picture or imagealt present --> I[422 picture not allowed]
    F -- Valid --> J[UpdateProfileController]
    J --> K[StudentProfileService.updateProfile]
    K --> L[ProfileWriteRepository.updateBio]
    L --> M[MoodleUserProfile.updateBio]
    M --> N[UPDATE user SET description, descriptionformat, timemodified]
    N --> O[Dispatch StudentProfileUpdated]
    O --> P[ForwardEventToMoodle queued]
    P --> Q[Refresh user model]
    Q --> R[Return StudentProfileDTO]
    R --> S[200 OK with StudentResource envelope]

    style C fill:#ef4444,color:#fff
    style E fill:#f59e0b,color:#000
    style G fill:#ef4444,color:#fff
    style H fill:#ef4444,color:#fff
    style I fill:#ef4444,color:#fff
    style S fill:#10b981,color:#000
      

Files Changed

File PathLayerDescription
app/Shared/Models/MoodleUserProfile.php Model Dedicated writable model for Moodle's user table. Exposes a single updateBio() method that touches exactly description, descriptionformat, timemodified. No mass assignment.
app/Modules/Student/Repositories/ProfileWriteRepositoryInterface.php Repository Contract for the narrow write path into the student's bio. Single method: updateBio(int, string).
app/Modules/Student/Repositories/ProfileWriteRepository.php Repository Concrete implementation — delegates to MoodleUserProfile::updateBio().
app/Modules/Student/DTOs/UpdateProfileDTO.php DTO final readonly DTO carrying studentId and bio from the request to the service layer.
app/Modules/Student/Requests/UpdateProfileRequest.php Request FormRequest validating bio (required, string, max 65535) and prohibiting picture/imagealt. Includes toDTO() factory method.
app/Modules/Student/Controllers/UpdateProfileController.php Controller Invokable controller — calls service, returns StudentResource envelope. Thin: 6 lines of logic.
app/Modules/Student/Services/StudentProfileService.php Service Modified — added updateProfile() method and ProfileWriteRepositoryInterface dependency. Persists bio, dispatches event, reloads and returns profile.
app/Modules/Student/Events/StudentProfileUpdated.php Event Domain event extending BaseEvent. Maps to Moodle's \core\event\user_updated. CRUD=Update, EduLevel=Other, ContextLevel=User.
app/Modules/Student/routes.php Route Added PATCH me route with throttle:10,1 middleware, named api.v1.students.me.update.
app/Providers/AppServiceProvider.php Config Added binding: ProfileWriteRepositoryInterface → ProfileWriteRepository.
tests/Feature/Student/UpdateProfileTest.php Test 8 feature tests: success path, persistence verification, 401 unauthenticated, 422 (missing bio, max length, prohibited picture), picture immutability, event dispatch.
tests/Unit/Modules/Student/Services/StudentProfileServiceTest.php Test Modified — added ProfileWriteRepositoryInterface mock as constructor dependency to match updated service signature.
docs/openapi.yaml Docs Added PATCH /students/me endpoint spec with request/response schemas and error codes.
docs/postman_collection.json Docs Added Postman request with example response and envelope test script.

Rules Applied