Skip to content

feat(external-api): knowledge base resource - #2132

Open
aliaska-varieva wants to merge 1 commit into
mainfrom
feature/external-api-knowledge-base
Open

aliaska-varieva wants to merge 1 commit into
mainfrom
feature/external-api-knowledge-base

Conversation

@aliaska-varieva

@aliaska-varieva aliaska-varieva commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Exposes the knowledge base on the External API under /api/v1/knowledge-base, following the tickets pattern: the controller runs the shared api-lib domain services (KnowledgeBaseService, KnowledgeBaseTagService, KnowledgeBaseAttachmentService) in-process on behalf of the API key owner. No domain logic is duplicated.

External API (openframe-external-api-service-core)

  • KnowledgeBaseController — 21 operations:
    • items: list (cursor-paginated, parentId/type/statuses/tagIds/search), read, move, add/remove tag
    • folders: tree, create, rename, delete (childrenAction=MOVE|ARCHIVE)
    • articles: archived list, create, update, publish, unpublish, archive, unarchive
    • tags: all / per-folder subtree / archived
    • attachments: upload-url, download-url, delete
  • KnowledgeBaseReadService batches tags + attachment metadata per page and turns unknown ids into 404. The domain services throw a generic IllegalArgumentException for a missing item (GraphQL surfaces it as a payload error); a REST contract needs a proper 404.
  • KnowledgeBaseMapper + REST DTOs in dto/knowledgebase; customer ids exposed under the product name as in the tickets API. List responses omit article content; single-item reads include it.
  • OpenAPI description lists the resource; API version bumped to 1.2.0.

Shared code

  • ErrorCode.KNOWLEDGE_BASE_ITEM_NOT_FOUND
  • KnowledgeBaseTagService.addTagToItem is now idempotent (mirrors TicketTagService), so a repeated public-API call does not create duplicate tag assignments.

Not included

  • Temp-attachment flow and inline image upload: the first is a dashboard two-step for unsaved articles, the second returns a dashboard-served URL unreachable with an API key.
  • Integration tests in openframe-test-service-core (can follow up with an ExternalKnowledgeBaseTest suite).

Test plan

  • mvn -o compile -pl openframe-external-api-service-core -am from source
  • mvn -o compile -pl openframe-api-service-core -am (consumer of the api-lib change)
  • Deploy to dev and exercise the endpoints with an API key via Swagger at /external-api/swagger-ui

Expose the knowledge base on the External API under /api/v1/knowledge-base,
running the shared api-lib domain services (KnowledgeBaseService,
KnowledgeBaseTagService, KnowledgeBaseAttachmentService) in-process on behalf
of the API key owner, the same way the tickets resource does.

- KnowledgeBaseController: list/read/move items, add/remove tag, folder tree,
  create/rename/delete folder, archived articles, create/update article,
  publish/unpublish/archive/unarchive, tags, attachment upload/download/delete
- KnowledgeBaseReadService batches tags + attachment metadata per page and
  turns unknown ids into 404 (the domain services throw a generic
  IllegalArgumentException that GraphQL surfaces as a payload error)
- KnowledgeBaseMapper + REST DTOs; customers exposed under the product name
- ErrorCode.KNOWLEDGE_BASE_ITEM_NOT_FOUND
- KnowledgeBaseTagService.addTagToItem is now idempotent, mirroring the ticket
  tag service, so repeated public-API calls do not create duplicate assignments
- OpenAPI description lists the resource; API version bumped to 1.2.0
Comment on lines +55 to +59
public Tag requireTag(String tagId) {
return tagRepository.findById(tagId)
.filter(tag -> tag.getEntityType() == TagEntityType.KNOWLEDGE_ARTICLE)
.orElseThrow(() -> new NotFoundException(ErrorCode.TAG_NOT_FOUND, "Tag not found: " + tagId));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🦩 🟠 [warn/recommended] OFJAVA-013 KnowledgeBaseReadService.requireTag throws generic NotFoundException instead of typed KnowledgeBaseTagNotFoundException

requireTag throws com.openframe.core.exception.NotFoundException directly with ErrorCode.TAG_NOT_FOUND, rather than a dedicated exception type mirroring the KnowledgeBaseItemNotFoundException pattern introduced in this same PR. This is inconsistent with the pattern established by requireItem/requireItem(id, type) in the same class and with OPENFRAM-003-14's requirement that domain error conditions use named exception classes. It also means the 404-vs-other-tag-types nuance (a tag belonging to a different TagEntityType) is indistinguishable from a truly nonexistent tag at the exception-type level.

Evidence
public Tag requireTag(String tagId) {
        return tagRepository.findById(tagId)
                .filter(tag -> tag.getEntityType() == TagEntityType.KNOWLEDGE_ARTICLE)
                .orElseThrow(() -> new NotFoundException(ErrorCode.TAG_NOT_FOUND, "Tag not found: " + tagId));
    }
🤖 Prompt for AI agents
In openframe-external-api-service-core/src/main/java/com/openframe/external/service/KnowledgeBaseReadService.java around lines 55-59, address this code-review finding: KnowledgeBaseReadService.requireTag throws generic NotFoundException instead of typed KnowledgeBaseTagNotFoundException.
requireTag throws com.openframe.core.exception.NotFoundException directly with ErrorCode.TAG_NOT_FOUND, rather than a dedicated exception type mirroring the KnowledgeBaseItemNotFoundException pattern introduced in this same PR. This is inconsistent with the pattern established by requireItem/requireItem(id, type) in the same class and with OPENFRAM-003-14's requirement that domain error conditions use named exception classes. It also means the 404-vs-other-tag-types nuance (a tag belonging to a different TagEntityType) is indistinguishable from a truly nonexistent tag at the exception-type level.
The flagged code:
```
public Tag requireTag(String tagId) {
        return tagRepository.findById(tagId)
                .filter(tag -> tag.getEntityType() == TagEntityType.KNOWLEDGE_ARTICLE)
                .orElseThrow(() -> new NotFoundException(ErrorCode.TAG_NOT_FOUND, "Tag not found: " + tagId));
    }
```
Make the minimal change that resolves the finding; do not refactor unrelated code.

confidence: 45 — react 👍/👎 to teach the reviewer

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🦩 Flamingo Code Review

1 finding(s) — 0 action required · 1 recommended · 0 informational

Mode: advisory · Rules cited: OFJAVA-013


Need another pass? Commits pushed after this review are not reviewed automatically.

  • Review the new commits — the commits added since this review
  • Review the whole diff again — ignoring what was already reviewed

Prefer typing? Comment @flamingo-review, or @flamingo-review full. To review every push on this pull request, add the flamingo-review-always label.

React 👍/👎 on inline comments to teach the reviewer.

@aliaska-varieva
aliaska-varieva requested a review from a team September 9, 2026 23:56
@aliaska-varieva aliaska-varieva self-assigned this Sep 9, 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.

1 participant