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
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ All notable changes to the DevStack API Service project will be documented in th
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.2] - 2026-02-20

### Added

#### External Service API Module (`external-service-api`)
- **New module** for standardizing external service integrations
- `ExternalService` interface providing common contract for all external services
- `isHealthy()` method for health check implementation across all external services
- `AbstractExternalServiceHealthIndicator` base class for standardized health monitoring

#### Health Monitoring & Observability
- **Spring Boot Actuator** health indicators for all external services:

### Changed
- Updated all external service implementations to extend `ExternalService` interface
- Use of lombok.extern.slf4j.Slf4j to remove boilerplate code.

### Dependencies
- Updated Spring Boot version
- Added health indicator dependencies across external service modules

---

## [0.0.1-SNAPSHOT] - 2025-11-27

### Added
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Project variables
PROJECT_NAME := devstack-api-service
VERSION := 0.0.1-SNAPSHOT
VERSION := 0.0.2-SNAPSHOT
JAVA_VERSION := 21
MAIN_CLASS := org.opendevstack.apiservice.core.DevstackApiServiceApplication

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Build a traditional Spring Boot JAR file:
```bash
make jar
```
- Output: `core/target/core-0.0.1-SNAPSHOT.jar`
- Output: `core/target/core-0.0.2-SNAPSHOT.jar`
- Includes all dependencies
- Standard Spring Boot startup time

Expand Down
3 changes: 1 addition & 2 deletions api-project-platform/openapi/api-project-platform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ info:
title: ODS API Server
description: API documentation for ODS (Open DevStack) API Service
contact:
name: EDPCore Team
url: https://confluence.biscrum.com/pages/viewpage.action?spaceKey=EDP&title=Welcome
name: ODS Team
version: v0.0.1
servers:
- url: http://{baseurl}/api/v1
Expand Down
4 changes: 2 additions & 2 deletions api-project-platform/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.opendevstack.apiservice</groupId>
<artifactId>devstack-api-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>

<artifactId>api-project-platform</artifactId>
Expand Down Expand Up @@ -139,4 +139,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
4 changes: 2 additions & 2 deletions api-project-users/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>org.opendevstack.apiservice</groupId>
<artifactId>devstack-api-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<version>0.0.2-SNAPSHOT</version>
</parent>

<artifactId>api-project-users</artifactId>
Expand Down Expand Up @@ -131,4 +131,4 @@
</plugins>
</build>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,21 @@
import org.opendevstack.apiservice.projectusers.exception.InvalidTokenException;
import org.opendevstack.apiservice.projectusers.service.MembershipRequestStatusService;
import org.opendevstack.apiservice.projectusers.service.ProjectUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import lombok.extern.slf4j.Slf4j;

