-
Notifications
You must be signed in to change notification settings - Fork 2
160 feature enhanced coaching sessions endpoint #202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
160 feature enhanced coaching sessions endpoint #202
Conversation
…ns, and overarching goals
1. GET /users/{user_id}/organizations
- Returns all organizations a user belongs to (via user_roles table)
- Replaces organizations_users with user_roles for future compatibility
2. GET /users/{user_id}/actions
- Query params: coaching_session_id (optional), status (optional), sorting
- Returns actions for the user with optional filtering by session and status
3. GET /users/{user_id}/coaching_sessions
- Query params: from_date (optional), to_date (optional), sorting
- Returns coaching sessions where user is coach OR coachee
- Supports date range filtering
4. GET /users/{user_id}/overarching_goals
- Query params: coaching_session_id (optional), sorting
- Returns overarching goals for the user
- Updated `organization::find_by_user` to use `user_roles` instead of `organizations_users`
- Added `coaching_session::find_by_user` to query sessions via coaching_relationships
- Added tests for both new functions
- Created params/user/ module with IndexParams for each resource type
- Created controller/user/ with controllers for each endpoint
- Added protection middleware in protect/users/ for authorization
- All endpoints enforce that authenticated_user.id matches user_id parameter
- Added 4 new route functions with proper middleware layering
- Registered all endpoints in OpenAPI spec
- Maintained existing API stability by creating new endpoints
- Follows existing multi-layered architecture pattern
- Implements proper error handling and logging
- Uses WithSortDefaults trait for consistent sorting behavior
- Maintains backward compatibility for existing user params
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit adds an enhanced user-scoped coaching sessions endpoint that supports optional batch loading of related resources to eliminate N+1 queries. Backend changes: - Add EnrichedSession and IncludeOptions to entity_api layer - Implement find_by_user_with_includes with efficient batch loading - Add IncludeParam enum for query parameter validation - Add EnrichedCoachingSession response DTO - Update user coaching_session_controller to use enhanced endpoint Technical improvements: - Single API call replaces 3+ separate queries - Batch loads relationships, users, organizations, goals, agreements - Validates include parameters (organization requires relationship) - Maintains backward compatibility with existing endpoints Related to Today's Sessions UI feature (Issue #160)
- Move validation from web to entity_api layer for proper error propagation - Add database-level sorting to find_by_user_with_includes - Simplify comma-separated deserializer with functional style - Use entity_api type directly with serde flatten, eliminating response DTO - Fix pre-existing test failure in organization.rs Impact: Removes 151 lines, improves consistency with codebase patterns
…ds added in this PR
ed975a1 to
502767e
Compare
Add missing import and fix assertions in documentation example to make the doc-test pass. The example now properly demonstrates both valid and invalid usage patterns with appropriate assertions.
Replace manual Default implementation with derived Default trait. Mark InProgress as the default variant using #[default] attribute.
calebbourg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a lot of really awesome stuff!
I'm impressed with the "includes" concept and where it's going. Very clever and awesome thinking.
I left a few comments calling out some things I think can be modified to be inline with some of our historical design decisions and patterns but there are some larger architectural things that I anticipate may resolve the other comments as well:
- We have existing controllers for many of the ones defined here in this PR. I see that you have scoped the controllers within the user domain. (
/{user_id}/{resource}). This isn't a bad idea and is worth considering. There becomes a lot of duplicated code thought, between the existing controllers and the new controllers. Just something to think about here pretty deeply as it's a large design choice. If we decide to use the existing controllers we would need to pass the user ID in as a query parameter and add some logic to check in theprotectmodules. Example. Your approach may actually be cleaner so just bringing this up to think about. The goal in me bringing it up is to reuse as much code as possible. - Finding a way to use our existing
querymodule, I believe, is paramount as it was specifically designed to remove the need for the manyifconditionals by allowing theweblayer to describe what query parameters are possible through the use of things likeIntoQueryFilterMap.IntoQueryFilterMapis exposed fromentity_apiintowebas a way forwebto provide information back down intoentity_apifunctions without having a circular dependency between the two crates. We can change the mechanics ofqueryor even move the includes concept into a more generic concept similar toquerybut I thing we need to maintain the larger concepts to preserve decoupling and dependency hierarchy. Very happy to think through this with you.
| #[sea_orm(string_value = "not_started")] | ||
| NotStarted, | ||
| #[sea_orm(string_value = "in_progress")] | ||
| #[default] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
entity_api/src/coaching_session.rs
Outdated
| pub enum SortField { | ||
| Date, | ||
| CreatedAt, | ||
| UpdatedAt, | ||
| } | ||
|
|
||
| /// Sort order for queries | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub enum SortOrder { | ||
| Asc, | ||
| Desc, | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe these should belong in web as they are the concerns of incoming API requests.
We already have a SortOrder defined here and I think we should use that if we can.
If we have a good argument for moving this concept into entity_api then we will need to find a more generic solution to port the other domains that use SortOrder (in web) to the new pattern.
Having two separate patterns, I think, might come back to bite us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in commit 77d70dc
entity_api/src/coaching_session.rs
Outdated
| from_date: Option<chrono::NaiveDate>, | ||
| to_date: Option<chrono::NaiveDate>, | ||
| sort_by: Option<SortField>, | ||
| sort_order: Option<SortOrder>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should find a way to use the query module here.
https://github.com/refactor-group/refactor-platform-rs/blob/main/entity_api/src/query.rs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thought... I've partially addressed this by updating the function signature to accept Option<coaching_sessions::Column> and Option<sea_orm::Order> directly (the same SeaORM types that query::find_by uses internally).
However, we can't fully use query::find_by here because it only supports simple column-based equality filtering. Our function needs complex JOINs with coaching_relationships (to filter by user as coach OR coachee), custom date range logic, and batch loading of related data.
The web layer now uses the QuerySort trait to convert params to these SeaORM types, which matches the pattern used by query::find_by - we just can't delegate to it directly due to the query complexity.
entity_api/src/coaching_session.rs
Outdated
| to_date: Option<chrono::NaiveDate>, | ||
| sort_by: Option<SortField>, | ||
| sort_order: Option<SortOrder>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, we can't use query::find_by here either. The find_by_user function needs to JOIN with the coaching_relationships table and filter with an OR condition (coach_id = user_id OR coachee_id = user_id). The query::find_by helper only supports simple column-based equality filtering - it iterates through entity columns and applies column.eq(value) filters. It doesn't support JOINs or OR conditions, so we need to manually build the query for this use case.
entity_api/src/coaching_session.rs
Outdated
| if let Some(from) = from_date { | ||
| query = query.filter(coaching_sessions::Column::Date.gte(from)); | ||
| } | ||
|
|
||
| if let Some(to) = to_date { | ||
| // Use next day with less-than for inclusive end date | ||
| let end_of_day = to.succ_opt().unwrap_or(to); | ||
| query = query.filter(coaching_sessions::Column::Date.lt(end_of_day)); | ||
| } | ||
|
|
||
| // Apply sorting if both field and order are provided | ||
| if let (Some(field), Some(order)) = (sort_by, sort_order) { | ||
| let sea_order = match order { | ||
| SortOrder::Asc => sea_orm::Order::Asc, | ||
| SortOrder::Desc => sea_orm::Order::Desc, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
using query, I believe will remove the need for all of these conditionals. The conditional logic is moved upstream by having params related structs implement IntoQueryFilterMap which is defined here.
We already have an example of this for Coaching Sessions which we may be able to use out of the box here and then define functions that use this mechanism and allow for the includes concept.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will hold off on this until you verify my findings for not being able to currently use query.
web/src/protect/users/mod.rs
Outdated
|
|
||
| pub(crate) mod passwords; | ||
|
|
||
| // checks: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on our current patterns, these should be extracted into their own modules and the function names should match the controller action names. See organizations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit b307fd3
…protect/organizations
Thanks. I thought about this pattern too but decided on the path I took because I thought you had already established this pattern in |
calebbourg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking really good!
Thank you for looking into using the generic query function. I see what you're describing where it doesn't allow for more complex situations that are required here. I appreciate you looking into it and for your patience with the reviews and other suggestions! Maybe we can come back and see if we can find a way to make query more flexible.
I also think your choice for the new controllers is the right one. Thanks for accommodating my needing to think it through a bit as well. This is cleaner.
I left a few comments relating to the use of IndexParams. If we could move as much of the logic from the controllers into the respective modules where the IndexParams structs are defined that would be the icing on the cake here.
For context on my thought process: My preference is to try to keep as much actual logic out of controllers as possible so any opportunity to keep the logic paired with where the data structures are defined is a win.
I don't think it's the end of the world to have logic in controllers and some is certainly necessary. Just more of a trying to combat clutter wherever possible as, probably, a futile goal :)
I realized after the fact that you had already defined new params modules for these new controllers. That's totally the right approach I think. The functions for adding user_id to the IndexParams structs can go there, not in the params modules relating to the other existing controllers.
If we can make those changes (if they are possible and make sense) I think this will be good to go!
calebbourg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beautiful! Thanks for working through all of it
Description
This PR adds enhanced user-scoped endpoints for coaching sessions with optional batch loading to eliminate N+1 queries, plus additional user-scoped endpoints for organizations, actions, and overarching goals.
GitHub Issue: Relates to #160
Changes
/users/{user_id}/coaching_sessionswith optionalincludequery parameter supporting batch loading of relatedresources (relationships, users, organizations, goals, agreements)
find_by_user_with_includesorganizations_usersentity back to support batch loading while maintaining user_roles as primary relationship tableauthenticated_user.idmatchesuser_idparameterTesting Strategy
/users/{user_id}/coaching_sessions?include=relationship/users/{user_id}/coaching_sessions?include=relationship,organizationConcerns
organizations_userstable was added back after being previously scheduled for removal. This decision supports efficient batch loading while keepinguser_roles as the primary source of truth for user-organization relationships.
★ Insight ─────────────────────────────────────
─────────────────────────────────────────────────