feat: enhance Firearm entity and add query services with controllers

This commit is contained in:
2026-04-03 15:38:24 +08:00
parent 2616e70062
commit 20c2da10ab
16 changed files with 342 additions and 22 deletions
+1
View File
@@ -49,6 +49,7 @@ dependencies {
implementation(libs.flyway.core) implementation(libs.flyway.core)
implementation(libs.flyway.mysql) implementation(libs.flyway.mysql)
implementation(libs.jackson.jsr310) implementation(libs.jackson.jsr310)
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.16")
testImplementation(libs.spring.boot.starter.test) testImplementation(libs.spring.boot.starter.test)
testImplementation(libs.reactor.test) testImplementation(libs.reactor.test)
testImplementation(libs.mybatis.starter.test) testImplementation(libs.mybatis.starter.test)
@@ -23,12 +23,16 @@ public class CorsConfig implements WebMvcConfigurer {
@Override @Override
public void addCorsMappings(CorsRegistry registry) { public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") registry.addMapping("/**")
.allowedOrigins(properties.allowedOrigins()) .allowedOrigins(toSafeArray(properties.allowedOrigins()))
.allowedHeaders(properties.allowedHeaders()) .allowedHeaders(toSafeArray(properties.allowedHeaders()))
.allowedMethods(toHttpMethodNames(properties.allowedMethods())) .allowedMethods(toHttpMethodNames(properties.allowedMethods()))
.allowCredentials(properties.allowCredentials()) .allowCredentials(properties.allowCredentials())
.maxAge(properties.maxAge().toSeconds()) .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) { private static String[] toHttpMethodNames(HttpMethod[] methods) {
@@ -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<FirearmResponse> 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);
}
}
@@ -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<ModificationResponse> 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);
}
}
@@ -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<FirearmType, Integer> {
@Override
public Integer convertToDatabaseColumn(FirearmType attribute) {
return attribute == null ? null : attribute.getCode();
}
@Override
public FirearmType convertToEntityAttribute(Integer dbData) {
return FirearmType.fromCode(dbData);
}
}
@@ -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()
);
}
}
@@ -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<String> 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()
);
}
}
@@ -0,0 +1,24 @@
package com.onixbyte.deltaforceguide.domain.dto;
import org.springframework.data.domain.Page;
import java.util.List;
public record PageResponse<T>(
List<T> items,
int page,
int size,
long totalElements,
int totalPages
) {
public static <T> PageResponse<T> from(Page<T> source) {
return new PageResponse<>(
source.getContent(),
source.getNumber(),
source.getSize(),
source.getTotalElements(),
source.getTotalPages()
);
}
}
@@ -1,7 +1,10 @@
package com.onixbyte.deltaforceguide.domain.entity; 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.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue; import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType; import jakarta.persistence.GenerationType;
@@ -24,10 +27,11 @@ public class Firearm {
private String name; private String name;
@Column(name = "type", nullable = false) @Column(name = "type", nullable = false)
private Integer type; @Convert(converter = FirearmTypeConverter.class)
private FirearmType type;
@Column(name = "level", nullable = false) @Column(name = "level", nullable = false)
private Integer level; private String level;
@Column(name = "review", columnDefinition = "TEXT") @Column(name = "review", columnDefinition = "TEXT")
private String review; private String review;
@@ -51,19 +55,19 @@ public class Firearm {
this.name = name; this.name = name;
} }
public Integer getType() { public FirearmType getType() {
return type; return type;
} }
public void setType(Integer type) { public void setType(FirearmType type) {
this.type = type; this.type = type;
} }
public Integer getLevel() { public String getLevel() {
return level; return level;
} }
public void setLevel(Integer level) { public void setLevel(String level) {
this.level = level; this.level = level;
} }
@@ -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);
}
}
@@ -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<Firearm, Long> {
}
@@ -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<Modification, Long> {
@EntityGraph(attributePaths = {"firearm"})
Page<Modification> findAllBy(Pageable pageable);
@Override
@EntityGraph(attributePaths = {"firearm"})
Optional<Modification> findById(Long id);
}
@@ -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<FirearmResponse> 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));
}
}
@@ -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<ModificationResponse> 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));
}
}
@@ -0,0 +1,2 @@
ALTER TABLE firearm
MODIFY level VARCHAR(2) NOT 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() {
}
}