/**
* REST Controller for managing project users and their roles.
* Provides endpoints for adding, removing, updating, and querying users in projects.
*/
@Slf4j
@RestController
@RequestMapping("/api/v1")
public class ProjectUserController implements ProjectUsersApi{

private static final Logger logger = LoggerFactory.getLogger(ProjectUserController.class);

private final ProjectUserService projectUserService;
private final MembershipRequestStatusService statusService;

Expand All @@ -46,7 +45,7 @@ public ResponseEntity<ApiResponseMembershipRequestResponse> triggerMembershipReq
String projectKey,
AddUserToProjectRequest addUserToProjectRequest) {

logger.info("Triggering membership request for account '{}' of user '{}' to project '{}' with role '{}'",
log.info("Triggering membership request for account '{}' of user '{}' to project '{}' with role '{}'",
addUserToProjectRequest.getAccount(), addUserToProjectRequest.getUser(), projectKey, addUserToProjectRequest.getRole());

MembershipRequestResponse response = projectUserService.addUserToProject(projectKey, addUserToProjectRequest);
Expand All @@ -66,7 +65,7 @@ public ResponseEntity<ApiResponseMembershipRequestStatusResponse> getRequestStat
String user,
String requestId) {

logger.info("Getting status for request '{}' - project '{}', user '{}'", requestId, projectKey, user);
log.info("Getting status for request '{}' - project '{}', user '{}'", requestId, projectKey, user);

// Check the request is valid for the given project and user
if (!statusService.validateRequestToken(requestId, projectKey, user)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.extern.slf4j.Slf4j;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
Expand All @@ -31,19 +31,18 @@
* Provides comprehensive error handling with detailed validation error
* messages.
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

/**
* Handles validation errors from @Valid annotations on request bodies.
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleMethodArgumentNotValidException(
MethodArgumentNotValidException ex) {

logger.warn("Validation failed for request: {}", ex.getMessage());
log.warn("Validation failed for request: {}", ex.getMessage());

List<FieldError> fieldErrors = new ArrayList<>();

Expand Down Expand Up @@ -90,7 +89,7 @@ public ResponseEntity<ValidationErrorResponse> handleMethodArgumentNotValidExcep
public ResponseEntity<BaseApiResponse> handleConstraintViolationException(
ConstraintViolationException ex) {

logger.warn("Constraint violation: {}", ex.getMessage());
log.warn("Constraint violation: {}", ex.getMessage());

List<String> errors = ex.getConstraintViolations()
.stream()
Expand All @@ -114,7 +113,7 @@ public ResponseEntity<BaseApiResponse> handleConstraintViolationException(
public ResponseEntity<BaseApiResponse> handleHttpMessageNotReadableException(
HttpMessageNotReadableException ex) {

logger.warn("Invalid request body: {}", ex.getMessage());
log.warn("Invalid request body: {}", ex.getMessage());

String errorMessage = ErrorMessages.INVALID_REQUEST_BODY;
String errorCode = ErrorCodes.PROJECT_USER_ERROR;
Expand Down Expand Up @@ -171,7 +170,7 @@ public ResponseEntity<BaseApiResponse> handleHttpMessageNotReadableException(
public ResponseEntity<BaseApiResponse> handleMissingPathVariableException(
MissingPathVariableException ex) {

logger.warn("Missing path variable: {}", ex.getMessage());
log.warn("Missing path variable: {}", ex.getMessage());

String errorMessage = String.format(
ErrorMessages.REQUIRED_PATH_PARAMETER_MISSING,
Expand All @@ -191,7 +190,7 @@ public ResponseEntity<BaseApiResponse> handleMissingPathVariableException(
public ResponseEntity<BaseApiResponse> handleMissingServletRequestParameterException(
MissingServletRequestParameterException ex) {

logger.warn("Missing request parameter: {}", ex.getMessage());
log.warn("Missing request parameter: {}", ex.getMessage());

String errorMessage = String.format(
ErrorMessages.REQUIRED_REQUEST_PARAMETER_MISSING,
Expand All @@ -211,7 +210,7 @@ public ResponseEntity<BaseApiResponse> handleMissingServletRequestParameterExcep
public ResponseEntity<BaseApiResponse> handleMethodArgumentTypeMismatchException(
MethodArgumentTypeMismatchException ex) {

logger.warn("Method argument type mismatch: {}", ex.getMessage());
log.warn("Method argument type mismatch: {}", ex.getMessage());

String errorMessage = String.format(
ErrorMessages.PARAMETER_TYPE_CONVERSION_FAILED,
Expand All @@ -233,7 +232,7 @@ public ResponseEntity<BaseApiResponse> handleMethodArgumentTypeMismatchException
public ResponseEntity<BaseApiResponse> handleProjectNotFoundException(
ProjectNotFoundException ex) {

logger.warn("Project not found: {}", ex.getMessage());
log.warn("Project not found: {}", ex.getMessage());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
Expand All @@ -249,7 +248,7 @@ public ResponseEntity<BaseApiResponse> handleProjectNotFoundException(
public ResponseEntity<BaseApiResponse> handleUserNotFoundException(
UserNotFoundException ex) {

logger.warn("User not found: {}", ex.getMessage());
log.warn("User not found: {}", ex.getMessage());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
Expand All @@ -265,7 +264,7 @@ public ResponseEntity<BaseApiResponse> handleUserNotFoundException(
public ResponseEntity<BaseApiResponse> handleUserNotAuthenticatedException(
UserNotAuthenticatedException ex) {

logger.warn("User not authenticated: {}", ex.getMessage());
log.warn("User not authenticated: {}", ex.getMessage());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
Expand All @@ -281,7 +280,7 @@ public ResponseEntity<BaseApiResponse> handleUserNotAuthenticatedException(
public ResponseEntity<BaseApiResponse> handleUserNotAuthorizedException(
UserNotAuthorizedException ex) {

logger.warn("User not authorized: {}", ex.getMessage());
log.warn("User not authorized: {}", ex.getMessage());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
Expand All @@ -297,7 +296,7 @@ public ResponseEntity<BaseApiResponse> handleUserNotAuthorizedException(
public ResponseEntity<BaseApiResponse> handleInvalidRoleException(
InvalidRoleException ex) {

logger.warn("Invalid role: {}", ex.getMessage());
log.warn("Invalid role: {}", ex.getMessage());
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ex.getMessage());
Expand All @@ -313,7 +312,7 @@ public ResponseEntity<BaseApiResponse> handleInvalidRoleException(
public ResponseEntity<BaseApiResponse> handleAutomationPlatformException(
AutomationPlatformException ex) {

logger.error("Automation platform error: {}", ex.getMessage(), ex);
log.error("Automation platform error: {}", ex.getMessage(), ex);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(String.format(ErrorMessages.EXTERNAL_SERVICE_ERROR, ex.getMessage()));
Expand All @@ -327,7 +326,7 @@ public ResponseEntity<BaseApiResponse> handleAutomationPlatformException(
*/
@ExceptionHandler(ProjectUserException.class)
public ResponseEntity<BaseApiResponse> handleProjectUserException(ProjectUserException ex) {
logger.error("Project user operation failed: {}", ex.getMessage(), ex);
log.error("Project user operation failed: {}", ex.getMessage(), ex);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(String.format(ErrorMessages.OPERATION_FAILED, ex.getMessage()));
Expand All @@ -341,7 +340,7 @@ public ResponseEntity<BaseApiResponse> handleProjectUserException(ProjectUserExc
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<BaseApiResponse> handleGenericException(Exception ex) {
logger.error("Unexpected error occurred: {}", ex.getMessage(), ex);
log.error("Unexpected error occurred: {}", ex.getMessage(), ex);
BaseApiResponse errorResponse = new BaseApiResponse();
errorResponse.setSuccess(false);
errorResponse.setMessage(ErrorMessages.UNEXPECTED_ERROR);
Expand Down
Loading