Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ private kr.devslab.kit.menu.MenuItem filterByCodes(kr.devslab.kit.menu.MenuItem
.filter(java.util.Objects::nonNull)
.toList();
return new kr.devslab.kit.menu.MenuItem(
item.id(), item.code(), item.label(), item.path(),
item.id(), item.code(), item.label(), item.path(), item.icon(),
item.requiredPermission(), visibleChildren);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,23 @@
import jakarta.validation.constraints.Size;
import java.util.UUID;

/**
* Wire shape for {@code POST /admin/api/v1/menus}.
*
* <p>Field names mirror what {@code devslab-kit-admin-ui} sends:
* {@code displayOrder} (not {@code sortOrder}),
* {@code requiredPermission} (not {@code requiredPermissionCode}),
* plus the new {@code icon} token (e.g. {@code "pi-users"}). The
* entity column names underneath are unchanged.
*/
public record CreateMenuRequest(
@NotBlank @Size(max = 64) String tenantId,
@NotBlank @Size(max = 64) String code,
@NotBlank @Size(max = 255) String label,
@Size(max = 255) String path,
UUID parentId,
int sortOrder,
@Size(max = 128) String requiredPermissionCode
int displayOrder,
@Size(max = 128) String requiredPermission,
@Size(max = 64) String icon
) {
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package kr.devslab.kit.admin.menu;

import jakarta.validation.Valid;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import kr.devslab.kit.admin.AdminApiPaths;
import kr.devslab.kit.core.id.MenuId;
import kr.devslab.kit.core.id.TenantId;
import kr.devslab.kit.menu.core.entity.PlatformMenuEntity;
import kr.devslab.kit.menu.core.service.MenuAdminService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand Down Expand Up @@ -36,20 +40,62 @@ public ResponseEntity<MenuResponse> create(@Valid @RequestBody CreateMenuRequest
req.label(),
req.path(),
req.parentId() == null ? null : MenuId.of(req.parentId()),
req.sortOrder(),
req.requiredPermissionCode()
req.displayOrder(),
req.requiredPermission(),
req.icon()
);
return ResponseEntity.status(201).body(MenuResponse.from(entity));
return ResponseEntity.status(201).body(MenuResponse.flat(entity));
}

@GetMapping
public List<MenuResponse> list(@RequestParam String tenantId) {
return service.listByTenant(TenantId.of(tenantId)).stream().map(MenuResponse::from).toList();
return service.listByTenant(TenantId.of(tenantId)).stream().map(MenuResponse::flat).toList();
}

@GetMapping("/{id}")
public ResponseEntity<MenuResponse> get(@PathVariable UUID id) {
return service.findById(MenuId.of(id))
.map(MenuResponse::flat)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}

/**
* Tree view for the admin UI's TreeTable component. Returns root nodes only;
* each root carries its descendants nested under {@code children}, sorted by
* {@code displayOrder}.
*/
@GetMapping("/tree")
public List<MenuResponse> tree(@RequestParam String tenantId) {
List<PlatformMenuEntity> entities = service.listByTenant(TenantId.of(tenantId));
Map<UUID, List<PlatformMenuEntity>> byParent = entities.stream()
.filter(e -> e.getParentId() != null)
.collect(Collectors.groupingBy(PlatformMenuEntity::getParentId));
return entities.stream()
.filter(e -> e.getParentId() == null)
.sorted(Comparator.comparingInt(PlatformMenuEntity::getSortOrder))
.map(root -> toNode(root, byParent))
.toList();
}

private MenuResponse toNode(PlatformMenuEntity entity, Map<UUID, List<PlatformMenuEntity>> byParent) {
List<MenuResponse> children = byParent.getOrDefault(entity.getId(), List.of()).stream()
.sorted(Comparator.comparingInt(PlatformMenuEntity::getSortOrder))
.map(child -> toNode(child, byParent))
.toList();
return MenuResponse.withChildren(entity, children);
}

@PutMapping("/{id}")
public ResponseEntity<Void> update(@PathVariable UUID id, @Valid @RequestBody UpdateMenuRequest req) {
service.update(MenuId.of(id), req.label(), req.path(), req.sortOrder(), req.requiredPermissionCode());
service.update(
MenuId.of(id),
req.label(),
req.path(),
req.displayOrder(),
req.requiredPermission(),
req.icon()
);
return ResponseEntity.noContent().build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
package kr.devslab.kit.admin.menu;

import java.util.List;
import java.util.UUID;
import kr.devslab.kit.menu.core.entity.PlatformMenuEntity;

/**
* Wire shape for menu items returned by {@code /admin/api/v1/menus*}.
*
* <p>Two representations live here:
*
* <ul>
* <li>{@link #flat(PlatformMenuEntity)} — single entity, {@code children == null}.
* Used by the flat list endpoint and by {@code GET /menus/{id}}.</li>
* <li>{@link #withChildren(PlatformMenuEntity, List)} — entity plus its
* already-built child list. Used by {@code GET /menus/tree}.</li>
* </ul>
*
* <p>Field names mirror the admin UI's {@code MenuItem} interface:
* {@code displayOrder} (not the entity's {@code sortOrder}) and
* {@code requiredPermission} (not {@code requiredPermissionCode}).
*/
public record MenuResponse(
UUID id,
String tenantId,
String code,
String label,
String path,
UUID parentId,
int sortOrder,
String requiredPermissionCode
int displayOrder,
String requiredPermission,
String icon,
List<MenuResponse> children
) {

public static MenuResponse from(PlatformMenuEntity entity) {
public static MenuResponse flat(PlatformMenuEntity entity) {
return new MenuResponse(
entity.getId(),
entity.getTenantId(),
Expand All @@ -23,7 +42,24 @@ public static MenuResponse from(PlatformMenuEntity entity) {
entity.getPath(),
entity.getParentId(),
entity.getSortOrder(),
entity.getRequiredPermissionCode()
entity.getRequiredPermissionCode(),
entity.getIcon(),
null
);
}

public static MenuResponse withChildren(PlatformMenuEntity entity, List<MenuResponse> children) {
return new MenuResponse(
entity.getId(),
entity.getTenantId(),
entity.getCode(),
entity.getLabel(),
entity.getPath(),
entity.getParentId(),
entity.getSortOrder(),
entity.getRequiredPermissionCode(),
entity.getIcon(),
children == null ? List.of() : List.copyOf(children)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

import jakarta.validation.constraints.Size;

/**
* Wire shape for {@code PUT /admin/api/v1/menus/{id}}.
*
* <p>Every field is optional — only those passed in are applied.
* Field names match {@link CreateMenuRequest} (and the admin UI).
*/
public record UpdateMenuRequest(
@Size(max = 255) String label,
@Size(max = 255) String path,
Integer sortOrder,
@Size(max = 128) String requiredPermissionCode
Integer displayOrder,
@Size(max = 128) String requiredPermission,
@Size(max = 64) String icon
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public record MenuItem(
String code,
String label,
String path,
String icon,
Optional<Permission> requiredPermission,
List<MenuItem> children
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public class PlatformMenuEntity {
@Column(name = "required_permission_code", length = 128)
private String requiredPermissionCode;

@Column(name = "icon", length = 64)
private String icon;

@Column(name = "created_at", nullable = false)
private Instant createdAt;

Expand All @@ -59,6 +62,7 @@ public PlatformMenuEntity(
UUID parentId,
int sortOrder,
String requiredPermissionCode,
String icon,
Instant createdAt
) {
this.id = id;
Expand All @@ -69,6 +73,7 @@ public PlatformMenuEntity(
this.parentId = parentId;
this.sortOrder = sortOrder;
this.requiredPermissionCode = requiredPermissionCode;
this.icon = icon;
this.createdAt = createdAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public PlatformMenuEntity create(
String path,
MenuId parentId,
int sortOrder,
String requiredPermissionCode
String requiredPermissionCode,
String icon
) {
PlatformMenuEntity entity = new PlatformMenuEntity(
UUID.randomUUID(),
Expand All @@ -39,20 +40,34 @@ public PlatformMenuEntity create(
parentId == null ? null : parentId.value(),
sortOrder,
requiredPermissionCode,
icon,
Instant.now(clock)
);
repository.save(entity);
return entity;
}

@Transactional
public void update(MenuId id, String label, String path, Integer sortOrder, String requiredPermissionCode) {
public void update(
MenuId id,
String label,
String path,
Integer sortOrder,
String requiredPermissionCode,
String icon
) {
PlatformMenuEntity e = repository.findById(id.value())
.orElseThrow(() -> new IllegalArgumentException("Menu not found: " + id));
if (label != null) e.setLabel(label);
if (path != null) e.setPath(path);
if (sortOrder != null) e.setSortOrder(sortOrder);
if (requiredPermissionCode != null) e.setRequiredPermissionCode(requiredPermissionCode);
if (icon != null) e.setIcon(icon);
}

@Transactional(readOnly = true)
public java.util.Optional<PlatformMenuEntity> findById(MenuId id) {
return repository.findById(id.value());
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ private MenuItem toItem(PlatformMenuEntity entity, Map<UUID, List<PlatformMenuEn
entity.getCode(),
entity.getLabel(),
entity.getPath(),
entity.getIcon(),
requiredPermission,
children
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private MenuItem filterItem(MenuItem item) {
item.code(),
item.label(),
item.path(),
item.icon(),
item.requiredPermission(),
visibleChildren
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Add optional `icon` column to platform_menu.
--
-- The admin UI renders menu items with PrimeIcons-style icon classes
-- (e.g. "pi-users", "pi-cog"); store the raw token here so the UI can
-- compose the full class at render time. Stays nullable -- menu items
-- without an icon render as text-only.

ALTER TABLE platform_menu
ADD COLUMN icon VARCHAR(64);
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ private PlatformMenuEntity entity(String code, UUID parentId, int sortOrder) {
parentId,
sortOrder,
null,
null,
Instant.now()
);
}
Expand Down
Loading