Skip to content

Commit 3f5ca56

Browse files
authored
implement API interfaces with swagger documentation (#18)
implement API interfaces with swagger documentation
1 parent 207d70b commit 3f5ca56

6 files changed

Lines changed: 186 additions & 32 deletions

File tree

src/main/java/org/fmazmz/casemanager/exception/GlobalExceptionHandler.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,23 @@
99
@ControllerAdvice
1010
public class GlobalExceptionHandler {
1111

12+
@ExceptionHandler(Exception.class)
13+
public ResponseEntity<ApiErrorResponse> handleGenericException(Exception ex) {
14+
return ResponseEntity
15+
.status(HttpStatus.INTERNAL_SERVER_ERROR)
16+
.body(new ApiErrorResponse(
17+
HttpStatus.INTERNAL_SERVER_ERROR.name(),
18+
ex.getMessage(),
19+
HttpStatus.INTERNAL_SERVER_ERROR.value()));
20+
}
21+
1222
@ExceptionHandler(AccessDeniedException.class)
1323
public ResponseEntity<ApiErrorResponse> handleAccessDenied(AccessDeniedException ex) {
1424
return ResponseEntity
1525
.status(HttpStatus.FORBIDDEN)
16-
.body(new ApiErrorResponse(HttpStatus.FORBIDDEN.name(), ex.getMessage(), HttpStatus.FORBIDDEN.value())); }
26+
.body(new ApiErrorResponse(
27+
HttpStatus.FORBIDDEN.name(),
28+
ex.getMessage(),
29+
HttpStatus.FORBIDDEN.value()));
30+
}
1731
}
Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,53 @@
11
package org.fmazmz.casemanager.ticket.api;
22

3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.media.Content;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
7+
import io.swagger.v3.oas.annotations.tags.Tag;
38
import jakarta.validation.Valid;
49
import org.fmazmz.casemanager.ticket.dto.CreateTicketRequest;
510
import org.fmazmz.casemanager.ticket.dto.TicketResponse;
6-
import org.fmazmz.casemanager.ticket.orchestration.TicketOrchestrator;
7-
import org.fmazmz.casemanager.utils.ApiResponse;
8-
import org.springframework.http.HttpStatus;
11+
import org.fmazmz.casemanager.utils.ApiErrorResponse;
12+
import org.fmazmz.casemanager.utils.ApiResponseWrapper;
13+
import org.springframework.http.MediaType;
914
import org.springframework.http.ResponseEntity;
1015
import org.springframework.web.bind.annotation.PostMapping;
1116
import org.springframework.web.bind.annotation.RequestBody;
1217
import org.springframework.web.bind.annotation.RequestMapping;
13-
import org.springframework.web.bind.annotation.RestController;
1418

