Merge branch 'develop' into feature/github-webhook
This commit is contained in:
@@ -3,6 +3,11 @@ package com.onixbyte.deltaforceguide;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* Entry point for the Delta Force Guide Server application.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class DeltaForceGuideApplication {
|
||||
|
||||
|
||||
@@ -12,6 +12,11 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* Client for generating and verifying JSON Web Tokens using the Auth0 java-jwt library.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Component
|
||||
public class TokenClient {
|
||||
|
||||
|
||||
@@ -12,10 +12,21 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Configuration for CORS (Cross-Origin Resource Sharing) policies.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({CorsProperties.class})
|
||||
public class CorsConfig {
|
||||
|
||||
/**
|
||||
* Creates the CORS configuration source with properties from configuration.
|
||||
*
|
||||
* @param properties the CORS configuration properties
|
||||
* @return the CORS configuration source
|
||||
*/
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource(
|
||||
CorsProperties properties
|
||||
|
||||
@@ -6,6 +6,11 @@ import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilde
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Configuration for Jackson JSON serialisation and deserialisation settings.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@ package com.onixbyte.deltaforceguide.config;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Configuration for MyBatis SQL mapping framework integration.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Configuration
|
||||
@MapperScan(basePackages = {"com.onixbyte.deltaforceguide.mapper"})
|
||||
public class MyBatisConfig {
|
||||
|
||||
@@ -23,12 +23,23 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.ExceptionTranslationFilter;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
|
||||
/**
|
||||
* Spring Security configuration defining authentication, authorisation, and filter chains.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@EnableConfigurationProperties({TokenProperties.class, CookieProperties.class})
|
||||
public class SecurityConfig {
|
||||
|
||||
/**
|
||||
* Configures the HTTP security filter chain including endpoint authorisation and JWT filter.
|
||||
*
|
||||
* @param http the HTTP security builder
|
||||
* @return the configured security filter chain
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(
|
||||
HttpSecurity httpSecurity,
|
||||
@@ -48,11 +59,20 @@ public class SecurityConfig {
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the BCrypt password encoder for credential hashing.
|
||||
* @return the BCrypt password encoder
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the authentication manager for the security configuration.
|
||||
*
|
||||
* @return the authentication manager
|
||||
*/
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(
|
||||
UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider
|
||||
|
||||
@@ -3,6 +3,11 @@ package com.onixbyte.deltaforceguide.config;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
|
||||
/**
|
||||
* Configuration for Spring Data JPA auditing and repository settings.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaRepositories(basePackages = {"com.onixbyte.deltaforceguide.repository"})
|
||||
public class SpringDataConfig {
|
||||
|
||||
@@ -19,6 +19,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* REST controller for user authentication endpoints (login, logout).
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Tag(name = "用户鉴权", description = "处理用户登录与退出功能")
|
||||
@RestController
|
||||
@RequestMapping("/auth")
|
||||
|
||||
@@ -8,6 +8,11 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* REST controller for retrieving daily-generated passwords.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Tag(name = "每日密码", description = "获取每日密码信息")
|
||||
@RestController
|
||||
@RequestMapping("/daily-passwords")
|
||||
|
||||
@@ -15,6 +15,11 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* REST controller for firearm CRUD operations.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Tag(name = "武器管理", description = "与武器有关的操作")
|
||||
@RestController
|
||||
@RequestMapping("/firearms")
|
||||
|
||||
@@ -6,6 +6,11 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* Global exception handler that translates exceptions into standard error responses.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST controller for modification CRUD operations, including batch creation and deletion.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Tag(name = "改装管理", description = "对枪械改装的管理")
|
||||
@RestController
|
||||
@RequestMapping("/modifications")
|
||||
|
||||
@@ -10,6 +10,11 @@ import com.onixbyte.deltaforceguide.service.ModificationService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* REST controller for retrieving available modification tags.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Tag(name = "标签管理", description = "管理标签信息")
|
||||
@RestController
|
||||
@RequestMapping("/tags")
|
||||
|
||||
@@ -4,6 +4,11 @@ import com.onixbyte.deltaforceguide.enumeration.FirearmType;
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
/**
|
||||
* JPA attribute converter that maps {@link FirearmType} enum to/from its integer database representation.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Converter
|
||||
public class FirearmTypeConverter implements AttributeConverter<FirearmType, Integer> {
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request DTO for creating or updating an accessory attached to a modification.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record AccessoryRequest(
|
||||
@NotBlank(message = "插槽名称不能为空")
|
||||
String slotName,
|
||||
|
||||
@@ -4,6 +4,11 @@ import com.onixbyte.deltaforceguide.domain.entity.Accessory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Response DTO for an accessory attached to a modification.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record AccessoryResponse(
|
||||
String slotName,
|
||||
String accessoryName,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.onixbyte.deltaforceguide.domain.dto;
|
||||
|
||||
/**
|
||||
* DTO representing a single daily-generated password for a map.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record DailyPassword(
|
||||
String mapName,
|
||||
String password
|
||||
|
||||
@@ -3,6 +3,11 @@ package com.onixbyte.deltaforceguide.domain.dto;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DTO containing daily password data including update information and password list.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record DailyPasswordData(
|
||||
String updateDate,
|
||||
Integer totalCount,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.onixbyte.deltaforceguide.domain.dto;
|
||||
|
||||
/**
|
||||
* DTO holding metadata about the daily password source and update tracking.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record DailyPasswordMetadata(
|
||||
String version,
|
||||
String author
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.onixbyte.deltaforceguide.domain.dto;
|
||||
|
||||
/**
|
||||
* Response DTO wrapping daily password data with metadata.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record DailyPasswordResponse(
|
||||
String status,
|
||||
String message,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.onixbyte.deltaforceguide.domain.dto;
|
||||
|
||||
/**
|
||||
* Standard error response body returned on API failures.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record ErrorResponse(
|
||||
String message
|
||||
) {
|
||||
|
||||
@@ -2,6 +2,11 @@ package com.onixbyte.deltaforceguide.domain.dto;
|
||||
|
||||
import com.onixbyte.deltaforceguide.enumeration.FirearmType;
|
||||
|
||||
/**
|
||||
* Request DTO for creating or updating a firearm.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record FirearmRequest(
|
||||
String name,
|
||||
FirearmType type,
|
||||
|
||||
@@ -3,6 +3,11 @@ package com.onixbyte.deltaforceguide.domain.dto;
|
||||
import com.onixbyte.deltaforceguide.domain.entity.Firearm;
|
||||
import com.onixbyte.deltaforceguide.enumeration.FirearmType;
|
||||
|
||||
/**
|
||||
* Response DTO for a firearm record, including associated modifications.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record FirearmResponse(
|
||||
Long id,
|
||||
String name,
|
||||
|
||||
@@ -3,6 +3,11 @@ package com.onixbyte.deltaforceguide.domain.dto;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* Login request containing principle (username/email) and credential (password).
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Schema(description = "登录请求")
|
||||
public record LoginRequest(
|
||||
@NotBlank(message = "登录名称不能为空")
|
||||
|
||||
+5
@@ -5,6 +5,11 @@ import jakarta.validation.constraints.NotEmpty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request DTO for batch creation of modifications.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record ModificationBatchCreateRequest(
|
||||
@NotEmpty(message = "批量创建列表不能为空")
|
||||
List<@Valid ModificationRequest> modifications
|
||||
|
||||
+5
@@ -5,6 +5,11 @@ import jakarta.validation.constraints.Positive;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request DTO for batch deletion of modifications by ID.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record ModificationBatchDeleteRequest(
|
||||
@NotEmpty(message = "批量删除ID列表不能为空")
|
||||
List<@Positive(message = "ID必须为正数") Long> ids
|
||||
|
||||
@@ -8,6 +8,11 @@ import jakarta.validation.constraints.Positive;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Request DTO for creating or updating a modification.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record ModificationRequest(
|
||||
@NotNull(message = "武器ID不能为空")
|
||||
@Positive(message = "武器ID必须为正数")
|
||||
|
||||
@@ -4,6 +4,11 @@ import com.onixbyte.deltaforceguide.domain.entity.Modification;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Response DTO for a modification record including accessories and tags.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record ModificationResponse(
|
||||
Long id,
|
||||
Long firearmId,
|
||||
|
||||
@@ -4,6 +4,11 @@ import org.springframework.data.domain.Page;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Generic paginated response wrapper for list endpoints.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record PageResponse<T>(
|
||||
List<T> items,
|
||||
int page,
|
||||
|
||||
@@ -3,6 +3,11 @@ package com.onixbyte.deltaforceguide.domain.dto;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* Request DTO for a tuning adjustment on an accessory.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record TuningRequest(
|
||||
@NotBlank(message = "调校项名称不能为空")
|
||||
String tuningName,
|
||||
|
||||
@@ -2,6 +2,11 @@ package com.onixbyte.deltaforceguide.domain.dto;
|
||||
|
||||
import com.onixbyte.deltaforceguide.domain.entity.Tuning;
|
||||
|
||||
/**
|
||||
* Response DTO for a tuning adjustment on an accessory.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record TuningResponse(
|
||||
String tuningName,
|
||||
Double tuningValue
|
||||
|
||||
@@ -2,6 +2,11 @@ package com.onixbyte.deltaforceguide.domain.dto;
|
||||
|
||||
import com.onixbyte.deltaforceguide.domain.entity.User;
|
||||
|
||||
/**
|
||||
* Response DTO for a user account, including associated credentials.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public record UserResponse(
|
||||
Long id,
|
||||
String username,
|
||||
|
||||
@@ -4,6 +4,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Entity representing an accessory attached to a modification, stored as JSONB.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class Accessory {
|
||||
|
||||
private String slotName;
|
||||
|
||||
@@ -15,6 +15,11 @@ import jakarta.persistence.Table;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entity representing a firearm in the Delta Force game.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "firearm")
|
||||
public class Firearm {
|
||||
|
||||
@@ -7,6 +7,11 @@ import org.hibernate.annotations.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entity representing a firearm modification or build configuration.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "modification",
|
||||
|
||||
@@ -2,6 +2,11 @@ package com.onixbyte.deltaforceguide.domain.entity;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Entity representing a tuning adjustment for an accessory, stored as JSONB within Accessory.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class Tuning {
|
||||
|
||||
private String tuningName;
|
||||
|
||||
@@ -12,6 +12,11 @@ import jakarta.persistence.Table;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Entity representing an application user with authentication credentials.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "app_user")
|
||||
public class User {
|
||||
|
||||
@@ -12,6 +12,11 @@ import jakarta.persistence.MapsId;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* Entity representing a user credential linked to an authentication provider.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "app_user_credential")
|
||||
public class UserCredential {
|
||||
@@ -28,7 +33,7 @@ public class UserCredential {
|
||||
@JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "fk_user_credential_user"))
|
||||
private User user;
|
||||
|
||||
@Column(name = "credential", nullable = false, length = 255)
|
||||
@Column(name = "credential", nullable = false)
|
||||
private String credential;
|
||||
|
||||
public UserCredentialId getId() {
|
||||
|
||||
@@ -5,6 +5,11 @@ import jakarta.persistence.Embeddable;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Composite key for the UserCredential entity, combining user ID and provider.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Embeddable
|
||||
public class UserCredentialId implements Serializable {
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
package com.onixbyte.deltaforceguide.enumeration;
|
||||
|
||||
/**
|
||||
* Enumeration of firearm types in the Delta Force game.
|
||||
* Each type is associated with an integer code used for database persistence.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public enum FirearmType {
|
||||
|
||||
RIFLE(0),
|
||||
@@ -21,6 +27,13 @@ public enum FirearmType {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a FirearmType from its integer code.
|
||||
*
|
||||
* @param code the integer code, may be null
|
||||
* @return the corresponding FirearmType, or null if the code is null
|
||||
* @throws IllegalArgumentException if the code does not match any known type
|
||||
*/
|
||||
public static FirearmType fromCode(Integer code) {
|
||||
if (code == null) {
|
||||
return null;
|
||||
|
||||
@@ -2,6 +2,11 @@ package com.onixbyte.deltaforceguide.exeption;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* Custom runtime exception that carries an HTTP status code for API error responses.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class BizException extends RuntimeException {
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
@@ -29,6 +28,11 @@ import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Servlet filter that extracts and validates JWT tokens from httpOnly cookies for each request.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Component
|
||||
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
@@ -52,6 +56,13 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
this.handlerExceptionResolver = handlerExceptionResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts JWT from httpOnly cookie, validates it, and sets the security context.
|
||||
*
|
||||
* @param request the HTTP request
|
||||
* @param response the HTTP response
|
||||
* @param filterChain the filter chain
|
||||
*/
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
|
||||
@@ -6,6 +6,11 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Manager providing cookie construction operations with configurable properties.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Component
|
||||
public class CookieManager {
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ import java.time.Duration;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Manager for daily password data access and caching coordination.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Component
|
||||
public class DailyPasswordManager {
|
||||
|
||||
@@ -49,6 +54,10 @@ public class DailyPasswordManager {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the daily password from cache or generates a new one.
|
||||
* @return the daily password response
|
||||
*/
|
||||
public DailyPasswordResponse getDailyPassword() {
|
||||
var key = CACHE_KEY_PREFIX + LocalDate.now();
|
||||
|
||||
|
||||
@@ -3,11 +3,15 @@ package com.onixbyte.deltaforceguide.manager;
|
||||
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
|
||||
import com.onixbyte.deltaforceguide.repository.UserCredentialRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Manager for user credential persistence and authentication data access.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Component
|
||||
public class UserCredentialManager {
|
||||
|
||||
@@ -17,27 +21,52 @@ public class UserCredentialManager {
|
||||
this.userCredentialRepository = userCredentialRepository;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds all credentials belonging to a specific user.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @return list of matching credentials
|
||||
*/
|
||||
public List<UserCredential> findAllByUserId(Long userId) {
|
||||
return userCredentialRepository.findAllByUserId(userId);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds a credential for a specific user and provider combination.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @param provider the authentication provider
|
||||
* @return the matching credential, if found
|
||||
*/
|
||||
public Optional<UserCredential> findByUserIdAndProvider(Long userId, String provider) {
|
||||
return userCredentialRepository.findByUserIdAndProvider(userId, provider);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Persists a new or updated credential.
|
||||
*
|
||||
* @param userCredential the credential to save
|
||||
* @return the saved credential
|
||||
*/
|
||||
public UserCredential save(UserCredential userCredential) {
|
||||
return userCredentialRepository.save(userCredential);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Deletes a credential for a specific user and provider.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @param provider the authentication provider
|
||||
*/
|
||||
public void deleteByUserIdAndProvider(Long userId, String provider) {
|
||||
userCredentialRepository.deleteByUserIdAndProvider(userId, provider);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Deletes all credentials belonging to a user.
|
||||
*
|
||||
* @param userId the user ID
|
||||
*/
|
||||
public void deleteAllByUserId(Long userId) {
|
||||
userCredentialRepository.deleteAllByUserId(userId);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Manager for user entity persistence and query operations.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Component
|
||||
public class UserManager {
|
||||
|
||||
@@ -17,17 +22,30 @@ public class UserManager {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds a user by their ID.
|
||||
*
|
||||
* @param id the user ID
|
||||
* @return the matching user, if found
|
||||
*/
|
||||
public Optional<User> findById(Long id) {
|
||||
return userRepository.findById(id);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Retrieves all registered users.
|
||||
* @return list of all users
|
||||
*/
|
||||
public List<User> findAll() {
|
||||
return userRepository.findAll();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds a user by their username.
|
||||
*
|
||||
* @param username the username to search for
|
||||
* @return the matching user, if found
|
||||
*/
|
||||
public Optional<User> findByUsername(String username) {
|
||||
return userRepository.findByUsername(username);
|
||||
}
|
||||
@@ -37,16 +55,31 @@ public class UserManager {
|
||||
return userRepository.findByEmail(email);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Persists a new or updated user.
|
||||
*
|
||||
* @param user the user to save
|
||||
* @return the saved user
|
||||
*/
|
||||
public User save(User user) {
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Deletes a user by their ID.
|
||||
*
|
||||
* @param id the user ID to delete
|
||||
*/
|
||||
public void deleteById(Long id) {
|
||||
userRepository.deleteById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a user by their username or email address.
|
||||
*
|
||||
* @param principal the username or email to search for
|
||||
* @return the matching user, if found
|
||||
*/
|
||||
public Optional<User> findByUsernameOrEmail(String principal) {
|
||||
return userRepository.findByUsernameOrEmail(principal);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,16 @@ import org.springframework.boot.web.server.Cookie;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Configuration properties for HTTP cookies used in authentication, prefixed with "app.cookie".
|
||||
*
|
||||
* @param httpOnly whether the cookie is httpOnly
|
||||
* @param secure whether the cookie is secure
|
||||
* @param path the cookie path
|
||||
* @param maxAge the maximum age of the cookie
|
||||
* @param sameSite the SameSite policy for the cookie
|
||||
* @author zihluwang
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "app.cookie")
|
||||
public record CookieProperties(
|
||||
@DefaultValue("true") Boolean httpOnly,
|
||||
|
||||
@@ -6,6 +6,18 @@ import org.springframework.http.HttpMethod;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Configuration properties for CORS settings, prefixed with "app.cors".
|
||||
*
|
||||
* @param allowedHeaders headers allowed in CORS requests
|
||||
* @param allowedMethods HTTP methods allowed in CORS requests
|
||||
* @param allowedOrigins origins permitted to make cross-origin requests
|
||||
* @param allowCredentials whether credentials are allowed in CORS requests
|
||||
* @param allowPrivateNetwork whether private network access is permitted
|
||||
* @param maxAge how long the CORS preflight response may be cached
|
||||
* @param exposedHeaders headers exposed to the client in CORS responses
|
||||
* @author zihluwang
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "app.cors")
|
||||
public record CorsProperties(
|
||||
@DefaultValue({"Content-Type", "Authorization"})
|
||||
|
||||
@@ -4,6 +4,14 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Configuration properties for JWT token generation and validation, prefixed with "app.jwt".
|
||||
*
|
||||
* @param issuer the JWT issuer claim
|
||||
* @param secret the signing secret for JWT tokens
|
||||
* @param validTime the duration for which a token remains valid
|
||||
* @author zihluwang
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "app.jwt")
|
||||
public record TokenProperties(
|
||||
String issuer,
|
||||
|
||||
@@ -9,6 +9,11 @@ import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for {@link Firearm} entity operations.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface FirearmRepository extends JpaRepository<Firearm, Long> {
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for {@link Modification} entity operations,
|
||||
* including native JSONB tag filtering for Postgres.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface ModificationRepository extends JpaRepository<Modification, Long> {
|
||||
|
||||
@@ -27,6 +33,14 @@ public interface ModificationRepository extends JpaRepository<Modification, Long
|
||||
@NonNull
|
||||
Optional<Modification> findById(@NonNull Long id);
|
||||
|
||||
/**
|
||||
* Page query modifications with optional firearm and JSONB tag filtering.
|
||||
*
|
||||
* @param firearmId optional firearm ID filter (nullable)
|
||||
* @param tagsJson optional JSON array of tags to match via Postgres {@code @>} operator (nullable)
|
||||
* @param pageable pagination information
|
||||
* @return a page of matching modifications
|
||||
*/
|
||||
@Query(value = """
|
||||
SELECT * FROM modification m
|
||||
WHERE (:firearmId IS NULL OR m.firearm_id = :firearmId)
|
||||
@@ -40,6 +54,12 @@ public interface ModificationRepository extends JpaRepository<Modification, Long
|
||||
nativeQuery = true)
|
||||
Page<Modification> pageQueryByFirearmAndTags(@Param("firearmId") Long firearmId, @Param("tagsJson") String tagsJson, Pageable pageable);
|
||||
|
||||
/**
|
||||
* Retrieve all distinct tag values from modifications, optionally filtered by firearm.
|
||||
*
|
||||
* @param firearmId optional firearm ID filter (nullable)
|
||||
* @return list of distinct tag strings
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -12,9 +12,20 @@ import org.springframework.stereotype.Repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for {@link UserCredential} entity operations.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface UserCredentialRepository extends JpaRepository<UserCredential, UserCredentialId> {
|
||||
|
||||
/**
|
||||
* Find all credentials belonging to a given user.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @return list of matching credentials
|
||||
*/
|
||||
@EntityGraph(attributePaths = {"user"})
|
||||
@Query("""
|
||||
select uc
|
||||
@@ -23,6 +34,13 @@ public interface UserCredentialRepository extends JpaRepository<UserCredential,
|
||||
""")
|
||||
List<UserCredential> findAllByUserId(@Param("userId") Long userId);
|
||||
|
||||
/**
|
||||
* Find a specific credential for a user by provider.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @param provider the authentication provider identifier
|
||||
* @return an optional containing the matching credential, or empty if not found
|
||||
*/
|
||||
@EntityGraph(attributePaths = {"user"})
|
||||
@Query("""
|
||||
select uc
|
||||
@@ -32,6 +50,12 @@ public interface UserCredentialRepository extends JpaRepository<UserCredential,
|
||||
""")
|
||||
Optional<UserCredential> findByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider);
|
||||
|
||||
/**
|
||||
* Delete a specific credential for a user by provider.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @param provider the authentication provider identifier
|
||||
*/
|
||||
@Modifying
|
||||
@Query("""
|
||||
delete from UserCredential uc
|
||||
@@ -40,6 +64,11 @@ public interface UserCredentialRepository extends JpaRepository<UserCredential,
|
||||
""")
|
||||
void deleteByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider);
|
||||
|
||||
/**
|
||||
* Delete all credentials for a given user.
|
||||
*
|
||||
* @param userId the user ID
|
||||
*/
|
||||
@Modifying
|
||||
@Query("""
|
||||
delete from UserCredential uc
|
||||
|
||||
@@ -10,6 +10,11 @@ import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Spring Data JPA repository for {@link User} entity operations.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
@@ -28,6 +33,12 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
||||
|
||||
boolean existsByEmail(String email);
|
||||
|
||||
/**
|
||||
* Find a user by either username or email.
|
||||
*
|
||||
* @param principal the username or email to search for
|
||||
* @return an optional containing the matching user, or empty if not found
|
||||
*/
|
||||
@EntityGraph(attributePaths = {"credentials"})
|
||||
@Query("""
|
||||
select u
|
||||
|
||||
@@ -7,6 +7,11 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to mark controller endpoints that require authentication.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
|
||||
+5
@@ -8,6 +8,11 @@ import org.springframework.security.core.GrantedAuthority;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Custom authentication token for username/password-based login flows.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class UsernamePasswordAuthentication implements Authentication, CredentialsContainer {
|
||||
private final String username;
|
||||
private String password;
|
||||
|
||||
+17
@@ -17,6 +17,11 @@ import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Authentication provider that validates username/password credentials against stored BCrypt hashes.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Component
|
||||
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
@@ -36,6 +41,12 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
|
||||
this.userCredentialRepository = userCredentialRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the username/password credentials against stored BCrypt hashes.
|
||||
*
|
||||
* @param authentication the authentication request object
|
||||
* @return a fully authenticated object including user details
|
||||
*/
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
if (!(authentication instanceof UsernamePasswordAuthentication usernamePasswordAuthentication)) {
|
||||
@@ -75,6 +86,12 @@ public class UsernamePasswordAuthenticationProvider implements AuthenticationPro
|
||||
return usernamePasswordAuthentication;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this provider supports the given authentication type.
|
||||
*
|
||||
* @param authentication the authentication class to check
|
||||
* @return true if this provider supports the given type
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return UsernamePasswordAuthentication.class.isAssignableFrom(authentication);
|
||||
|
||||
@@ -10,9 +10,11 @@ import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Service handling user authentication, login, and session management.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Service
|
||||
public class AuthService {
|
||||
|
||||
@@ -23,6 +25,16 @@ public class AuthService {
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a user with the given login credentials.
|
||||
* <p>
|
||||
* Delegates authentication to Spring Security's {@link AuthenticationManager} and verifies
|
||||
* that the result is of the expected {@link UsernamePasswordAuthentication} type.
|
||||
*
|
||||
* @param request the login credentials containing principle and password
|
||||
* @return the authenticated {@link User}
|
||||
* @throws BizException if authentication fails or the result type is unexpected
|
||||
*/
|
||||
public User login(LoginRequest request) {
|
||||
var _authentication = authenticationManager.authenticate(UsernamePasswordAuthentication
|
||||
.unauthenticated(request.principle(), request.credential()));
|
||||
|
||||
@@ -6,6 +6,11 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Service for building HTTP cookies with configurable properties.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Service
|
||||
public class CookieService {
|
||||
|
||||
@@ -15,10 +20,25 @@ public class CookieService {
|
||||
this.cookieManager = cookieManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a response cookie with the default max age from configuration.
|
||||
*
|
||||
* @param cookieName the cookie name
|
||||
* @param value the cookie value
|
||||
* @return a configured ResponseCookie
|
||||
*/
|
||||
public ResponseCookie buildCookie(String cookieName, String value) {
|
||||
return buildCookieInternal(cookieName, value, cookieManager.getMaxAge());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a response cookie with a custom valid duration.
|
||||
*
|
||||
* @param cookieName the cookie name
|
||||
* @param value the cookie value
|
||||
* @param validDuration the cookie's max age
|
||||
* @return a configured ResponseCookie
|
||||
*/
|
||||
public ResponseCookie buildCookie(String cookieName, String value, Duration validDuration) {
|
||||
return buildCookieInternal(cookieName, value, validDuration);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ import com.onixbyte.deltaforceguide.domain.dto.DailyPasswordResponse;
|
||||
import com.onixbyte.deltaforceguide.manager.DailyPasswordManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Service for generating and caching daily rotation passwords.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Service
|
||||
public class DailyPasswordService {
|
||||
|
||||
@@ -13,6 +18,10 @@ public class DailyPasswordService {
|
||||
this.dailyPasswordManager = dailyPasswordManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the daily password for the current day.
|
||||
* @return the daily password response
|
||||
*/
|
||||
public DailyPasswordResponse getDailyPassword() {
|
||||
return dailyPasswordManager.getDailyPassword();
|
||||
}
|
||||
|
||||
@@ -11,9 +11,13 @@ import org.springframework.data.domain.Page;
|
||||
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 handling firearm business logic including CRUD operations and queries.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Service
|
||||
public class FirearmService {
|
||||
|
||||
@@ -23,7 +27,13 @@ public class FirearmService {
|
||||
this.firearmRepository = firearmRepository;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Queries firearms with optional type filter and pagination.
|
||||
*
|
||||
* @param type optional firearm type filter
|
||||
* @param pageable pagination parameters
|
||||
* @return a paginated response of firearm records
|
||||
*/
|
||||
public PageResponse<FirearmResponse> pageQuery(FirearmType type, Pageable pageable) {
|
||||
Page<Firearm> page = type == null
|
||||
? firearmRepository.findAll(pageable)
|
||||
@@ -32,13 +42,24 @@ public class FirearmService {
|
||||
return PageResponse.from(page.map(FirearmResponse::from));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds a firearm by its ID.
|
||||
*
|
||||
* @param id the firearm ID
|
||||
* @return the firearm response
|
||||
*/
|
||||
public FirearmResponse queryById(Long id) {
|
||||
return firearmRepository.findById(id)
|
||||
.map(FirearmResponse::from)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Firearm not found: " + id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new firearm from the provided request data.
|
||||
*
|
||||
* @param request the firearm creation request
|
||||
* @return the created firearm response
|
||||
*/
|
||||
public FirearmResponse addFirearm(FirearmRequest request) {
|
||||
var firearm = firearmRepository.save(Firearm.builder()
|
||||
.name(request.name())
|
||||
@@ -54,7 +75,13 @@ public class FirearmService {
|
||||
return FirearmResponse.from(firearm);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Updates an existing firearm identified by ID.
|
||||
*
|
||||
* @param id the firearm ID
|
||||
* @param request the updated firearm data
|
||||
* @return the updated firearm response
|
||||
*/
|
||||
public FirearmResponse updateFirearm(Long id, FirearmRequest request) {
|
||||
var firearm = firearmRepository.findById(id)
|
||||
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "Firearm not found: " + id));
|
||||
@@ -71,7 +98,11 @@ public class FirearmService {
|
||||
return FirearmResponse.from(firearmRepository.save(firearm));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Deletes a firearm by its ID.
|
||||
*
|
||||
* @param id the firearm ID to delete
|
||||
*/
|
||||
public void deleteFirearm(Long id) {
|
||||
Firearm firearm = firearmRepository.findById(id)
|
||||
.orElseThrow(() -> new BizException(HttpStatus.NOT_FOUND, "Firearm not found: " + id));
|
||||
|
||||
@@ -18,7 +18,6 @@ import org.springframework.data.domain.Page;
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -26,6 +25,11 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Service handling modification business logic including CRUD, batch operations, and tag filtering.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Service
|
||||
public class ModificationService {
|
||||
|
||||
@@ -46,7 +50,14 @@ public class ModificationService {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Queries modifications with optional firearm and tag filters.
|
||||
*
|
||||
* @param firearmId optional firearm ID filter
|
||||
* @param tags optional tag list filter
|
||||
* @param pageable pagination parameters
|
||||
* @return a paginated response of modification records
|
||||
*/
|
||||
public PageResponse<ModificationResponse> pageQuery(Long firearmId, List<String> tags, Pageable pageable) {
|
||||
String tagsJson = null;
|
||||
if (tags != null && !tags.isEmpty()) {
|
||||
@@ -67,27 +78,55 @@ public class ModificationService {
|
||||
return PageResponse.from(page.map(ModificationResponse::from));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds a modification by its ID.
|
||||
*
|
||||
* @param id the modification ID
|
||||
* @return the modification response
|
||||
*/
|
||||
public ModificationResponse queryById(Long id) {
|
||||
return modificationRepository.findById(id)
|
||||
.map(ModificationResponse::from)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds all unique tags across modifications, optionally scoped to a firearm.
|
||||
*
|
||||
* @param firearmId optional firearm ID to scope the tag search
|
||||
* @return list of unique tag strings
|
||||
*/
|
||||
public List<String> findAllTags(Long firearmId) {
|
||||
return modificationRepository.findAllTags(firearmId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new modification for a given firearm.
|
||||
*
|
||||
* @param request the modification creation request
|
||||
* @return the created modification response
|
||||
*/
|
||||
public ModificationResponse create(ModificationRequest request) {
|
||||
return modificationManager.create(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates multiple modifications in a single batch operation.
|
||||
*
|
||||
* @param requests list of modification creation requests
|
||||
* @return list of created modification responses
|
||||
*/
|
||||
public List<ModificationResponse> batchCreate(List<ModificationRequest> requests) {
|
||||
return modificationManager.batchCreate(requests);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Updates an existing modification identified by ID.
|
||||
*
|
||||
* @param id the modification ID
|
||||
* @param request the updated modification data
|
||||
* @return the updated modification response
|
||||
*/
|
||||
public ModificationResponse update(Long id, ModificationRequest request) {
|
||||
Modification modification = modificationRepository.findById(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id));
|
||||
@@ -106,14 +145,22 @@ public class ModificationService {
|
||||
return ModificationResponse.from(modificationRepository.save(modification));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Deletes a modification by its ID.
|
||||
*
|
||||
* @param id the modification ID to delete
|
||||
*/
|
||||
public void delete(Long id) {
|
||||
Modification modification = modificationRepository.findById(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Modification not found: " + id));
|
||||
modificationRepository.delete(modification);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Deletes multiple modifications in a single batch operation.
|
||||
*
|
||||
* @param ids list of modification IDs to delete
|
||||
*/
|
||||
public void batchDelete(List<Long> ids) {
|
||||
Set<Long> uniqueIds = new LinkedHashSet<>(ids);
|
||||
List<Modification> modifications = modificationRepository.findAllById(uniqueIds);
|
||||
|
||||
@@ -6,11 +6,15 @@ import com.onixbyte.deltaforceguide.manager.UserCredentialManager;
|
||||
import com.onixbyte.deltaforceguide.manager.UserManager;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Service for user account management and profile operations.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
@Service
|
||||
public class UserService {
|
||||
|
||||
@@ -22,29 +26,53 @@ public class UserService {
|
||||
this.userCredentialManager = userCredentialManager;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Retrieves all registered users.
|
||||
*
|
||||
* @return list of all users
|
||||
*/
|
||||
public List<User> findAll() {
|
||||
return userManager.findAll();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds a user by their ID.
|
||||
*
|
||||
* @param id the user ID
|
||||
* @return the user
|
||||
*/
|
||||
public User queryById(Long id) {
|
||||
return userManager.findById(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + id));
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds a user by their username.
|
||||
*
|
||||
* @param username the username to search for
|
||||
* @return the user
|
||||
*/
|
||||
public User queryByUsername(String username) {
|
||||
return userManager.findByUsername(username)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + username));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Creates a new user account.
|
||||
*
|
||||
* @param user the user entity to persist
|
||||
* @return the saved user entity
|
||||
*/
|
||||
public User create(User user) {
|
||||
return userManager.save(user);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Updates an existing user account.
|
||||
*
|
||||
* @param user the user entity with updated fields
|
||||
* @return the saved user entity
|
||||
*/
|
||||
public User update(User user) {
|
||||
if (user.getId() == null || userManager.findById(user.getId()).isEmpty()) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + user.getId());
|
||||
@@ -52,13 +80,24 @@ public class UserService {
|
||||
return userManager.save(user);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Finds all credentials associated with a user.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @return list of user credentials
|
||||
*/
|
||||
public List<UserCredential> findCredentials(Long userId) {
|
||||
ensureUserExists(userId);
|
||||
return userCredentialManager.findAllByUserId(userId);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
/**
|
||||
* Queries a specific credential for a user by provider.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @param provider the authentication provider
|
||||
* @return the matching credential
|
||||
*/
|
||||
public UserCredential queryCredential(Long userId, String provider) {
|
||||
ensureUserExists(userId);
|
||||
return userCredentialManager.findByUserIdAndProvider(userId, provider)
|
||||
@@ -67,7 +106,14 @@ public class UserService {
|
||||
"User credential not found: userId=" + userId + ", provider=" + provider));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Creates or updates a credential for a user and provider.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @param provider the authentication provider
|
||||
* @param credential the credential value
|
||||
* @return the saved credential
|
||||
*/
|
||||
public UserCredential upsertCredential(Long userId, String provider, String credential) {
|
||||
User user = ensureUserExists(userId);
|
||||
UserCredential userCredential = userCredentialManager.findByUserIdAndProvider(userId, provider)
|
||||
@@ -78,13 +124,22 @@ public class UserService {
|
||||
return userCredentialManager.save(userCredential);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Deletes a specific credential for a user by provider.
|
||||
*
|
||||
* @param userId the user ID
|
||||
* @param provider the authentication provider
|
||||
*/
|
||||
public void deleteCredential(Long userId, String provider) {
|
||||
ensureUserExists(userId);
|
||||
userCredentialManager.deleteByUserIdAndProvider(userId, provider);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
/**
|
||||
* Deletes a user and all associated credentials.
|
||||
*
|
||||
* @param id the user ID to delete
|
||||
*/
|
||||
public void deleteById(Long id) {
|
||||
ensureUserExists(id);
|
||||
userCredentialManager.deleteAllByUserId(id);
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.onixbyte.deltaforceguide.shared;
|
||||
|
||||
/**
|
||||
* Constants for HTTP cookie names used for authentication tokens.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class CookieName {
|
||||
|
||||
public static final String ACCESS_TOKEN = "AccessToken";
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.onixbyte.deltaforceguide.shared;
|
||||
|
||||
/**
|
||||
* Constants for supported authentication provider identifiers.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class CredentialProvider {
|
||||
|
||||
public static final String LOCAL = "LOCAL";
|
||||
|
||||
@@ -13,6 +13,12 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
||||
/**
|
||||
* Shared Jackson {@link com.fasterxml.jackson.databind.Module} instances for custom date/time
|
||||
* serialisation and deserialisation across the application.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class JacksonModules {
|
||||
|
||||
public static final SimpleModule DATE_TIME_MODULE = initialiseDateTimeModule();
|
||||
|
||||
@@ -4,6 +4,12 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
|
||||
/**
|
||||
* Singleton serialiser for Redis cache operations using
|
||||
* {@link GenericJackson2JsonRedisSerializer} with JavaTime module support.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class JacksonRedisSerialiser {
|
||||
|
||||
public static final GenericJackson2JsonRedisSerializer INSTANCE = initialiseSerializer();
|
||||
|
||||
@@ -4,8 +4,19 @@ import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
|
||||
/**
|
||||
* Utility class for date and time operations using system-default time zone.
|
||||
*
|
||||
* @author zihluwang
|
||||
*/
|
||||
public class DateTimeUtil {
|
||||
|
||||
/**
|
||||
* Convert a {@link LocalDateTime} to an {@link Instant} using the system-default time zone.
|
||||
*
|
||||
* @param ldt the local date-time to convert
|
||||
* @return the corresponding instant
|
||||
*/
|
||||
public static Instant asInstant(LocalDateTime ldt) {
|
||||
return ldt.atZone(ZoneId.systemDefault())
|
||||
.toInstant();
|
||||
|
||||
Reference in New Issue
Block a user