Repository navigation
Persist the dashboard layout server-side (#213) - #270
Conversation
Adds GET/PUT/DELETE /api/v1/users/me/dashboard/layout: one arrangement per user, stored as JSON with the client's layout version. A missing layout or one written under another version answers with the default instead of a 404. The caller is always resolved from the token, and a user's layout is removed together with the user. The per-board arrangement already lives in /me/board/structure.
DavidLeuter
left a comment
There was a problem hiding this comment.
Looks good, approving. The API is small and self-serve only, and the "no layout / other version → 200 with updatedAt: null" contract is clear, documented and tested. The upsert follows the board_structures pattern, and the user-deletion hook is covered. I also like that the version is the client's rather than a server constant.
Minor, non-blocking:
DashboardLayoutItemPayload/DashboardLayoutLimitslive inmodel.entity, but they're wire DTOs with validation annotations and not JPA entities.model.request.dashboard(or a sharedmodel.dashboard) would be a better home. Fine if you mirrored the board's layout on purpose.there is no endpoint that reads another user's layout by idonly proves that an unmapped path answers 404, which would stay true even if such an endpoint existed under a different shape. The service tests cover the real guarantee (user always resolved from the token), so I'd just drop this one or rename it to what it actually checks.- As with the other migrations,
V21__…is documentation only, since there's no Flyway andddl-autocreates the table. Mentioning it so nobody expects the unique constraint to exist on a DB that predates ddl-auto picking up the entity. On a fresh schema Hibernate emits it from@UniqueConstraint, so theON CONFLICT (user_id)upsert is fine.
The frontend half (sprintstart-frontend#288) has one issue around reset + migration that I've requested changes for. It doesn't need any backend change.
DashboardLayoutItemPayload and DashboardLayoutLimits are wire DTOs, not JPA entities, so they now live in model.request.dashboard. Also drops the controller test that only proved an unmapped path answers 404; the service tests cover that a layout is always resolved from the token.
|
Picked up the non-blocking points in c9031b1:
|
DavidLeuter
left a comment
There was a problem hiding this comment.
Thanks for picking up the nits. c9031b1 is a pure package move plus dropping the 404 test, so my approval stands. The frontend half is approved as well now. Merge once CI is green.
Closes #213
What
Persists the dashboard layout server-side, one per user:
GET /api/v1/users/me/dashboard/layout?version=N– the caller's layoutPUT /api/v1/users/me/dashboard/layout– replaces it ({ version, items: [{ id, size }] })DELETE /api/v1/users/me/dashboard/layout– forgets it (reset), 204The per-board half of the issue already exists: areas, folds, sizes, pins etc. are stored per board through
/api/v1/onboarding/me/board/structure. So both scopes are covered — one board arrangement perboardId, one dashboard layout per user — without flattening them into one key.How it maps to the acceptance criteria
DashboardLayoutController+DashboardLayoutServicein theusermodule, stored as JSON indashboard_layouts(one row per user, upsert, last write wins – same pattern asboard_structures).LAYOUT_VERSIONin the frontend); a row of any other version answers as the default. The version is the client's rather than a server constant because only the client knows its widget and size vocabulary — otherwise every frontend change to it would need a backend release.items: []andupdatedAt: null. An empty list with a non-nullupdatedAtmeans "removed everything on purpose".DashboardLayoutServiceTest(round trip, absent layout, version mismatch, unreadable row, cross-user refusal, reset) andDashboardLayoutControllerTest(status codes, validation, auth).Also: a user's layout is deleted together with the user (
UserService.deleteUserById), andV21__add_dashboard_layouts.sqlrecords the table likeV11does for board structures.Tests
./gradlew clean checkpasses locally.Frontend: SprintStartProject/sprintstart-frontend#288