Skip to content

CELDEV-1334 Add navigation REST API - #309

Open
fpichler wants to merge 2 commits into
devfrom
feature/CELDEV-1334
Open

CELDEV-1334 Add navigation REST API#309
fpichler wants to merge 2 commits into
devfrom
feature/CELDEV-1334

Conversation

@fpichler

@fpichler fpichler commented Jul 31, 2026

Copy link
Copy Markdown
Member

https://synjira.atlassian.net/browse/CELDEV-1334

Summary

  • add celements-navigation-rest with the public /api/v1/navigation/{nodeSpace} contract
  • enforce current-wiki canonical references, caller-relative rights filtering, localized titles and URLs, stable segmented JSON, and safe errors
  • expose OpenAPI documentation and apply Cache-Control: private, no-store to successful and error responses
  • cover request validation, guest/authenticated rights behavior, expansion, ordering, mandatory-value omission, JSON, errors, and OpenAPI

Coordinated delivery

Draft 2 of 4 for CELDEV-1334:

  1. CELDEV-1334 Add current-wiki REST reference ADR celements-spring#31
  2. CELDEV-1334 Add navigation REST API #309
  3. CELDEV-1334 Manage navigation REST artifact celements-parent-poms#142
  4. CELDEV-1334 Deploy navigation REST component celements-web#556

Validation

  • offline clean verify: 43 tests, zero failures/errors
  • Maven dependency analysis: no problems
  • formatter, diff check, and fully-qualified-name scan: clean
  • packaged REST JAR matched the copy embedded in the coordinated webapp WAR
  • local guest and error-contract runtime smoke checks passed
  • manually tested and approved by the requester

@fpichler
fpichler requested a review from msladek July 31, 2026 22:59
@fpichler
fpichler marked this pull request as ready for review July 31, 2026 22:59
Comment on lines +13 to +14
@RestControllerAdvice(assignableTypes = NavigationController.class)
public final class NavigationExceptionHandler {

@msladek msladek Aug 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move these @ExceptionHandler methods into NavigationController. NavigationExceptionHandler applies only to NavigationController. Separate advice adds another component and cross-file control flow without reuse. extract shared handlers when multiple controllers use the same error contract.

Comment on lines +39 to +95
@GetMapping(path = "/{nodeSpace}", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("permitAll()")
@Operation(summary = "Get the current wiki's navigation tree", description = """
Public endpoint returning a caller-relative, rights-filtered navigation tree.
Input and output references are canonical and local to the wiki handling the request.
Responses are private and not cacheable by shared or browser caches.
""")
@ApiResponses({
@ApiResponse(responseCode = "200",
description = "The rights-filtered segmented navigation tree",
content = @Content(schema = @Schema(implementation = NavigationTreeResponse.class),
examples = @ExampleObject(value = """
{
"nodeSpace": "Content",
"currentNode": "Content.MyPage",
"language": "de",
"partName": null,
"showInactiveToLevel": 2,
"segments": []
}
"""))),
@ApiResponse(responseCode = "400",
description = "Invalid reference or parameter, or unsupported language",
content = @Content(schema = @Schema(implementation = NavigationErrorResponse.class),
examples = @ExampleObject(value = """
{"code":"invalid_reference","message":"The reference is invalid."}
"""))),
@ApiResponse(responseCode = "404",
description = "The active node is absent, inaccessible, out of root, or part-excluded",
content = @Content(schema = @Schema(implementation = NavigationErrorResponse.class),
examples = @ExampleObject(value = """
{
"code": "navigation_node_not_found",
"message": "The navigation node was not found."
}
"""))),
@ApiResponse(responseCode = "500", description = "Navigation infrastructure is unavailable",
content = @Content(schema = @Schema(implementation = NavigationErrorResponse.class),
examples = @ExampleObject(value = """
{
"code": "navigation_unavailable",
"message": "Navigation is currently unavailable."
}
"""))) })
public ResponseEntity<NavigationTreeResponse> getNavigation(
@Parameter(description = "Canonical local space reference identifying the navigation root",
example = "Content", required = true) @PathVariable String nodeSpace,
@Parameter(description = "Canonical local document reference identifying the active node",
example = "Content.MyPage") @RequestParam(required = false) String currentNode,
@Parameter(description = "Allowed wiki language; defaults to the current request language",
example = "de") @RequestParam(required = false) String language,
@Parameter(
description = "Case-sensitive root part filter; missing or blank returns all parts",
example = "main") @RequestParam(required = false) String partName,
@Parameter(description = "Inactive expansion threshold from 0 through 100", example = "2",
schema = @Schema(defaultValue = "0", minimum = "0", maximum = "100")) @RequestParam(
name = "show_inactive_to_level", defaultValue = "0") int showInactiveToLevel) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annotation soup.

Springdoc already infers request parameters, success responses, and record schemas. Remove inline JSON examples and annotations that merely restate names or types. Keep only a short operation summary, non-obvious constraints, and explicit error responses. Simplify OpenAPI tests to verify structural contract instead of prose and examples. Do not move duplication into interfaces, wrappers, or custom annotations.

Applies to:

  • NavigationController.java
  • NavigationErrorResponse.java
  • NavigationNodeDto.java
  • NavigationSegmentDto.java
  • NavigationTreeResponse.java
  • NavigationOpenApiContractTest.java

Comment on lines +86 to +88
return groupedRoots.entrySet().stream().sorted(Map.Entry.comparingByKey(PART_NAME_COMPARATOR))
.map(entry -> new NavigationSegmentDto(emptyToNull(entry.getKey()),
entry.getValue().stream().map(root -> toDto(root, 1, activePath, request)).toList()))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one chain call per line if a full chain doesn't fit in one line. please apply this readability concern for the entire file.

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

public class NavigationControllerTest {

@msladek msladek Aug 2, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests in this PR do not follow celements-testing conventions: they manually construct components and mocks instead of using AbstractComponentTest, registerComponentMocks(...), and getBeanFactory(). This bypasses real Spring/XWiki component wiring.

Please install relevant skills globally https://github.com/celements/synventis-tools/blob/dev/skills/README.md

@msladek msladek assigned fpichler and unassigned msladek Aug 2, 2026
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