From 1a88cf37bccc6e460172597f56be2d266c549edf Mon Sep 17 00:00:00 2001 From: zihluwang Date: Mon, 6 Apr 2026 21:03:50 +0800 Subject: [PATCH 1/4] chore: update artefact version to 1.1.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cb94965..b5a5b32 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -artefactVersion = 1.0.0 \ No newline at end of file +artefactVersion = 1.1.0 \ No newline at end of file From a28033ff4c16b4aef0f7887f0827d4b704290e3a Mon Sep 17 00:00:00 2001 From: zihluwang Date: Tue, 7 Apr 2026 11:58:46 +0800 Subject: [PATCH 2/4] feat: add tag filtering to modification queries and implement tag retrieval endpoint --- .../controller/ModificationController.java | 8 +++-- .../controller/TagController.java | 25 ++++++++++++++ .../repository/ModificationRepository.java | 21 ++++++++++-- .../service/ModificationService.java | 34 +++++++++++++++---- 4 files changed, 77 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java index d047263..816adda 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java +++ b/src/main/java/com/onixbyte/deltaforceguide/controller/ModificationController.java @@ -15,6 +15,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + @Validated @RestController @RequestMapping("/modifications") @@ -32,9 +34,10 @@ public class ModificationController { @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size, @RequestParam(required = false) @Positive Long firearmId, @RequestParam(defaultValue = "id") String sortBy, - @RequestParam(defaultValue = "DESC") Sort.Direction direction + @RequestParam(defaultValue = "DESC") Sort.Direction direction, + @RequestParam(required = false) List tags ) { - return modificationService.pageQuery(firearmId, PageRequest.of(page, size, Sort.by(direction, sortBy))); + return modificationService.pageQuery(firearmId, tags, PageRequest.of(page, size, Sort.by(direction, sortBy))); } @GetMapping("/{id}") @@ -42,4 +45,3 @@ public class ModificationController { return modificationService.queryById(id); } } - diff --git a/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java b/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java new file mode 100644 index 0000000..41230aa --- /dev/null +++ b/src/main/java/com/onixbyte/deltaforceguide/controller/TagController.java @@ -0,0 +1,25 @@ +package com.onixbyte.deltaforceguide.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import com.onixbyte.deltaforceguide.service.ModificationService; + +import java.util.List; + +@RestController +@RequestMapping("/tags") +public class TagController { + + private final ModificationService modificationService; + + public TagController(ModificationService modificationService) { + this.modificationService = modificationService; + } + + @GetMapping + public List getTags(@RequestParam(required = false) Long firearmId) { + return modificationService.findAllTags(firearmId); + } +} diff --git a/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java b/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java index f81f73e..5fd88d4 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java +++ b/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java @@ -5,8 +5,11 @@ 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 org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -21,6 +24,20 @@ public interface ModificationRepository extends JpaRepository findById(Long id); + + @Query(value = """ + SELECT * FROM modification m + WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId) + AND (CAST(:tagsJson AS text) IS NULL OR cast(m.tags as jsonb) @> cast(CAST(:tagsJson AS text) as jsonb)) + """, + countQuery = """ + SELECT count(*) FROM modification m + WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId) + AND (CAST(:tagsJson AS text) IS NULL OR cast(m.tags as jsonb) @> cast(CAST(:tagsJson AS text) as jsonb)) + """, + nativeQuery = true) + Page pageQueryByFirearmAndTags(@Param("firearmId") Long firearmId, @Param("tagsJson") String tagsJson, Pageable pageable); + + @Query(value = "SELECT DISTINCT jsonb_array_elements_text(cast(tags as jsonb)) FROM modification WHERE (:firearmId IS NULL OR firearm_id = :firearmId)", nativeQuery = true) + List findAllTags(@Param("firearmId") Long firearmId); } - - diff --git a/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java b/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java index b1d9cc1..4da45d3 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java +++ b/src/main/java/com/onixbyte/deltaforceguide/service/ModificationService.java @@ -4,6 +4,8 @@ import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse; import com.onixbyte.deltaforceguide.domain.dto.PageResponse; import com.onixbyte.deltaforceguide.domain.entity.Modification; import com.onixbyte.deltaforceguide.repository.ModificationRepository; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -11,20 +13,36 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; +import java.util.List; + @Service public class ModificationService { private final ModificationRepository modificationRepository; + private final ObjectMapper objectMapper; - public ModificationService(ModificationRepository modificationRepository) { + public ModificationService(ModificationRepository modificationRepository, ObjectMapper objectMapper) { this.modificationRepository = modificationRepository; + this.objectMapper = objectMapper; } @Transactional(readOnly = true) - public PageResponse pageQuery(Long firearmId, Pageable pageable) { - Page page = firearmId == null - ? modificationRepository.findAllBy(pageable) - : modificationRepository.findAllByFirearm_Id(firearmId, pageable); + public PageResponse pageQuery(Long firearmId, List tags, Pageable pageable) { + String tagsJson = null; + if (tags != null && !tags.isEmpty()) { + try { + tagsJson = objectMapper.writeValueAsString(tags); + } catch (JsonProcessingException e) { + throw new RuntimeException("Failed to serialize tags", e); + } + } + + Page page; + if (tagsJson != null || firearmId != null) { + page = modificationRepository.pageQueryByFirearmAndTags(firearmId, tagsJson, pageable); + } else { + page = modificationRepository.findAllBy(pageable); + } return PageResponse.from(page.map(ModificationResponse::from)); } @@ -35,5 +53,9 @@ public class ModificationService { .map(ModificationResponse::from) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id)); } -} + @Transactional(readOnly = true) + public List findAllTags(Long firearmId) { + return modificationRepository.findAllTags(firearmId); + } +} From 09926353917bd28776fd305d09973584bfee2e5f Mon Sep 17 00:00:00 2001 From: zihluwang Date: Thu, 9 Apr 2026 11:37:02 +0800 Subject: [PATCH 3/4] feat: add nullability annotations to findById method in ModificationRepository --- .../repository/ModificationRepository.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java b/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java index 5fd88d4..d4f891a 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java +++ b/src/main/java/com/onixbyte/deltaforceguide/repository/ModificationRepository.java @@ -1,6 +1,7 @@ package com.onixbyte.deltaforceguide.repository; import com.onixbyte.deltaforceguide.domain.entity.Modification; +import org.jspecify.annotations.NonNull; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.EntityGraph; @@ -23,15 +24,16 @@ public interface ModificationRepository extends JpaRepository findById(Long id); + @NonNull + Optional findById(@NonNull Long id); @Query(value = """ - SELECT * FROM modification m + SELECT * FROM modification m WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId) AND (CAST(:tagsJson AS text) IS NULL OR cast(m.tags as jsonb) @> cast(CAST(:tagsJson AS text) as jsonb)) """, countQuery = """ - SELECT count(*) FROM modification m + SELECT count(*) FROM modification m WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId) AND (CAST(:tagsJson AS text) IS NULL OR cast(m.tags as jsonb) @> cast(CAST(:tagsJson AS text) as jsonb)) """, From bd1f2441f3845577de14f674140b70bde156f744 Mon Sep 17 00:00:00 2001 From: zihluwang Date: Thu, 9 Apr 2026 13:28:28 +0800 Subject: [PATCH 4/4] feat: add calibre, fire rate, armour damage, and body damage fields to Firearm model and update related response and migration scripts --- .../domain/dto/FirearmResponse.java | 8 +++ .../domain/entity/Firearm.java | 44 ++++++++++++++ .../db/migration/V3__bullet_and_damages.sql | 60 +++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 src/main/resources/db/migration/V3__bullet_and_damages.sql diff --git a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java index fc62d43..eb5b1ac 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/dto/FirearmResponse.java @@ -8,6 +8,10 @@ public record FirearmResponse( String name, FirearmType type, String level, + String calibre, + Integer fireRate, + Integer armourDamage, + Integer bodyDamage, String review ) { public static FirearmResponse from(Firearm firearm) { @@ -16,6 +20,10 @@ public record FirearmResponse( firearm.getName(), firearm.getType(), firearm.getLevel(), + firearm.getCalibre(), + firearm.getFireRate(), + firearm.getArmourDamage(), + firearm.getBodyDamage(), firearm.getReview() ); } 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 9db6dc4..c38173f 100644 --- a/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java +++ b/src/main/java/com/onixbyte/deltaforceguide/domain/entity/Firearm.java @@ -36,6 +36,18 @@ public class Firearm { @Column(name = "review", columnDefinition = "TEXT") private String review; + @Column(name = "calibre") + private String calibre; + + @Column(name = "fire_rate") + private Integer fireRate; + + @Column(name = "armour_damage") + private Integer armourDamage; + + @Column(name = "body_damage") + private Integer bodyDamage; + @OneToMany(mappedBy = "firearm", cascade = CascadeType.ALL, orphanRemoval = true) private List modifications = new ArrayList<>(); @@ -79,6 +91,38 @@ public class Firearm { this.review = review; } + public String getCalibre() { + return calibre; + } + + public void setCalibre(String calibre) { + this.calibre = calibre; + } + + public Integer getFireRate() { + return fireRate; + } + + public void setFireRate(Integer fireRate) { + this.fireRate = fireRate; + } + + public Integer getArmourDamage() { + return armourDamage; + } + + public void setArmourDamage(Integer armourDamage) { + this.armourDamage = armourDamage; + } + + public Integer getBodyDamage() { + return bodyDamage; + } + + public void setBodyDamage(Integer bodyDamage) { + this.bodyDamage = bodyDamage; + } + public List getModifications() { return modifications; } diff --git a/src/main/resources/db/migration/V3__bullet_and_damages.sql b/src/main/resources/db/migration/V3__bullet_and_damages.sql new file mode 100644 index 0000000..97ac135 --- /dev/null +++ b/src/main/resources/db/migration/V3__bullet_and_damages.sql @@ -0,0 +1,60 @@ +-- 创建新表 +CREATE TABLE firearm_new +( + id BIGSERIAL NOT NULL, + name VARCHAR(64) NOT NULL, + type INTEGER NOT NULL, + level VARCHAR(10) NOT NULL, + calibre VARCHAR(20) NOT NULL, + fire_rate INTEGER NOT NULL, + armour_damage INTEGER NOT NULL, + body_damage INTEGER NOT NULL, + review TEXT NULL, + CONSTRAINT firearm_new_pkey PRIMARY KEY (id) +); + +-- 迁移数据 +INSERT INTO firearm_new(id, name, type, level, calibre, fire_rate, armour_damage, body_damage, + review) +SELECT id, + name, + type, + level, + calibre, + 0, + armour_damage, + body_damage, + review +FROM firearm; + +-- 处理外键(关键步骤) +-- 先删除指向旧表的外键约束 +ALTER TABLE modification + DROP CONSTRAINT fk_modification_firearm; + +-- 重命名旧表和索引 +ALTER TABLE firearm + RENAME TO firearm_legacy; +ALTER INDEX firearm_pkey RENAME TO firearm_legacy_pkey; + +-- 重命名新表和索引 +ALTER TABLE firearm_new + RENAME TO firearm; +ALTER INDEX firearm_new_pkey RENAME TO firearm_pkey; + +-- 重新建立外键,指向新的 firearm 表 +ALTER TABLE modification + ADD CONSTRAINT fk_modification_firearm + FOREIGN KEY (firearm_id) REFERENCES firearm (id); + +-- 序列所有权与名称修正 +ALTER SEQUENCE firearm_id_seq RENAME TO firearm_legacy_id_seq; +ALTER SEQUENCE firearm_new_id_seq RENAME TO firearm_id_seq; +ALTER SEQUENCE firearm_id_seq OWNED BY firearm.id; + +-- 更新序列计数器 +SELECT setval('firearm_id_seq', coalesce(max(id), 1)) +FROM firearm; + +-- 删除旧表 +DROP TABLE IF EXISTS firearm_legacy CASCADE; \ No newline at end of file