feat(menus): tree endpoint + get-by-id + icon + admin-ui field names#14
Merged
Conversation
The admin UI's Menus page calls three things this controller didn't
expose, plus a column the entity didn't have:
- GET /admin/api/v1/menus/tree?tenantId=... → nested tree (TreeTable)
- GET /admin/api/v1/menus/{id} → single item
- POST/PUT body fields displayOrder / requiredPermission / icon
(not sortOrder / requiredPermissionCode / —)
- MenuItem.icon for sidebar rendering (e.g. "pi-users")
This PR closes that gap while keeping the entity column names
stable, so nothing downstream of MenuAdminService breaks.
Schema
------
- V8__platform_menu_icon.sql adds `icon VARCHAR(64)` to platform_menu,
nullable. Existing rows unaffected; the column is text-only on the
UI when null.
Entity / API
------------
- PlatformMenuEntity: new icon field + extended all-args constructor.
- MenuItem: new icon field, propagated by MenuTreeBuilder and
PermissionBasedMenuFilter so visibility-filtered trees keep icons.
- MenuAdminService.create / update: new icon parameter; partial-
update semantics on update (icon updates only if non-null).
- MenuAdminService.findById: new read used by GET /menus/{id}.
DTOs
----
- CreateMenuRequest / UpdateMenuRequest: rename to the wire shape the
admin UI sends — displayOrder (was sortOrder), requiredPermission
(was requiredPermissionCode), plus icon. Validation constraints
preserved.
- MenuResponse: two factory methods. `flat(entity)` for the existing
list + new get-by-id endpoint (children == null). `withChildren`
for the new tree endpoint (children == nested list). Wire field
names also follow the UI: displayOrder, requiredPermission, icon.
Controller
----------
- New GET /menus/tree builds the nested response in-controller from
the same JPA repository the flat list uses (single round-trip,
sorted by displayOrder at every level).
- New GET /menus/{id} returns 404 on miss.
- Existing POST/PUT/GET/DELETE adjusted to the renamed DTO fields.
Other call-sites
----------------
- DiagnosticsController.filterByCodes preserves the icon when
pruning the menu tree by user permissions.
- MenuTreeBuilderTest fixture updated to the new entity constructor
(icon = null).
V8 migration comment intentionally avoids `${...}` because Flyway's
PlaceholderReplacingReader scans SQL comments too — the first run
hit `org.flywaydb.core.api.FlywayException` in sample-app tests
because the original comment contained `pi pi-${icon}`.
This was referenced May 27, 2026
This was referenced May 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First PR in a small series closing contract gaps between the admin-ui PRs (devslab-kr/devslab-kit-admin-ui#2–#7) and the backend on
main. The admin-ui'sMenusViewcalls three things this controller didn't expose, plus a column the entity didn't have.Summary
Endpoints
GET /admin/api/v1/menus/tree?tenantId=...— new. Returns the nested tree the TreeTable component needs (each root carries its descendants underchildren, sorted bydisplayOrderat every level). Built in-controller from the existing flat repo query — single round-trip.GET /admin/api/v1/menus/{id}— new. Returns 404 on miss.DTO wire-shape (matches the admin-ui)
CreateMenuRequest/UpdateMenuRequest— fields renamed to what the admin UI sends:displayOrder(wassortOrder),requiredPermission(wasrequiredPermissionCode), plus the newicon. Validation constraints preserved.MenuResponse— two factory methods.flat(entity)for the existing list + new get-by-id (children = null).withChildren(entity, list)for the tree endpoint. Wire field names also follow the UI.Entity / schema
PlatformMenuEntity— newiconfield (nullable,VARCHAR(64)). All-args constructor extended.V8__platform_menu_icon.sql—ALTER TABLE platform_menu ADD COLUMN icon VARCHAR(64);. Existing rows unaffected; icon-less items render text-only on the UI.Service layer
MenuAdminService.create / update— newiconparameter; partial-update semantics onupdate(icon updates only if non-null), matching the existing pattern for label/path/sortOrder.MenuAdminService.findById— new read used byGET /menus/{id}.Tree propagation
MenuItemAPI record — newiconfield.MenuTreeBuilderreads it from the entity.PermissionBasedMenuFilterpreserves it when pruning by user permissions.DiagnosticsController.filterByCodesdoes the same when computing menu visibility.Test fixture
MenuTreeBuilderTestentity factory updated to the new constructor (icon = null).Gotcha worth flagging
The V8 migration comment intentionally avoids
${...}even though it's just a code-comment example. Flyway'sPlaceholderReplacingReaderscans SQL comments too — my first attempt hadpi pi-${icon}in a--line and the sample-app integration tests blew up withorg.flywaydb.core.api.FlywayException. Easy fix once you know.Test plan
./gradlew buildgreen (55 tasks, all sample-app + menu-core tests pass)What's next in this series
Other gap closures still queued (separate PRs): AuditLogs (paging + filters + field rename), Policies (PolicyDescriptor list + nested test body), Tenants (status enum + createdAt), Diagnostics + Settings field names.