diff --git a/backend/src/main/java/com/vaessl/app/connection/Endpoint.java b/backend/src/main/java/com/vaessl/app/connection/Endpoint.java index 619e914..da8d35a 100644 --- a/backend/src/main/java/com/vaessl/app/connection/Endpoint.java +++ b/backend/src/main/java/com/vaessl/app/connection/Endpoint.java @@ -2,7 +2,7 @@ package com.vaessl.app.connection; public enum Endpoint { HOMEBOX_LOGIN("/api/v1/users/login"), LOGIN("/login"), CONNECTION_STATUS( - "/connections/status"), HOMEBOX_QUERY_ALL_ITEMS("/api/v1/items"), SEARCH("/search"); + "/connections/status"), HOMEBOX_QUERY_ALL_ITEMS("/api/v1/entities"), SEARCH("/search"); private final String value; diff --git a/backend/src/main/java/com/vaessl/app/connection/HomeboxConnectionProvider.java b/backend/src/main/java/com/vaessl/app/connection/HomeboxConnectionProvider.java index 7685008..6f664ff 100644 --- a/backend/src/main/java/com/vaessl/app/connection/HomeboxConnectionProvider.java +++ b/backend/src/main/java/com/vaessl/app/connection/HomeboxConnectionProvider.java @@ -58,7 +58,7 @@ public class HomeboxConnectionProvider implements ConnectionProvider { .body(HomeboxLoginResponse.class); if (hbResponse == null) { - throw new RemoteApiException(request.appUrl()); + throw new RemoteApiException(request.appUrl(), HOMEBOX_LOGIN.getValue()); } Map attachmentToken = new HashMap<>(); diff --git a/backend/src/main/java/com/vaessl/app/exception/ErrorMessage.java b/backend/src/main/java/com/vaessl/app/exception/ErrorMessage.java index f4013b1..7e4fc96 100644 --- a/backend/src/main/java/com/vaessl/app/exception/ErrorMessage.java +++ b/backend/src/main/java/com/vaessl/app/exception/ErrorMessage.java @@ -16,7 +16,9 @@ public enum ErrorMessage { HttpStatus.NOT_FOUND, "No active connection found for this service."), REMOTE_API_EMPTY_RESPONSE( HttpStatus.BAD_GATEWAY, - "Remote API returned empty response for "); + "Remote API returned empty response for "), REMOTE_API_CLIENT_ERROR( + HttpStatus.BAD_GATEWAY, + "Remote API returned an unexpected error: "); private final HttpStatus status; private final String message; diff --git a/backend/src/main/java/com/vaessl/app/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/vaessl/app/exception/GlobalExceptionHandler.java index 3adbd51..8019f6a 100644 --- a/backend/src/main/java/com/vaessl/app/exception/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/vaessl/app/exception/GlobalExceptionHandler.java @@ -1,5 +1,7 @@ package com.vaessl.app.exception; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.ProblemDetail; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -16,6 +18,8 @@ import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + @ExceptionHandler(MethodArgumentNotValidException.class) public ProblemDetail handleEmptyCredentialInput(MethodArgumentNotValidException e) { @@ -33,6 +37,15 @@ public class GlobalExceptionHandler { UNAUTHORIZED_WRONG_LOGIN.getMessage()); } + // Catches any other 4xx from a remote API (e.g. 404 caused by a changed endpoint). + // Must come after the Unauthorized handler so Spring matches 401 there first. + @ExceptionHandler(HttpClientErrorException.class) + public ProblemDetail handleRemoteClientError(HttpClientErrorException e) { + log.error("Remote API returned {}: {}", e.getStatusCode(), e.getResponseBodyAsString()); + return ProblemDetail.forStatusAndDetail(REMOTE_API_CLIENT_ERROR.getStatus(), + REMOTE_API_CLIENT_ERROR.getMessage() + e.getStatusCode()); + } + @ExceptionHandler(ResourceAccessException.class) public ProblemDetail handleNoConnection(ResourceAccessException e) { @@ -42,7 +55,7 @@ public class GlobalExceptionHandler { @ExceptionHandler(HttpServerErrorException.class) public ProblemDetail handleTimeoutOrNotFound(HttpServerErrorException e) { - + log.error("Remote API server error {}: {}", e.getStatusCode(), e.getResponseBodyAsString()); return ProblemDetail.forStatusAndDetail(e.getStatusCode(), SERVER_ERROR_GENERAL.getMessage() + e.getStatusText()); } @@ -61,12 +74,14 @@ public class GlobalExceptionHandler { @ExceptionHandler(ConnectionNotFoundException.class) public ProblemDetail handleConnectionNotFound(ConnectionNotFoundException e) { - return ProblemDetail.forStatusAndDetail(CONNECTION_NOT_FOUND.getStatus(), CONNECTION_NOT_FOUND.getMessage()); + return ProblemDetail.forStatusAndDetail(CONNECTION_NOT_FOUND.getStatus(), + CONNECTION_NOT_FOUND.getMessage()); } @ExceptionHandler(RemoteApiException.class) public ProblemDetail handleRemoteApiException(RemoteApiException e) { + log.error("Remote API empty body: {}{}", e.getAppUrl(), e.getPath()); return ProblemDetail.forStatusAndDetail(REMOTE_API_EMPTY_RESPONSE.getStatus(), - REMOTE_API_EMPTY_RESPONSE.getMessage() + e.getAppUrl()); + REMOTE_API_EMPTY_RESPONSE.getMessage() + e.getAppUrl() + e.getPath()); } } diff --git a/backend/src/main/java/com/vaessl/app/exception/RemoteApiException.java b/backend/src/main/java/com/vaessl/app/exception/RemoteApiException.java index e35e40c..35ac62c 100644 --- a/backend/src/main/java/com/vaessl/app/exception/RemoteApiException.java +++ b/backend/src/main/java/com/vaessl/app/exception/RemoteApiException.java @@ -1,11 +1,16 @@ package com.vaessl.app.exception; import lombok.Getter; -import lombok.RequiredArgsConstructor; @Getter -@RequiredArgsConstructor public class RemoteApiException extends RuntimeException { private final String appUrl; + private final String path; + + public RemoteApiException(String appUrl, String path) { + super("Remote API returned empty body: " + appUrl + path); + this.appUrl = appUrl; + this.path = path; + } } diff --git a/backend/src/main/java/com/vaessl/app/search/HomeboxSearchProvider.java b/backend/src/main/java/com/vaessl/app/search/HomeboxSearchProvider.java index ee490ae..e2b7f57 100644 --- a/backend/src/main/java/com/vaessl/app/search/HomeboxSearchProvider.java +++ b/backend/src/main/java/com/vaessl/app/search/HomeboxSearchProvider.java @@ -50,17 +50,17 @@ public class HomeboxSearchProvider implements SearchProvider { .queryParam("q", request.query()) .queryParam("page", pageable.getPageNumber() + 1) .queryParam("pageSize", pageable.getPageSize()).build()) - .headers(h -> h.setBearerAuth( - hbEntity.getToken())).retrieve().body(HomeboxSearchResponse.class); + .headers(h -> h.setBearerAuth(hbEntity.getToken())).retrieve() + .body(HomeboxSearchResponse.class); if (hbResponse == null) { - throw new RemoteApiException(request.appUrl()); + throw new RemoteApiException(request.appUrl(), HOMEBOX_QUERY_ALL_ITEMS.getValue()); } List items = hbResponse.items().stream().map(i -> { String title = i.name(); String description = i.description(); - Map extraSearchResponseData = Map.of("location", i.location()); + Map extraSearchResponseData = Map.of("location", i.parent()); return new SearchResponse(title, description, extraSearchResponseData); }).toList(); @@ -71,7 +71,7 @@ public class HomeboxSearchProvider implements SearchProvider { List items) { } - private record HomeboxItem(String name, String description, HomeboxLocation location) { + private record HomeboxItem(String name, String description, HomeboxLocation parent) { } private record HomeboxLocation(String name, String description) { diff --git a/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java b/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java index cfded59..eb66eee 100644 --- a/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java +++ b/backend/src/test/java/com/vaessl/app/search/SearchControllerTest.java @@ -57,7 +57,7 @@ class SearchControllerTest { "createdAt": "2026-05-13T19:52:20.016176Z", "updatedAt": "2026-05-14T12:39:11.836403Z", "purchasePrice": 0, - "location": { + "parent": { "id": "b6f60ab8-3a2a-4a8d-a4bf-897d0555f636", "name": "Server Schrank Ikea weiß", "description": "Weißer Ikea Schrank, wo sich der Server befindet.",