15-
@RestController
16-
@RequestMapping("api/v1/tickets")
17-
public class TicketApi {
18-
private final TicketOrchestrator ticketOrchestrator;
19-
20-
public TicketApi(TicketOrchestrator ticketOrchestrator) {
21-
this.ticketOrchestrator = ticketOrchestrator;
22-
}
19+
@Tag(name = "Ticket API", description = "Perform CRUD operations on Tickets")
20+
@RequestMapping(
21+
path = "api/v1/tickets",
22+
consumes = MediaType.APPLICATION_JSON_VALUE,
23+
produces = MediaType.APPLICATION_JSON_VALUE
24+
)
25+
public interface TicketApi {
2326

27+
@Operation(summary = "Create a new Ticket")
28+
@ApiResponse(
29+
responseCode = "201",
30+
description = "Created",
31+
useReturnTypeSchema = true
32+
)
33+
@ApiResponse(responseCode = "400", description = "Bad Request",
34+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))
35+
)
36+
@ApiResponse(responseCode = "401", description = "Unauthorized",
37+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))
38+
)
39+
@ApiResponse(responseCode = "403", description = "Forbidden",
40+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class)))
41+
@ApiResponse(responseCode = "500", description = "Internal Server Error",
42+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class)))
2443
@PostMapping
25-
public ResponseEntity<ApiResponse<TicketResponse>> createTicket(
26-
@RequestBody @Valid CreateTicketRequest request) {
27-
28-
TicketResponse response = ticketOrchestrator.createTicket(request, request.requesterId());
29-
30-
return ResponseEntity
31-
.status(HttpStatus.CREATED)
32-
.body(new ApiResponse<>(response));
33-
}
44+
ResponseEntity<ApiResponseWrapper<TicketResponse>> createTicket(
45+
@Valid
46+
@RequestBody
47+
@io.swagger.v3.oas.annotations.parameters.RequestBody(
48+
required = true,
49+
description = "Payload to create a new Ticket"
50+
)
51+
CreateTicketRequest request
52+
);
3453
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.fmazmz.casemanager.ticket.api;
2+
3+
import jakarta.validation.Valid;
4+
import org.fmazmz.casemanager.ticket.dto.CreateTicketRequest;
5+
import org.fmazmz.casemanager.ticket.dto.TicketResponse;
6+
import org.fmazmz.casemanager.ticket.orchestration.TicketOrchestrator;
7+
import org.fmazmz.casemanager.utils.ApiResponseWrapper;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.PostMapping;
11+
import org.springframework.web.bind.annotation.RequestBody;
12+
import org.springframework.web.bind.annotation.RestController;
13+
14+
@RestController
15+
public class TicketController implements TicketApi{
16+
private final TicketOrchestrator ticketOrchestrator;
17+
18+
public TicketController(TicketOrchestrator ticketOrchestrator) {
19+
this.ticketOrchestrator = ticketOrchestrator;
20+
}
21+
22+
@PostMapping
23+
@Override
24+
public ResponseEntity<ApiResponseWrapper<TicketResponse>> createTicket(
25+
@RequestBody @Valid CreateTicketRequest request) {
26+
27+
TicketResponse response = ticketOrchestrator.createTicket(request, request.requesterId());
28+
29+
return ResponseEntity
30+
.status(HttpStatus.CREATED)
31+
.body(new ApiResponseWrapper<>(response));
32+
}
33+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.fmazmz.casemanager.user.auth;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.media.Content;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import io.swagger.v3.oas.annotations.media.SchemaProperty;
7+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import jakarta.validation.Valid;
10+
import org.fmazmz.casemanager.user.dto.OAuthInfoResponse;
11+
import org.fmazmz.casemanager.user.dto.SignupRequest;
12+
import org.fmazmz.casemanager.user.dto.UserResponse;
13+
import org.fmazmz.casemanager.utils.ApiErrorResponse;
14+
import org.fmazmz.casemanager.utils.ApiResponseWrapper;
15+
import org.springframework.http.MediaType;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
18+
import org.springframework.web.bind.annotation.GetMapping;
19+
import org.springframework.web.bind.annotation.PostMapping;
20+
import org.springframework.web.bind.annotation.RequestBody;
21+
import org.springframework.web.bind.annotation.RequestMapping;
22+
23+
@Tag(name = "Oauth2 API", description = "Authentication and user onboarding via OAuth2")
24+
@RequestMapping(
25+
path = "api/v1/auth",
26+
consumes = MediaType.APPLICATION_JSON_VALUE,
27+
produces = MediaType.APPLICATION_JSON_VALUE
28+
)
29+
public interface Oauth2Api {
30+
31+
@Operation(summary = "Get authenticated OAuth2 user info")
32+
@ApiResponse(
33+
responseCode = "200",
34+
description = "OAuth2 user information",
35+
content = @Content(
36+
schemaProperties = {
37+
@SchemaProperty(
38+
name = "data",
39+
schema = @Schema(oneOf = {OAuthInfoResponse.class, UserResponse.class})
40+
),
41+
@SchemaProperty(
42+
name = "requestId",
43+
schema = @Schema(type = "string", format = "uuid")
44+
),
45+
@SchemaProperty(
46+
name = "timestamp",
47+
schema = @Schema(type = "integer", format = "int64")
48+
)
49+
}
50+
)
51+
)
52+
@ApiResponse(responseCode = "401", description = "Unauthorized",
53+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))
54+
)
55+
@ApiResponse(responseCode = "500", description = "Internal Server Error",
56+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))
57+
)
58+
@GetMapping("/me")
59+
ResponseEntity<ApiResponseWrapper<?>> me(OAuth2AuthenticationToken authentication);
60+
61+
@Operation(summary = "Complete signup for authenticated OAuth2 user")
62+
@ApiResponse(
63+
responseCode = "201",
64+
description = "Created",
65+
useReturnTypeSchema = true
66+
)
67+
@ApiResponse(responseCode = "400", description = "Bad Request",
68+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))
69+
)
70+
@ApiResponse(responseCode = "401", description = "Unauthorized",
71+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))
72+
)
73+
@ApiResponse(responseCode = "500", description = "Internal Server Error",
74+
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))
75+
)
76+
@PostMapping("/signup")
77+
ResponseEntity<ApiResponseWrapper<UserResponse>> signup(
78+
OAuth2AuthenticationToken authentication,
79+
@Valid
80+
@RequestBody
81+
@io.swagger.v3.oas.annotations.parameters.RequestBody(
82+
required = true,
83+
description = "Payload to complete user signup"
84+
)
85+
SignupRequest request
86+
);
87+
}

