Skip to content

Split large controllers and the teacher calendar into services/components; run frontend tests in CI - #65

Merged
yt314 merged 5 commits into
mainfrom
claude/nifty-franklin-cpn0wj
Oct 20, 2026
Merged

yt314 merged 5 commits into
mainfrom
claude/nifty-franklin-cpn0wj

Conversation

@yt314

@yt314 yt314 commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

Code-structure work on the three largest units, plus frontend tests in CI. Apart from two small UI bug fixes, behavior is unchanged.

Unit Before After
LessonsController.cs 621 lines, uses DbContext directly 150 lines, HTTP only
MessagesController.cs 572 lines, uses DbContext directly 144 lines, HTTP only
lessons-list.component.ts / .html 1,224 / 626 lines 657 / 275 lines
Frontend tests 40, not run in CI 89, run in CI

Changes

CI: the frontend-build job now runs npm test -- --watch=false after the build.

Backend: lessons (Talmidon.Api/Services/Lessons/)

  • LessonQueries: the read side for teacher, parent and student. The LessonDto projection was copied 3× and is now defined once.
  • LessonService: the teacher's calendar actions: create, update, delete, complete, no-show, approve/decline a new-lesson request.
  • LessonRequestService: requests from the family, and the teacher's answer to change requests.
  • FamilyMembers: the current parent/student and the parent→child ownership check (row-level access).
  • TeacherNotifier: notification and email to the teacher. These were private helpers in the controller.
  • ServiceError / ServiceResult<T>: services report NotFound / Forbidden / Conflict / Invalid, and the controller maps that to HTTP.

Backend: messages (Talmidon.Api/Services/Messages/)

  • MessageThreads: shared thread mechanics.
  • MessageQueries: the read side.
  • TeacherMessageService and FamilyMessageService: each side's actions.
  • MessageNotifier: notifications and email. The teacher email now goes through TeacherNotifier.SendEmailAsync instead of a second copy of the same code.

Frontend: teacher calendar (features/lessons/)

  • lesson-scheduling.util.ts: the scheduling rules as pure functions: conflicts, availability, what is waiting to be marked, the next pending request, future occurrences of a series, all-day event ranges, and the amount explanation.
  • AddLessonDialogComponent, CompleteLessonDialogComponent (including the "mark next" batch flow) and CalendarEventDialogComponent.
  • The drag (move) and resize handlers now share one helper, and so do the request-answer actions.

Bug fixes

  • Personal-event dialog and student-portal lesson request form: both templates checked for an endBeforeStart error, but endAfterStartValidator returns dateRange. As a result, "end time must be after start time" was never shown in either form.

Testing

  • Backend: dotnet test, all 295 tests pass after each backend commit. The run covers lesson status transitions, IDOR, tenant isolation, change requests, messaging and notifications.
  • Frontend: ng test, 89 tests pass, 49 of them new: 32 for the util rules and 17 for the three dialogs (form, validation and service calls, with mocked HTTP). ng build is clean.
  • Manual browser check: Playwright against the running API and dev server. All 13 checks passed: batch marking of two lessons, opening from ?studentId= with the student pre-selected, end time auto-filled from the student's duration, the conflict warning on save, the event time error now visible, saving an event, and no uncaught page errors.

Notes for reviewers

  • Each commit stands on its own: CI → lessons backend → messages backend → calendar frontend → student-portal fix.
  • Not in scope, found along the way: the build reports a known high-severity advisory for System.Security.Cryptography.Pkcs 10.0.0 (GHSA-846f-f764-76mx).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5ZtSuF2RvbretgLfz7fjJ
…ices

LessonsController (621 lines) loaded entities, enforced lesson rules,
checked row-level access and sent notifications itself. It is now a thin
HTTP layer (150 lines, no DbContext) over:

- LessonQueries: read side for teacher, parent and student
- LessonService: teacher calendar actions (create, update, delete,
  complete, no-show, approve/decline new-lesson requests)
- LessonRequestService: family requests and the teacher's answer
- FamilyMembers: current parent/student and child-ownership checks
- TeacherNotifier: teacher notification + email (was private helpers)
- ServiceError/ServiceResult: services report NotFound/Forbidden/
  Conflict/Invalid; the controller maps them to HTTP

Behavior is unchanged; the full backend suite (295 tests) passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5ZtSuF2RvbretgLfz7fjJ
…rvices

MessagesController (572 lines) is now a thin HTTP layer (144 lines, no
DbContext) over:

- MessageThreads: shared thread mechanics (create, append, detail view,
  counterpart resolution)
- MessageQueries: inbox, thread lists, unread counts, recipients
- TeacherMessageService / FamilyMessageService: each side's actions
- MessageNotifier: notification and email to teacher / counterpart

TeacherNotifier gains SendEmailAsync so messages reuse the same
"find teacher, send, log failure" path as lessons instead of a copy.
FamilyMembers.IsChildOfAsync now takes a parent id.

Behavior is unchanged; the full backend suite (295 tests) passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5ZtSuF2RvbretgLfz7fjJ
lessons-list.component.ts was 1,224 lines (plus a 626-line template)
holding the calendar, five dialogs and all scheduling rules. It is now
657 lines (275-line template):

- lesson-scheduling.util.ts: the rules as pure functions — conflicts,
  availability, what awaits marking, next pending request, future
  occurrences of a series, all-day event ranges, amount explanation
- AddLessonDialogComponent: single lesson or weekly series, with the
  conflict / outside-availability warnings
- CompleteLessonDialogComponent: completing a lesson, incl. the
  "mark next" batch flow
- CalendarEventDialogComponent: personal calendar events
- the drag (move) and resize handlers share one helper; request
  answers share one helper

Fix: the personal-event dialog checked for an "endBeforeStart" error
that endAfterStartValidator never returns (it returns "dateRange"), so
"end time must be after start time" was never shown.

Tests: 49 new (40 -> 89) — the util rules, and each dialog's form,
validation and service calls. Also verified in a real browser against
the running API: batch marking, deep link with ?studentId, auto end
time, conflict warning, and the event time error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5ZtSuF2RvbretgLfz7fjJ
The request form's template checked for an "endBeforeStart" error, but
endAfterStartValidator returns "dateRange", so the message never
appeared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V5ZtSuF2RvbretgLfz7fjJ
@vercel

vercel Bot commented Oct 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
talmidon Ready Ready Preview Oct 20, 2026 6:00pm UTC

This branch was successfully deployed

1 active deployment
Preview — f683c38d Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants