diff --git a/src/main/java/group/goforward/battlbuilder/configuration/SecurityConfig.java b/src/main/java/group/goforward/battlbuilder/configuration/SecurityConfig.java index 5172398..a7faf3d 100644 --- a/src/main/java/group/goforward/battlbuilder/configuration/SecurityConfig.java +++ b/src/main/java/group/goforward/battlbuilder/configuration/SecurityConfig.java @@ -37,19 +37,29 @@ public class SecurityConfig { .cors(c -> c.configurationSource(corsConfigurationSource())) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - // public + + // ---------------------------- + // Public + // ---------------------------- .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/actuator/health", "/actuator/info").permitAll() .requestMatchers("/api/products/gunbuilder/**").permitAll() - .requestMatchers(HttpMethod.GET, "/api/v1/builds").permitAll() - // protected + // Public builds feed + public build detail (1 path segment only) + .requestMatchers(HttpMethod.GET, "/api/v1/builds").permitAll() + .requestMatchers(HttpMethod.GET, "/api/v1/builds/*").permitAll() + + // ---------------------------- + // Protected + // ---------------------------- .requestMatchers("/api/v1/builds/me/**").authenticated() .requestMatchers("/api/v1/admin/**").hasRole("ADMIN") - // everything else (adjust later as you lock down) + + // Everything else (adjust later as you lock down) .anyRequest().permitAll() ) + // run JWT before AnonymousAuth sets principal="anonymousUser" .addFilterBefore(jwtAuthenticationFilter, AnonymousAuthenticationFilter.class); diff --git a/src/main/java/group/goforward/battlbuilder/controllers/BuildV1Controller.java b/src/main/java/group/goforward/battlbuilder/controllers/BuildV1Controller.java index 4d3e04b..bff7978 100644 --- a/src/main/java/group/goforward/battlbuilder/controllers/BuildV1Controller.java +++ b/src/main/java/group/goforward/battlbuilder/controllers/BuildV1Controller.java @@ -5,9 +5,9 @@ import group.goforward.battlbuilder.web.dto.BuildDto; import group.goforward.battlbuilder.web.dto.BuildFeedCardDto; import group.goforward.battlbuilder.web.dto.BuildSummaryDto; import group.goforward.battlbuilder.web.dto.UpdateBuildRequest; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.http.HttpStatus; import java.util.List; import java.util.UUID; @@ -34,6 +34,15 @@ public class BuildV1Controller { return ResponseEntity.ok(buildService.listPublicBuilds(limit == null ? 50 : limit)); } + /** + * Public build detail for /builds/{uuid} + * GET /api/v1/builds/{uuid} + */ + @GetMapping("/{uuid}") + public ResponseEntity getPublicBuild(@PathVariable("uuid") UUID uuid) { + return ResponseEntity.ok(buildService.getPublicBuild(uuid)); + } + /** * Vault builds (authenticated user). * GET /api/v1/builds/me?limit=100 @@ -75,7 +84,6 @@ public class BuildV1Controller { return ResponseEntity.ok(buildService.updateMyBuild(uuid, req)); } - /** * Delete a build (authenticated user; must own build). * DELETE /api/v1/builds/me/{uuid} diff --git a/src/main/java/group/goforward/battlbuilder/repos/BuildRepository.java b/src/main/java/group/goforward/battlbuilder/repos/BuildRepository.java index 0af62e6..daf0ec9 100644 --- a/src/main/java/group/goforward/battlbuilder/repos/BuildRepository.java +++ b/src/main/java/group/goforward/battlbuilder/repos/BuildRepository.java @@ -17,5 +17,8 @@ public interface BuildRepository extends JpaRepository { Page findByUserIdAndDeletedAtIsNullOrderByUpdatedAtDesc(Integer userId, Pageable pageable); + Optional findByUuidAndIsPublicTrueAndDeletedAtIsNull(UUID uuid); + Optional findByUuidAndDeletedAtIsNull(UUID uuid); + } \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/services/BuildService.java b/src/main/java/group/goforward/battlbuilder/services/BuildService.java index d93954a..30c14ec 100644 --- a/src/main/java/group/goforward/battlbuilder/services/BuildService.java +++ b/src/main/java/group/goforward/battlbuilder/services/BuildService.java @@ -20,5 +20,7 @@ public interface BuildService { BuildDto updateMyBuild(UUID uuid, UpdateBuildRequest req); + BuildDto getPublicBuild(UUID uuid); + void deleteMyBuild(UUID uuid); } \ No newline at end of file diff --git a/src/main/java/group/goforward/battlbuilder/services/impl/BuildServiceImpl.java b/src/main/java/group/goforward/battlbuilder/services/impl/BuildServiceImpl.java index db05dea..7c666fb 100644 --- a/src/main/java/group/goforward/battlbuilder/services/impl/BuildServiceImpl.java +++ b/src/main/java/group/goforward/battlbuilder/services/impl/BuildServiceImpl.java @@ -112,6 +112,31 @@ public class BuildServiceImpl implements BuildService { .toList(); } + // --------------------------- +// Public build detail (/builds/{uuid}) +// GET /api/v1/builds/public/{uuid} +// --------------------------- + @Override + public BuildDto getPublicBuild(UUID uuid) { + if (uuid == null) throw new IllegalArgumentException("uuid is required"); + + Build build = buildRepository.findByUuidAndDeletedAtIsNull(uuid) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Build not found")); + + // Only allow public builds here (and not deleted) + if (!Boolean.TRUE.equals(build.getIsPublic())) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Build not found"); + } + + List items = buildItemRepository.findByBuild_Id(build.getId()); + + BuildDto dto = toBuildDto(build, items); + hydrateBuildDtoItems(dto); // keep consistent with getMyBuild + + return dto; + } + + // --------------------------- // Vault list (/builds/me) // --------------------------- diff --git a/src/main/java/group/goforward/battlbuilder/services/utils/EmailService.java b/src/main/java/group/goforward/battlbuilder/services/utils/EmailService.java index 578ae2d..4c6bb8a 100644 --- a/src/main/java/group/goforward/battlbuilder/services/utils/EmailService.java +++ b/src/main/java/group/goforward/battlbuilder/services/utils/EmailService.java @@ -7,7 +7,18 @@ public interface EmailService { EmailRequest sendEmailHtml(String recipient, String subject, String htmlBody, String textBody); - EmailRequest sendEmailHtml(String recipient, String subject, String htmlBody, String textBody, String templateKey); + // ✅ convenience overload for templates + default EmailRequest sendEmailHtml( + String recipient, + String subject, + String htmlBody, + String textBody, + String templateKey + ) { + EmailRequest req = sendEmailHtml(recipient, subject, htmlBody, textBody); + req.setTemplateKey(templateKey); + return req; + } void deleteById(Integer id); } \ No newline at end of file