Skip to content

Commit f8bf413

Browse files
committed
Add docstrings for all functions, classes, and entity fields
1 parent 49d29ba commit f8bf413

38 files changed

+324
-39
lines changed

src/main/java/org/openpodcastapi/opa/advice/GlobalExceptionHandler.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,53 @@
1616
import java.time.Instant;
1717
import java.util.List;
1818

19+
/// A global handler for common exceptions thrown by the application.
20+
///
21+
/// Where possible, controllers should throw their own exceptions.
22+
/// However, for common exceptions such as invalid parameters and
23+
/// not found entities, a global exception handler can be added.
1924
@RestControllerAdvice
2025
@RequiredArgsConstructor
2126
@Log4j2
2227
public class GlobalExceptionHandler {
28+
/// Returns a 404 if a database entity is not found
29+
///
30+
/// @param exception the thrown [EntityNotFoundException]
31+
/// @return a [ResponseEntity] containing the error message
2332
@ExceptionHandler(EntityNotFoundException.class)
2433
@ResponseStatus(HttpStatus.NOT_FOUND)
25-
public ResponseEntity<@NonNull String> handleEntityNotFoundException(EntityNotFoundException error) {
26-
log.debug("{}", error.getMessage());
34+
public ResponseEntity<@NonNull String> handleEntityNotFoundException(EntityNotFoundException exception) {
35+
log.debug("{}", exception.getMessage());
2736
return ResponseEntity.notFound().build();
2837
}
2938

39+
/// Returns a 400 error when conflicting data is entered
40+
///
41+
/// @param exception the thrown [DataIntegrityViolationException]
42+
/// @return a [ResponseEntity] containing the error message
3043
@ExceptionHandler(DataIntegrityViolationException.class)
3144
@ResponseStatus(HttpStatus.BAD_REQUEST)
32-
public ResponseEntity<@NonNull String> handleDataIntegrityViolationException(DataIntegrityViolationException e) {
33-
return ResponseEntity.badRequest().body(e.getMessage());
45+
public ResponseEntity<@NonNull String> handleDataIntegrityViolationException(DataIntegrityViolationException exception) {
46+
return ResponseEntity.badRequest().body(exception.getMessage());
3447
}
3548

49+
/// Returns a 400 error when illegal arguments are passed
50+
///
51+
/// @param exception the thrown [IllegalArgumentException]
52+
/// @return a [ResponseEntity] containing the error message
3653
@ExceptionHandler(IllegalArgumentException.class)
3754
@ResponseStatus(HttpStatus.BAD_REQUEST)
38-
public ResponseEntity<@NonNull String> handleIllegalArgumentException(IllegalArgumentException e) {
39-
return ResponseEntity.badRequest().body(e.getMessage());
55+
public ResponseEntity<@NonNull String> handleIllegalArgumentException(IllegalArgumentException exception) {
56+
return ResponseEntity.badRequest().body(exception.getMessage());
4057
}
4158

59+
/// Returns a 400 error when invalid arguments are passed to an endpoint
60+
///
61+
/// @param exception the thrown [MethodArgumentNotValidException]
62+
/// @return a [ResponseEntity] containing the error message
4263
@ExceptionHandler(MethodArgumentNotValidException.class)
43-
public ResponseEntity<@NonNull ValidationErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
44-
List<ValidationErrorResponse.FieldError> errors = ex.getBindingResult().getFieldErrors().stream()
64+
public ResponseEntity<@NonNull ValidationErrorResponse> handleValidationException(MethodArgumentNotValidException exception) {
65+
List<ValidationErrorResponse.FieldError> errors = exception.getBindingResult().getFieldErrors().stream()
4566
.map(fe -> new ValidationErrorResponse.FieldError(fe.getField(), fe.getDefaultMessage()))
4667
.toList();
4768

src/main/java/org/openpodcastapi/opa/advice/GlobalModelAttributeAdvice.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,19 @@
99

1010
import java.security.Principal;
1111

12+
/// A helper class for adding user information to requests.
13+
///
14+
/// This class is used to populate user details in templates
15+
/// and to ensure that a user is authenticated when viewing
16+
/// web pages.
1217
@Log4j2
1318
@ControllerAdvice
1419
public class GlobalModelAttributeAdvice {
1520

21+
/// Adds a boolean `isAuthenticated` property to the request model based on
22+
/// whether the user is logged-in.
23+
///
24+
/// @param model the [Model] attached to the request
1625
@ModelAttribute
1726
public void addAuthenticationFlag(Model model) {
1827
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
@@ -21,6 +30,10 @@ public void addAuthenticationFlag(Model model) {
2130
model.addAttribute("isAuthenticated", isAuthenticated);
2231
}
2332

33+
/// Adds user details to the request model.
34+
///
35+
/// @param principal the [Principal] representing the user
36+
/// @param model the [Model] attached to the request
2437
@ModelAttribute
2538
public void addUserDetails(Principal principal, Model model) {
2639
var username = principal != null ? principal.getName() : "Guest";

src/main/java/org/openpodcastapi/opa/auth/ApiBearerTokenAuthenticationConverter.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
import org.springframework.security.web.authentication.AuthenticationConverter;
77
import org.springframework.stereotype.Component;
88

9+
/// A converter that handles JWT-based auth for API requests.
10+
///
11+
/// This converter targets only the API endpoints at `/api`.
12+
/// Auth for the frontend is handled by Spring's form login.
913
@Component
1014
public class ApiBearerTokenAuthenticationConverter implements AuthenticationConverter {
1115

@@ -15,7 +19,7 @@ public class ApiBearerTokenAuthenticationConverter implements AuthenticationConv
1519
@Override
1620
public Authentication convert(HttpServletRequest request) {
1721

18-
String path = request.getRequestURI();
22+
final var path = request.getRequestURI();
1923

2024
// Don't authenticate the auth endpoints
2125
if (path.startsWith("/api/auth/")) {

src/main/java/org/openpodcastapi/opa/auth/ApiSecurityHandlers.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import org.springframework.security.web.access.AccessDeniedHandler;
77
import org.springframework.stereotype.Component;
88

9+
/// Contains handlers for token-related errors on API endpoints
910
@Component
1011
public class ApiSecurityHandlers {
1112
/// Returns an unauthorized response for unauthenticate API queries
13+
///
14+
/// @return an unauthorized response with a JSON-formatted error message
1215
@Bean
1316
public AuthenticationEntryPoint apiAuthenticationEntryPoint() {
1417
return (_, response, authException) -> {
@@ -21,6 +24,8 @@ public AuthenticationEntryPoint apiAuthenticationEntryPoint() {
2124
}
2225

2326
/// Returns a forbidden response for API queries
27+
///
28+
/// @return a forbidden response with a JSON-formatted error message
2429
@Bean
2530
public AccessDeniedHandler apiAccessDeniedHandler() {
2631
return (_, response, exception) -> {

src/main/java/org/openpodcastapi/opa/auth/JwtAuthenticationProvider.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,18 @@
1818
import java.nio.charset.StandardCharsets;
1919
import java.util.UUID;
2020

21+
/// Handles provisioning and authenticating JWTs for API requests
2122
@Component
2223
public class JwtAuthenticationProvider implements AuthenticationProvider {
2324

2425
private final UserRepository repository;
2526
private final SecretKey key;
2627

28+
/// Constructor with secret value provided in `.env` file
29+
/// or environment variables.
30+
///
31+
/// @param repository the [UserRepository] interface for user entities
32+
/// @param secret the secret value used to generate JWT values
2733
public JwtAuthenticationProvider(
2834
UserRepository repository,
2935
@Value("${jwt.secret}") String secret) {
@@ -36,25 +42,31 @@ public JwtAuthenticationProvider(
3642
public Authentication authenticate(Authentication authentication)
3743
throws AuthenticationException {
3844

45+
// Get the JWT token from the authentication header
3946
final var token = (String) authentication.getCredentials();
4047

4148
try {
49+
// Parse the JWT claims
4250
final var claims = Jwts.parser()
4351
.verifyWith(key)
4452
.build()
4553
.parseSignedClaims(token)
4654
.getPayload();
4755

56+
// Get the user's UUID from the claims subject
4857
final var uuid = UUID.fromString(claims.getSubject());
4958

50-
final var user = repository.getUserByUuid(uuid)
59+
// Find the user entity
60+
final var user = repository.findUserByUuid(uuid)
5161
.orElseThrow(() -> new BadCredentialsException("User not found"));
5262

63+
// Configure the user details for the authenticated user
5364
final var details = new CustomUserDetails(
5465
user.getId(), user.getUuid(), user.getUsername(),
5566
user.getPassword(), user.getUserRoles()
5667
);
5768

69+
// Return the parsed token
5870
return new UsernamePasswordAuthenticationToken(
5971
details, token, details.getAuthorities());
6072
} catch (Exception ex) {

src/main/java/org/openpodcastapi/opa/config/SecurityConfig.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.security.web.access.AccessDeniedHandler;
2323
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
2424

25+
/// Security configuration for the Spring application
2526
@Configuration
2627
@EnableWebSecurity
2728
@RequiredArgsConstructor
@@ -41,6 +42,14 @@ public class SecurityConfig {
4142
"/favicon.ico",
4243
};
4344

45+
/// API-related security configuration
46+
///
47+
/// @param http the [HttpSecurity] object to be configured
48+
/// @param jwtAuthenticationProvider the [JwtAuthenticationProvider] used to handle JWT auth
49+
/// @param entryPoint the entrypoint that commences the JWT auth
50+
/// @param deniedHandler the [AccessDeniedHandler] that handles auth failures
51+
/// @param converter the [ApiBearerTokenAuthenticationConverter] that manages JWT validation
52+
/// @return the configured [HttpSecurity] object
4453
@Bean
4554
@Order(1)
4655
public SecurityFilterChain apiSecurity(
@@ -79,6 +88,10 @@ public SecurityFilterChain apiSecurity(
7988
return http.build();
8089
}
8190

91+
/// Web-related security configuration
92+
///
93+
/// @param http the [HttpSecurity] object to be configured
94+
/// @return the configured [HttpSecurity] object
8295
@Bean
8396
@Order(2)
8497
public SecurityFilterChain webSecurity(HttpSecurity http) {
@@ -104,11 +117,19 @@ public SecurityFilterChain webSecurity(HttpSecurity http) {
104117
.build();
105118
}
106119

120+
/// The default password encoder used for hashing and encoding user passwords and JWTs
121+
///
122+
/// @return a configured [BCryptPasswordEncoder]
107123
@Bean
108124
public BCryptPasswordEncoder passwordEncoder() {
109125
return new BCryptPasswordEncoder();
110126
}
111127

128+
/// An authentication provider for password-based authentication
129+
///
130+
/// @param userDetailsService the [UserDetailsService] for loading user data
131+
/// @param passwordEncoder the default password encoder
132+
/// @return the configured [DaoAuthenticationProvider]
112133
@Bean
113134
public DaoAuthenticationProvider daoAuthenticationProvider(UserDetailsService userDetailsService,
114135
BCryptPasswordEncoder passwordEncoder) {
@@ -117,11 +138,20 @@ public DaoAuthenticationProvider daoAuthenticationProvider(UserDetailsService us
117138
return provider;
118139
}
119140

141+
/// An authentication provider for JWT-based authentication
142+
///
143+
/// @param provider a configured [JwtAuthenticationProvider]
144+
/// @return a configured [ProviderManager] that uses the JWT auth provider
145+
/// @see JwtAuthenticationProvider for provider details
120146
@Bean(name = "jwtAuthManager")
121147
public AuthenticationManager jwtAuthenticationManager(JwtAuthenticationProvider provider) {
122148
return new ProviderManager(provider);
123149
}
124150

151+
/// An authentication provider for API POST login
152+
///
153+
/// @param daoProvider a configured [DaoAuthenticationProvider]
154+
/// @return a configured [ProviderManager] that uses basic username/password auth
125155
@Bean(name = "apiLoginManager", defaultCandidate = false)
126156
public AuthenticationManager apiLoginAuthenticationManager(
127157
DaoAuthenticationProvider daoProvider) {

src/main/java/org/openpodcastapi/opa/config/WebConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,32 @@
66
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
77
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
88

9+
/// Configuration for the web interface
910
@Configuration
1011
public class WebConfig implements WebMvcConfigurer {
1112
@Override
1213
public void addResourceHandlers(ResourceHandlerRegistry registry) {
14+
// Additional CSS storage
1315
registry
1416
.addResourceHandler("/css/**")
1517
.addResourceLocations("classpath:/static/css/");
1618

19+
// Additional JavaScript storage
1720
registry
1821
.addResourceHandler("/js/**")
1922
.addResourceLocations("classpath:/static/js/");
2023

24+
// The hosted documentation
2125
registry
2226
.addResourceHandler("/docs/**")
2327
.addResourceLocations("classpath:/static/docs/");
2428
}
2529

30+
/// Informs Spring to use Thymeleaf Layout Dialect for composing Thymeleaf templates
31+
///
32+
/// See [Thymeleaf Layout Dialect](https://ultraq.github.io/thymeleaf-layout-dialect/) for more information
33+
///
34+
/// @return the configured [LayoutDialect]
2635
@Bean
2736
public LayoutDialect layoutDialect() {
2837
return new LayoutDialect();

src/main/java/org/openpodcastapi/opa/controllers/api/AuthController.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@
1818
import org.springframework.web.bind.annotation.RequestBody;
1919
import org.springframework.web.bind.annotation.RestController;
2020

21+
/// Controllers for API-based authentication
2122
@RestController
2223
@Log4j2
2324
public class AuthController {
2425
private final TokenService tokenService;
2526
private final UserRepository userRepository;
2627
private final AuthenticationManager authenticationManager;
2728

29+
/// Constructs the controller with the correct [AuthenticationManager]
30+
///
31+
/// @param tokenService the [TokenService] used to manage auth tokens
32+
/// @param userRepository the [UserRepository] used to manage user entity interaction
33+
/// @param authenticationManager the [AuthenticationManager] used to handle auth
34+
/// @see org.openpodcastapi.opa.config.SecurityConfig#apiLoginAuthenticationManager
2835
public AuthController(
2936
TokenService tokenService,
3037
UserRepository userRepository,
@@ -35,7 +42,10 @@ public AuthController(
3542
this.authenticationManager = authenticationManager;
3643
}
3744

38-
// === Login endpoint ===
45+
/// The API login endpoint. Accepts a basic username/password combination to authenticate.
46+
///
47+
/// @param loginRequest the [AuthDTO.LoginRequest] containing the user's credentials
48+
/// @return a [ResponseEntity] containing a [AuthDTO.LoginSuccessResponse]
3949
@PostMapping("/api/auth/login")
4050
public ResponseEntity<AuthDTO.@NonNull LoginSuccessResponse> login(@RequestBody @NotNull AuthDTO.LoginRequest loginRequest) {
4151
// Set the authentication using the provided details
@@ -47,7 +57,7 @@ public AuthController(
4757
SecurityContextHolder.getContext().setAuthentication(authentication);
4858

4959
// Fetch the user record from the database
50-
final var userEntity = userRepository.findByUsername(loginRequest.username()).orElseThrow(() -> new EntityNotFoundException("No userEntity with username " + loginRequest.username() + " found"));
60+
final var userEntity = userRepository.findUserByUsername(loginRequest.username()).orElseThrow(() -> new EntityNotFoundException("No userEntity with username " + loginRequest.username() + " found"));
5161

5262
// Generate the access and refresh tokens for the user
5363
final String accessToken = tokenService.generateAccessToken(userEntity);
@@ -59,10 +69,13 @@ public AuthController(
5969
return ResponseEntity.ok(response);
6070
}
6171

62-
// === Refresh token endpoint ===
72+
/// The token refresh endpoint. Validates refresh tokens and returns new access tokens.
73+
///
74+
/// @param refreshTokenRequest the [AuthDTO.RefreshTokenRequest] request body
75+
/// @return a [ResponseEntity] containing a [AuthDTO.RefreshTokenResponse]
6376
@PostMapping("/api/auth/refresh")
6477
public ResponseEntity<AuthDTO.@NonNull RefreshTokenResponse> getRefreshToken(@RequestBody @NotNull AuthDTO.RefreshTokenRequest refreshTokenRequest) {
65-
final var targetUserEntity = userRepository.findByUsername(refreshTokenRequest.username()).orElseThrow(() -> new EntityNotFoundException("No user with username " + refreshTokenRequest.username() + " found"));
78+
final var targetUserEntity = userRepository.findUserByUsername(refreshTokenRequest.username()).orElseThrow(() -> new EntityNotFoundException("No user with username " + refreshTokenRequest.username() + " found"));
6679

6780
// Validate the existing refresh token
6881
final UserEntity userEntity = tokenService.validateRefreshToken(refreshTokenRequest.refreshToken(), targetUserEntity);

src/main/java/org/openpodcastapi/opa/controllers/web/DocsController.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,23 @@
44
import org.springframework.stereotype.Controller;
55
import org.springframework.web.bind.annotation.GetMapping;
66

7+
/// Controller for the hosted documentation endpoints
78
@Controller
89
@Log4j2
910
public class DocsController {
1011

11-
// === Docs index page ===
12+
/// The hosted documentation endpoint. Redirects users to the index page.
13+
///
14+
/// @return a redirect to the documentation
1215
@GetMapping("/docs")
1316
public String docs() {
1417
return "forward:/docs/index.html";
1518
}
1619

17-
// === Docs page with trailing slash ===
20+
/// The hosted documentation endpoint (with a trailing slash).
21+
/// Redirects users to the index page.
22+
///
23+
/// @return a redirect to the documentation
1824
@GetMapping("/docs/")
1925
public String docsWithSlash() {
2026
return "forward:/docs/index.html";

0 commit comments

Comments
 (0)