feat: add tag filtering to modification queries and implement tag retrieval endpoint

This commit is contained in:
2026-04-07 11:58:46 +08:00
parent 1a88cf37bc
commit a28033ff4c
4 changed files with 77 additions and 11 deletions
@@ -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<String> 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);
}
}
@@ -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<String> getTags(@RequestParam(required = false) Long firearmId) {
return modificationService.findAllTags(firearmId);
}
}
@@ -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<Modification, Long
@Override
@EntityGraph(attributePaths = {"firearm"})
Optional<Modification> 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<Modification> 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<String> findAllTags(@Param("firearmId") Long firearmId);
}
@@ -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<ModificationResponse> pageQuery(Long firearmId, Pageable pageable) {
Page<Modification> page = firearmId == null
? modificationRepository.findAllBy(pageable)
: modificationRepository.findAllByFirearm_Id(firearmId, pageable);
public PageResponse<ModificationResponse> pageQuery(Long firearmId, List<String> 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<Modification> 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<String> findAllTags(Long firearmId) {
return modificationRepository.findAllTags(firearmId);
}
}