-
Notifications
You must be signed in to change notification settings - Fork 0
feat(api): implement POST endpoint for searching entities #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
3077707
b21dfc5
9cbad52
6334a57
90ec141
4b6b0ee
bc1f913
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,4 +54,4 @@ repos: | |
| language: system | ||
| pass_filenames: false | ||
| files: \.java$ | ||
| stages: [commit] | ||
| stages: [pre-commit] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| -- Initialize PostgreSQL extensions required by the application. | ||
| -- This script runs once on first container startup (docker-entrypoint-initdb.d). | ||
| CREATE EXTENSION IF NOT EXISTS pg_trgm; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.decathlon.idp_core.domain.constant; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| /// Domain constants for the entity filter query DSL safety limits. | ||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public final class FilterConstraints { | ||
|
|
||
| /// Maximum number of filter criteria per `q` query string (DoS prevention). | ||
| public static final int MAX_CRITERIA_COUNT = 10; | ||
|
|
||
| /// Maximum length (in characters) of a key or value in a single filter | ||
| /// criterion. | ||
| public static final int MAX_KEY_VALUE_LENGTH = 255; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.decathlon.idp_core.domain.constant; | ||
|
|
||
| import java.util.Set; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| /// Domain constants for search and filter query safety limits. | ||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public final class SearchConstraints { | ||
|
|
||
| /// Maximum number of entities returned per page in a search request. | ||
| public static final int MAX_PAGE_SIZE = 500; | ||
|
|
||
| /// Maximum length (in characters) of the free-text `query` parameter. | ||
| public static final int MAX_QUERY_LENGTH = 255; | ||
|
|
||
| /// Maximum nesting depth of a | ||
| /// [com.decathlon.idp_core.domain.model.entity.SearchFilterNode] tree. | ||
| public static final int MAX_NESTING_DEPTH = 5; | ||
|
|
||
| /// Maximum total number of criterion nodes across a | ||
| /// [com.decathlon.idp_core.domain.model.entity.SearchFilterNode] tree. | ||
| public static final int MAX_TOTAL_CRITERIA = 50; | ||
|
|
||
| /// Fields on which search results may be sorted. | ||
| public static final Set<String> ALLOWED_SORT_FIELDS = Set.of("identifier", "name", | ||
| "templateIdentifier"); | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.decathlon.idp_core.domain.exception.filter; | ||
|
|
||
| /// Domain exception thrown when the `q` filter query string contains invalid syntax. | ||
| /// | ||
| /// **Business semantics:** Signals that the caller provided a malformed filter query | ||
| /// for the `GET /api/v1/entities/{template}?q=` endpoint. This exception should be | ||
| /// mapped to HTTP 400 Bad Request by the infrastructure layer. | ||
| public class InvalidFilterDslException extends RuntimeException { | ||
|
|
||
| public InvalidFilterDslException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.decathlon.idp_core.domain.exception.search; | ||
|
|
||
| /// Domain exception thrown when a search filter tree or free-text query contains invalid syntax. | ||
| /// | ||
| /// **Business semantics:** Signals that the caller provided a malformed search request | ||
| /// for the `POST /api/v1/entities/search` endpoint. This exception should be mapped to | ||
| /// HTTP 400 Bad Request by the infrastructure layer. | ||
| public class InvalidSearchQueryException extends RuntimeException { | ||
|
|
||
| public InvalidSearchQueryException(String message) { | ||
| super(message); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.decathlon.idp_core.domain.model.entity; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. RawSearchFilterNode représente un arbre de filtre brut utilisé pour le parsing et la validation, plutôt qu'une entité métier.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Effectivement j'ai déplacé les objets domaines liés au search dans un package dédié domain.search 👍 |
||
|
|
||
| import java.util.List; | ||
|
|
||
| /// A domain-native raw node of the search filter tree, produced by the infrastructure mapper | ||
| /// before domain parsing and validation. | ||
| /// | ||
| /// **Responsibility:** Carries the unvalidated, string-only representation of a filter tree | ||
| /// from the API adapter into the domain parser. All fields are raw strings — no enums, | ||
| /// no framework types. Structural conversion from [com.decathlon.idp_core.infrastructure.adapters.api.dto.in.FilterNodeDtoIn] | ||
| /// to this type is handled by the infrastructure mapper; validation and enum resolution | ||
| /// are handled by the domain parser. | ||
| /// | ||
| /// **Nodes:** | ||
| /// - [Group] — a logical group with a raw connector string and child nodes | ||
| /// - [Criterion] — a leaf predicate with raw field, operation, and value strings | ||
| public sealed interface RawSearchFilterNode { | ||
|
|
||
| /// A logical group combining multiple child [RawSearchFilterNode]s. | ||
| /// | ||
| /// @param connector raw connector string (e.g. "AND", "OR"); may be null or | ||
| /// blank until validated | ||
| /// @param nodes child nodes; may be null until validated | ||
| record Group(String connector, List<RawSearchFilterNode> nodes) implements RawSearchFilterNode { | ||
| public Group { | ||
| nodes = nodes != null ? List.copyOf(nodes) : List.of(); | ||
| } | ||
| } | ||
|
|
||
| /// A leaf predicate in the filter tree. | ||
| /// | ||
| /// @param field raw field name (e.g. "template", "property.language"); may be | ||
| /// null until validated | ||
| /// @param operation raw operation string (e.g. "EQ", "CONTAINS"); may be null | ||
| /// until validated | ||
| /// @param value raw value string; may be null until validated | ||
| record Criterion(String field, String operation, String value) implements RawSearchFilterNode { | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.