From 20c2da10ab6c4f9f11d5d9d07646952cdd7cce67 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Fri, 3 Apr 2026 15:38:24 +0800 Subject: [PATCH] feat: enhance Firearm entity and add query services with controllers --- build.gradle.kts | 1 + .../deltaforceguide/config/CorsConfig.java | 10 +++-- .../controller/FirearmQueryController.java | 43 +++++++++++++++++++ .../ModificationQueryController.java | 43 +++++++++++++++++++ .../converter/FirearmTypeConverter.java | 21 +++++++++ .../domain/dto/FirearmResponse.java | 23 ++++++++++ .../domain/dto/ModificationResponse.java | 30 +++++++++++++ .../domain/dto/PageResponse.java | 24 +++++++++++ .../domain/entity/Firearm.java | 16 ++++--- .../enumeration/FirearmType.java | 37 ++++++++++++++++ .../repository/FirearmRepository.java | 8 ++++ .../repository/ModificationRepository.java | 21 +++++++++ .../service/FirearmQueryService.java | 36 ++++++++++++++++ .../service/ModificationQueryService.java | 36 ++++++++++++++++ .../db/migration/V3__alter_firearm_level.sql | 2 + .../DeltaForceGuideApplicationTests.java | 13 ------ 16 files changed, 342 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/FirearmQueryController.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/ModificationQueryController.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/FirearmQueryService.java create mode 100644 src/main/java/com/onixbyte/deltaforceguide/service/ModificationQueryService.java create mode 100644 src/main/resources/db/migration/V3__alter_firearm_level.sql delete mode 100644 src/test/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplicationTests.java diff --git a/build.gradle.kts b/build.gradle.kts index d7b6f12..64a0cbc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,6 +49,7 @@ dependencies { implementation(libs.flyway.core) implementation(libs.flyway.mysql) implementation(libs.jackson.jsr310) + implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.16") testImplementation(libs.spring.boot.starter.test) testImplementation(libs.reactor.test) testImplementation(libs.mybatis.starter.test) diff --git a/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java b/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java index 565bf69..916ff57 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java +++ b/src/main/java/com/onixbyte/deltaforceguide/config/CorsConfig.java @@ -23,12 +23,16 @@ public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins(properties.allowedOrigins()) - .allowedHeaders(properties.allowedHeaders()) + .allowedOrigins(toSafeArray(properties.allowedOrigins())) + .allowedHeaders(toSafeArray(properties.allowedHeaders())) .allowedMethods(toHttpMethodNames(properties.allowedMethods())) .allowCredentials(properties.allowCredentials()) .maxAge(properties.maxAge().toSeconds()) - .exposedHeaders(properties.exposedHeaders()); + .exposedHeaders(toSafeArray(properties.exposedHeaders())); + } + + private static String[] toSafeArray(String[] values) { + return values == null ? new String[0] : values; } private static String[] toHttpMethodNames(HttpMethod[] methods) { diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmQueryController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmQueryController.java new file mode 100644 index 0000000..b4570b1 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/controller/FirearmQueryController.java @@ -0,0 +1,43 @@ +package com.onixbyte.deltaforceguide.controller; + +import com.onixbyte.deltaforceguide.domain.dto.FirearmResponse; +import com.onixbyte.deltaforceguide.domain.dto.PageResponse; +import com.onixbyte.deltaforceguide.service.FirearmQueryService; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Validated +@RestController +@RequestMapping("/api/v1/firearms") +public class FirearmQueryController { + + private final FirearmQueryService firearmQueryService; + + public FirearmQueryController(FirearmQueryService firearmQueryService) { + this.firearmQueryService = firearmQueryService; + } + + @GetMapping + public PageResponse pageQuery( + @RequestParam(defaultValue = "0") @Min(0) int page, + @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "DESC") Sort.Direction direction + ) { + return firearmQueryService.pageQuery(PageRequest.of(page, size, Sort.by(direction, sortBy))); + } + + @GetMapping("/{id}") + public FirearmResponse queryById(@PathVariable Long id) { + return firearmQueryService.queryById(id); + } +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationQueryController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationQueryController.java new file mode 100644 index 0000000..888b964 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationQueryController.java @@ -0,0 +1,43 @@ +package com.onixbyte.deltaforceguide.controller; + +import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; +import com.onixbyte.deltaforceguide.domain.dto.PageResponse; +import com.onixbyte.deltaforceguide.service.ModificationQueryService; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Validated +@RestController +@RequestMapping("/api/v1/modifications") +public class ModificationQueryController { + + private final ModificationQueryService modificationQueryService; + + public ModificationQueryController(ModificationQueryService modificationQueryService) { + this.modificationQueryService = modificationQueryService; + } + + @GetMapping + public PageResponse pageQuery( + @RequestParam(defaultValue = "0") @Min(0) int page, + @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size, + @RequestParam(defaultValue = "id") String sortBy, + @RequestParam(defaultValue = "DESC") Sort.Direction direction + ) { + return modificationQueryService.pageQuery(PageRequest.of(page, size, Sort.by(direction, sortBy))); + } + + @GetMapping("/{id}") + public ModificationResponse queryById(@PathVariable Long id) { + return modificationQueryService.queryById(id); + } +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java b/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java new file mode 100644 index 0000000..f6f46f8 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/converter/FirearmTypeConverter.java @@ -0,0 +1,21 @@ +package com.onixbyte.deltaforceguide.domain.converter; + +import com.onixbyte.deltaforceguide.enumeration.FirearmType; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +@Converter +public class FirearmTypeConverter implements AttributeConverter { + + @Override + public Integer convertToDatabaseColumn(FirearmType attribute) { + return attribute == null ? null : attribute.getCode(); + } + + @Override + public FirearmType convertToEntityAttribute(Integer dbData) { + return FirearmType.fromCode(dbData); + } +} + + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java new file mode 100644 index 0000000..fc62d43 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java @@ -0,0 +1,23 @@ +package com.onixbyte.deltaforceguide.domain.dto; + +import com.onixbyte.deltaforceguide.domain.entity.Firearm; +import com.onixbyte.deltaforceguide.enumeration.FirearmType; + +public record FirearmResponse( + Long id, + String name, + FirearmType type, + String level, + String review +) { + public static FirearmResponse from(Firearm firearm) { + return new FirearmResponse( + firearm.getId(), + firearm.getName(), + firearm.getType(), + firearm.getLevel(), + firearm.getReview() + ); + } +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java new file mode 100644 index 0000000..7eb6384 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/ModificationResponse.java @@ -0,0 +1,30 @@ +package com.onixbyte.deltaforceguide.domain.dto; + +import com.onixbyte.deltaforceguide.domain.entity.Modification; + +import java.util.List; + +public record ModificationResponse( + Long id, + Long firearmId, + String name, + String code, + List tags, + String note, + String author, + String videoUrl +) { + public static ModificationResponse from(Modification modification) { + return new ModificationResponse( + modification.getId(), + modification.getFirearm().getId(), + modification.getName(), + modification.getCode(), + modification.getTags(), + modification.getNote(), + modification.getAuthor(), + modification.getVideoUrl() + ); + } +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java new file mode 100644 index 0000000..0eefb62 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/PageResponse.java @@ -0,0 +1,24 @@ +package com.onixbyte.deltaforceguide.domain.dto; + +import org.springframework.data.domain.Page; + +import java.util.List; + +public record PageResponse( + List items, + int page, + int size, + long totalElements, + int totalPages +) { + public static PageResponse from(Page source) { + return new PageResponse<>( + source.getContent(), + source.getNumber(), + source.getSize(), + source.getTotalElements(), + source.getTotalPages() + ); + } +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java index 478cc66..9db6dc4 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java @@ -1,7 +1,10 @@ package com.onixbyte.deltaforceguide.domain.entity; +import com.onixbyte.deltaforceguide.domain.converter.FirearmTypeConverter; +import com.onixbyte.deltaforceguide.enumeration.FirearmType; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -24,10 +27,11 @@ public class Firearm { private String name; @Column(name = "type", nullable = false) - private Integer type; + @Convert(converter = FirearmTypeConverter.class) + private FirearmType type; @Column(name = "level", nullable = false) - private Integer level; + private String level; @Column(name = "review", columnDefinition = "TEXT") private String review; @@ -51,19 +55,19 @@ public class Firearm { this.name = name; } - public Integer getType() { + public FirearmType getType() { return type; } - public void setType(Integer type) { + public void setType(FirearmType type) { this.type = type; } - public Integer getLevel() { + public String getLevel() { return level; } - public void setLevel(Integer level) { + public void setLevel(String level) { this.level = level; } diff --git a/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java b/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java new file mode 100644 index 0000000..d4965fa --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/enumeration/FirearmType.java @@ -0,0 +1,37 @@ +package com.onixbyte.deltaforceguide.enumeration; + +public enum FirearmType { + + RIFLE(0), + SUB_MACHINE_GUN(1), + SHOTGUN(2), + LIGHT_MACHINE_GUN(3), + DESIGNATED_MARKSMAN_RIFLE(4), + SNIPER_RIFLE(5), + PISTOL(6), + SPECIAL(7); + + private final int code; + + FirearmType(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public static FirearmType fromCode(Integer code) { + if (code == null) { + return null; + } + + for (FirearmType type : values()) { + if (type.code == code) { + return type; + } + } + + throw new IllegalArgumentException("Unknown FirearmType code: " + code); + } +} diff --git a/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java b/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java new file mode 100644 index 0000000..cb78b5b --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/repository/FirearmRepository.java @@ -0,0 +1,8 @@ +package com.onixbyte.deltaforceguide.repository; + +import com.onixbyte.deltaforceguide.domain.entity.Firearm; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FirearmRepository extends JpaRepository { +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java b/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java new file mode 100644 index 0000000..baf5ad4 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java @@ -0,0 +1,21 @@ +package com.onixbyte.deltaforceguide.repository; + +import com.onixbyte.deltaforceguide.domain.entity.Modification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface ModificationRepository extends JpaRepository { + + @EntityGraph(attributePaths = {"firearm"}) + Page findAllBy(Pageable pageable); + + @Override + @EntityGraph(attributePaths = {"firearm"}) + Optional findById(Long id); +} + + diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/FirearmQueryService.java b/src/main/java/com/onixbyte/deltaforceguide/service/FirearmQueryService.java new file mode 100644 index 0000000..6b07124 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/service/FirearmQueryService.java @@ -0,0 +1,36 @@ +package com.onixbyte.deltaforceguide.service; + +import com.onixbyte.deltaforceguide.domain.dto.FirearmResponse; +import com.onixbyte.deltaforceguide.domain.dto.PageResponse; +import com.onixbyte.deltaforceguide.repository.FirearmRepository; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +@Service +public class FirearmQueryService { + + private final FirearmRepository firearmRepository; + + public FirearmQueryService(FirearmRepository firearmRepository) { + this.firearmRepository = firearmRepository; + } + + @Transactional(readOnly = true) + public PageResponse pageQuery(Pageable pageable) { + return PageResponse.from( + firearmRepository.findAll(pageable) + .map(FirearmResponse::from) + ); + } + + @Transactional(readOnly = true) + public FirearmResponse queryById(Long id) { + return firearmRepository.findById(id) + .map(FirearmResponse::from) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + id)); + } +} + diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/ModificationQueryService.java b/src/main/java/com/onixbyte/deltaforceguide/service/ModificationQueryService.java new file mode 100644 index 0000000..e44e616 --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/service/ModificationQueryService.java @@ -0,0 +1,36 @@ +package com.onixbyte.deltaforceguide.service; + +import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; +import com.onixbyte.deltaforceguide.domain.dto.PageResponse; +import com.onixbyte.deltaforceguide.repository.ModificationRepository; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +@Service +public class ModificationQueryService { + + private final ModificationRepository modificationRepository; + + public ModificationQueryService(ModificationRepository modificationRepository) { + this.modificationRepository = modificationRepository; + } + + @Transactional(readOnly = true) + public PageResponse pageQuery(Pageable pageable) { + return PageResponse.from( + modificationRepository.findAllBy(pageable) + .map(ModificationResponse::from) + ); + } + + @Transactional(readOnly = true) + public ModificationResponse queryById(Long id) { + return modificationRepository.findById(id) + .map(ModificationResponse::from) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); + } +} + diff --git a/src/main/resources/db/migration/V3__alter_firearm_level.sql b/src/main/resources/db/migration/V3__alter_firearm_level.sql new file mode 100644 index 0000000..3d70fc0 --- /dev/null +++ b/src/main/resources/db/migration/V3__alter_firearm_level.sql @@ -0,0 +1,2 @@ +ALTER TABLE firearm + MODIFY level VARCHAR(2) NOT NULL; diff --git a/src/test/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplicationTests.java b/src/test/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplicationTests.java deleted file mode 100644 index 237025f..0000000 --- a/src/test/java/com/onixbyte/deltaforceguide/DeltaForceGuideApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.onixbyte.deltaforceguide; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class DeltaForceGuideApplicationTests { - - @Test - void contextLoads() { - } - -}