src/main/java/org/fmazmz/casemanager/user/auth/Oauth2Controller.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import org.fmazmz.casemanager.user.dto.UserResponse;
88
import org.fmazmz.casemanager.user.model.AuthProvider;
99
import org.fmazmz.casemanager.user.model.User;
10-
import org.fmazmz.casemanager.utils.ApiResponse;
10+
import org.fmazmz.casemanager.utils.ApiResponseWrapper;
1111
import org.springframework.http.HttpStatus;
1212
import org.springframework.http.ResponseEntity;
1313
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
@@ -17,8 +17,7 @@
1717

1818
@Slf4j
1919
@RestController
20-
@RequestMapping("api/v1/auth")
21-
public class Oauth2Controller {
20+
public class Oauth2Controller implements Oauth2Api {
2221

2322
private final UserAuth userAuth;
2423

@@ -27,7 +26,8 @@ public Oauth2Controller(UserAuth userAuth) {
2726
}
2827

2928
@GetMapping("/me")
30-
public ResponseEntity<ApiResponse<?>> me(OAuth2AuthenticationToken authentication) {
29+
@Override
30+
public ResponseEntity<ApiResponseWrapper<?>> me(OAuth2AuthenticationToken authentication) {
3131
var principal = authentication.getPrincipal();
3232
AuthProvider provider = AuthProvider.fromRegistrationId(
3333
authentication.getAuthorizedClientRegistrationId());
@@ -45,21 +45,22 @@ public ResponseEntity<ApiResponse<?>> me(OAuth2AuthenticationToken authenticatio
4545
principal.getAttribute(provider.getNameAttribute()),
4646
principal.getAttribute(provider.getAvatarAttribute())
4747
);
48-
return ResponseEntity.ok(new ApiResponse<>(response));
48+
return ResponseEntity.ok(new ApiResponseWrapper<>(response));
4949
}
5050

51-
return ResponseEntity.ok(new ApiResponse<>(UserResponse.from(user.get())));
51+
return ResponseEntity.ok(new ApiResponseWrapper<>(UserResponse.from(user.get())));
5252
}
5353

5454
@PostMapping("/signup")
55-
public ResponseEntity<ApiResponse<UserResponse>> signup(
55+
@Override
56+
public ResponseEntity<ApiResponseWrapper<UserResponse>> signup(
5657
OAuth2AuthenticationToken authentication,
5758
@RequestBody @Valid SignupRequest request) {
5859

5960
User user = userAuth.signup(authentication, request);
6061

6162
return ResponseEntity
6263
.status(HttpStatus.CREATED)
63-
.body(new ApiResponse<>(UserResponse.from(user)));
64+
.body(new ApiResponseWrapper<>(UserResponse.from(user)));
6465
}
6566
}

src/main/java/org/fmazmz/casemanager/utils/ApiResponse.java renamed to src/main/java/org/fmazmz/casemanager/utils/ApiResponseWrapper.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
import java.time.Instant;
44
import java.util.UUID;
55

6-
public record ApiResponse<T>(
6+
public record ApiResponseWrapper<T>(
77
T data,
88
UUID requestId,
99
Long timestamp
1010
)
1111
{
12-
public ApiResponse(T data) {
12+
public ApiResponseWrapper(T data) {
1313
this(data, UUID.randomUUID(), Instant.now().toEpochMilli());
1414
}
1515
}

0 commit comments

Comments
 (0)