feat(mod): add status and creator tracking
- Introduce ModificationStatus enum with integer codes and a JPA converter - Record the creating user on new modifications - Rebuild the modification table with status, creator and audit columns - Simplify user_role to a single-column primary key
This commit is contained in:
11 files changed
+276
-150
No files matched your search
@@ -4,7 +4,9 @@ import com.onixbyte.deltaforceguide.domain.dto.ModificationBatchCreateRequest;
|
|||||||
import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest;
|
import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest;
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse;
|
import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse;
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.PageResponse;
|
import com.onixbyte.deltaforceguide.domain.dto.PageResponse;
|
||||||
|
import com.onixbyte.deltaforceguide.domain.entity.User;
|
||||||
import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth;
|
import com.onixbyte.deltaforceguide.security.annotation.RequiresAuth;
|
||||||
|
import com.onixbyte.deltaforceguide.security.resolver.CurrentUser;
|
||||||
import com.onixbyte.deltaforceguide.service.ModificationService;
|
import com.onixbyte.deltaforceguide.service.ModificationService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -66,15 +68,21 @@ public class ModificationController {
|
|||||||
@RequiresAuth
|
@RequiresAuth
|
||||||
@Operation(description = "创建改装")
|
@Operation(description = "创建改装")
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ModificationResponse create(@Valid @RequestBody ModificationRequest request) {
|
public ModificationResponse create(
|
||||||
return modificationService.create(request);
|
@CurrentUser User user,
|
||||||
|
@Valid @RequestBody ModificationRequest request
|
||||||
|
) {
|
||||||
|
return modificationService.create(request, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresAuth
|
@RequiresAuth
|
||||||
@Operation(description = "批量创建改装")
|
@Operation(description = "批量创建改装")
|
||||||
@PostMapping("/batch")
|
@PostMapping("/batch")
|
||||||
public List<ModificationResponse> batchCreate(@Valid @RequestBody ModificationBatchCreateRequest request) {
|
public List<ModificationResponse> batchCreate(
|
||||||
return modificationService.batchCreate(request.modifications());
|
@CurrentUser User user,
|
||||||
|
@Valid @RequestBody ModificationBatchCreateRequest request
|
||||||
|
) {
|
||||||
|
return modificationService.batchCreate(request.modifications(), user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresAuth
|
@RequiresAuth
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.domain.converter;
|
||||||
|
|
||||||
|
import com.onixbyte.deltaforceguide.enumeration.ModificationStatus;
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
import jakarta.persistence.Converter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPA attribute converter that maps {@link ModificationStatus} enum to/from its integer database representation.
|
||||||
|
*
|
||||||
|
* @author zihluwang
|
||||||
|
*/
|
||||||
|
@Converter
|
||||||
|
public class ModificationStatusConverter implements AttributeConverter<ModificationStatus, Integer> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer convertToDatabaseColumn(ModificationStatus attribute) {
|
||||||
|
return attribute == null ? null : attribute.getCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ModificationStatus convertToEntityAttribute(Integer dbData) {
|
||||||
|
return ModificationStatus.fromCode(dbData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.onixbyte.deltaforceguide.domain.entity;
|
package com.onixbyte.deltaforceguide.domain.entity;
|
||||||
|
|
||||||
|
import com.onixbyte.deltaforceguide.domain.converter.ModificationStatusConverter;
|
||||||
|
import com.onixbyte.deltaforceguide.enumeration.ModificationStatus;
|
||||||
import io.hypersistence.utils.hibernate.type.json.JsonType;
|
import io.hypersistence.utils.hibernate.type.json.JsonType;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import org.hibernate.annotations.Type;
|
import org.hibernate.annotations.Type;
|
||||||
|
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity representing a firearm modification or build configuration.
|
* Entity representing a firearm modification or build configuration.
|
||||||
@@ -14,10 +18,10 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@Table(
|
@Table(
|
||||||
name = "modification",
|
name = "modification",
|
||||||
indexes = {
|
indexes = {
|
||||||
@Index(name = "idx_modification_firearm_id", columnList = "firearm_id")
|
@Index(name = "idx_modification_firearm_id", columnList = "firearm_id")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
public class Modification {
|
public class Modification {
|
||||||
|
|
||||||
@@ -25,6 +29,10 @@ public class Modification {
|
|||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "user_id")
|
||||||
|
private User user;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
@JoinColumn(name = "firearm_id", nullable = false, foreignKey = @ForeignKey(name = "fk_modification_firearm"))
|
@JoinColumn(name = "firearm_id", nullable = false, foreignKey = @ForeignKey(name = "fk_modification_firearm"))
|
||||||
private Firearm firearm;
|
private Firearm firearm;
|
||||||
@@ -36,11 +44,11 @@ public class Modification {
|
|||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
@Type(JsonType.class)
|
@Type(JsonType.class)
|
||||||
@Column(name = "tags", columnDefinition = "jsonb")
|
@Column(name = "tags", columnDefinition = "jsonb", nullable = false)
|
||||||
private List<String> tags = new ArrayList<>();
|
private List<String> tags = new ArrayList<>();
|
||||||
|
|
||||||
@Type(JsonType.class)
|
@Type(JsonType.class)
|
||||||
@Column(name = "accessories", columnDefinition = "jsonb")
|
@Column(name = "accessories", columnDefinition = "jsonb", nullable = false)
|
||||||
private List<Accessory> accessories = new ArrayList<>();
|
private List<Accessory> accessories = new ArrayList<>();
|
||||||
|
|
||||||
@Column(name = "note", columnDefinition = "TEXT")
|
@Column(name = "note", columnDefinition = "TEXT")
|
||||||
@@ -52,6 +60,16 @@ public class Modification {
|
|||||||
@Column(name = "video_url", length = 512)
|
@Column(name = "video_url", length = 512)
|
||||||
private String videoUrl;
|
private String videoUrl;
|
||||||
|
|
||||||
|
@Column(name = "status", nullable = false)
|
||||||
|
@Convert(converter = ModificationStatusConverter.class)
|
||||||
|
private ModificationStatus status = ModificationStatus.DRAFT;
|
||||||
|
|
||||||
|
@Column(name = "create_by")
|
||||||
|
private Long createBy;
|
||||||
|
|
||||||
|
@Column(name = "create_time", nullable = false)
|
||||||
|
private OffsetDateTime createTime;
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -132,6 +150,38 @@ public class Modification {
|
|||||||
this.videoUrl = videoUrl;
|
this.videoUrl = videoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(User user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCreateBy() {
|
||||||
|
return createBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateBy(Long createBy) {
|
||||||
|
this.createBy = createBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OffsetDateTime getCreateTime() {
|
||||||
|
return createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreateTime(OffsetDateTime createTime) {
|
||||||
|
this.createTime = createTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModificationStatus getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(ModificationStatus status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
return new Builder();
|
return new Builder();
|
||||||
}
|
}
|
||||||
@@ -139,6 +189,7 @@ public class Modification {
|
|||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
private User user;
|
||||||
private Firearm firearm;
|
private Firearm firearm;
|
||||||
private String name;
|
private String name;
|
||||||
private String code;
|
private String code;
|
||||||
@@ -147,12 +198,20 @@ public class Modification {
|
|||||||
private String note;
|
private String note;
|
||||||
private String author;
|
private String author;
|
||||||
private String videoUrl;
|
private String videoUrl;
|
||||||
|
private Long createBy;
|
||||||
|
private OffsetDateTime createTime;
|
||||||
|
private ModificationStatus status;
|
||||||
|
|
||||||
public Builder id(Long id) {
|
public Builder id(Long id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder user(User user) {
|
||||||
|
this.user = user;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder firearm(Firearm firearm) {
|
public Builder firearm(Firearm firearm) {
|
||||||
this.firearm = firearm;
|
this.firearm = firearm;
|
||||||
return this;
|
return this;
|
||||||
@@ -193,17 +252,36 @@ public class Modification {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder createBy(Long createBy) {
|
||||||
|
this.createBy = createBy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder createTime(OffsetDateTime createTime) {
|
||||||
|
this.createTime = createTime;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder status(ModificationStatus status) {
|
||||||
|
this.status = status;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Modification build() {
|
public Modification build() {
|
||||||
Modification modification = new Modification();
|
Modification modification = new Modification();
|
||||||
modification.id = this.id;
|
modification.id = this.id;
|
||||||
|
modification.user = this.user;
|
||||||
modification.firearm = this.firearm;
|
modification.firearm = this.firearm;
|
||||||
modification.name = this.name;
|
modification.name = this.name;
|
||||||
modification.code = this.code;
|
modification.code = this.code;
|
||||||
modification.tags = this.tags == null ? new ArrayList<>() : this.tags;
|
modification.tags = Optional.ofNullable(this.tags).orElseGet(ArrayList::new);
|
||||||
modification.accessories = this.accessories == null ? new ArrayList<>() : this.accessories;
|
modification.accessories = Optional.ofNullable(this.accessories).orElseGet(ArrayList::new);
|
||||||
modification.note = this.note;
|
modification.note = this.note;
|
||||||
modification.author = this.author;
|
modification.author = this.author;
|
||||||
modification.videoUrl = this.videoUrl;
|
modification.videoUrl = this.videoUrl;
|
||||||
|
modification.createBy = this.createBy;
|
||||||
|
modification.createTime = Optional.ofNullable(this.createTime).orElseGet(OffsetDateTime::now);
|
||||||
|
modification.status = Optional.ofNullable(this.status).orElse(ModificationStatus.DRAFT);
|
||||||
return modification;
|
return modification;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
package com.onixbyte.deltaforceguide.domain.entity;
|
package com.onixbyte.deltaforceguide.domain.entity;
|
||||||
|
|
||||||
import jakarta.persistence.AttributeOverride;
|
|
||||||
import jakarta.persistence.AttributeOverrides;
|
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.EmbeddedId;
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.FetchType;
|
import jakarta.persistence.FetchType;
|
||||||
import jakarta.persistence.ForeignKey;
|
import jakarta.persistence.ForeignKey;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.MapsId;
|
import jakarta.persistence.MapsId;
|
||||||
@@ -14,6 +12,8 @@ import jakarta.persistence.Table;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity representing a role assignment linking a user to a security role.
|
* Entity representing a role assignment linking a user to a security role.
|
||||||
|
* <p>
|
||||||
|
* A user owns at most one role assignment, so the user ID is the primary key.
|
||||||
*
|
*
|
||||||
* @author zihluwang
|
* @author zihluwang
|
||||||
*/
|
*/
|
||||||
@@ -21,24 +21,24 @@ import jakarta.persistence.Table;
|
|||||||
@Table(name = "user_role")
|
@Table(name = "user_role")
|
||||||
public class UserRole {
|
public class UserRole {
|
||||||
|
|
||||||
@EmbeddedId
|
@Id
|
||||||
@AttributeOverrides({
|
@Column(name = "user_id")
|
||||||
@AttributeOverride(name = "userId", column = @Column(name = "user_id")),
|
private Long userId;
|
||||||
@AttributeOverride(name = "role", column = @Column(name = "role"))
|
|
||||||
})
|
|
||||||
private UserRoleId id = new UserRoleId();
|
|
||||||
|
|
||||||
@MapsId("userId")
|
@MapsId("userId")
|
||||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||||
@JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "fk_user_role_user"))
|
@JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "fk_user_role_user"))
|
||||||
private User user;
|
private User user;
|
||||||
|
|
||||||
public UserRoleId getId() {
|
@Column(name = "role", nullable = false, length = 32)
|
||||||
return id;
|
private String role;
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(UserRoleId id) {
|
public void setUserId(Long userId) {
|
||||||
this.id = id;
|
this.userId = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public User getUser() {
|
public User getUser() {
|
||||||
@@ -47,32 +47,17 @@ public class UserRole {
|
|||||||
|
|
||||||
public void setUser(User user) {
|
public void setUser(User user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
if (this.id == null) {
|
if (user != null) {
|
||||||
this.id = new UserRoleId();
|
this.userId = user.getId();
|
||||||
}
|
}
|
||||||
this.id.setUserId(user == null ? null : user.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long getUserId() {
|
|
||||||
return id == null ? null : id.getUserId();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserId(Long userId) {
|
|
||||||
if (this.id == null) {
|
|
||||||
this.id = new UserRoleId();
|
|
||||||
}
|
|
||||||
this.id.setUserId(userId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRole() {
|
public String getRole() {
|
||||||
return id == null ? null : id.getRole();
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRole(String role) {
|
public void setRole(String role) {
|
||||||
if (this.id == null) {
|
this.role = role;
|
||||||
this.id = new UserRoleId();
|
|
||||||
}
|
|
||||||
this.id.setRole(role);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder builder() {
|
public static Builder builder() {
|
||||||
@@ -81,16 +66,10 @@ public class UserRole {
|
|||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private UserRoleId id;
|
|
||||||
private User user;
|
private User user;
|
||||||
private Long userId;
|
private Long userId;
|
||||||
private String role;
|
private String role;
|
||||||
|
|
||||||
public Builder id(UserRoleId id) {
|
|
||||||
this.id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder user(User user) {
|
public Builder user(User user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
return this;
|
return this;
|
||||||
@@ -108,16 +87,13 @@ public class UserRole {
|
|||||||
|
|
||||||
public UserRole build() {
|
public UserRole build() {
|
||||||
UserRole userRole = new UserRole();
|
UserRole userRole = new UserRole();
|
||||||
userRole.id = this.id == null ? new UserRoleId() : this.id;
|
|
||||||
userRole.user = this.user;
|
userRole.user = this.user;
|
||||||
|
userRole.role = this.role;
|
||||||
if (this.user != null) {
|
if (this.user != null) {
|
||||||
userRole.id.setUserId(this.user.getId());
|
userRole.userId = this.user.getId();
|
||||||
}
|
}
|
||||||
if (this.userId != null) {
|
if (this.userId != null) {
|
||||||
userRole.id.setUserId(this.userId);
|
userRole.userId = this.userId;
|
||||||
}
|
|
||||||
if (this.role != null) {
|
|
||||||
userRole.id.setRole(this.role);
|
|
||||||
}
|
}
|
||||||
return userRole;
|
return userRole;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package com.onixbyte.deltaforceguide.domain.entity;
|
|
||||||
|
|
||||||
import jakarta.persistence.Embeddable;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Composite key for the UserRole entity, combining user ID and role name.
|
|
||||||
*
|
|
||||||
* @author zihluwang
|
|
||||||
*/
|
|
||||||
@Embeddable
|
|
||||||
public class UserRoleId implements Serializable {
|
|
||||||
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
private String role;
|
|
||||||
|
|
||||||
public Long getUserId() {
|
|
||||||
return userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserId(Long userId) {
|
|
||||||
this.userId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRole() {
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRole(String role) {
|
|
||||||
this.role = role;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
UserRoleId that = (UserRoleId) o;
|
|
||||||
return Objects.equals(userId, that.userId) && Objects.equals(role, that.role);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(userId, role);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
|
|
||||||
private Long userId;
|
|
||||||
private String role;
|
|
||||||
|
|
||||||
public Builder userId(Long userId) {
|
|
||||||
this.userId = userId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder role(String role) {
|
|
||||||
this.role = role;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserRoleId build() {
|
|
||||||
UserRoleId id = new UserRoleId();
|
|
||||||
id.userId = this.userId;
|
|
||||||
id.role = this.role;
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.onixbyte.deltaforceguide.enumeration;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of modification statuses.
|
||||||
|
* <p>
|
||||||
|
* Each status is associated with an integer code used for database persistence.
|
||||||
|
*
|
||||||
|
* @author zihluwang
|
||||||
|
*/
|
||||||
|
public enum ModificationStatus {
|
||||||
|
|
||||||
|
DRAFT(0),
|
||||||
|
PENDING_APPROVAL(1),
|
||||||
|
PUBLISHED(2);
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
ModificationStatus(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Map<Integer, ModificationStatus> CODE_MAP = Arrays.stream(values())
|
||||||
|
.collect(Collectors.toMap(ModificationStatus::getCode, Function.identity()));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a ModificationStatus from its integer code.
|
||||||
|
*
|
||||||
|
* @param code the integer code, may be null
|
||||||
|
* @return the corresponding ModificationStatus, or null if the code is null
|
||||||
|
* @throws IllegalArgumentException if the code does not match any known status
|
||||||
|
*/
|
||||||
|
public static ModificationStatus fromCode(Integer code) {
|
||||||
|
return CODE_MAP.get(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import com.onixbyte.deltaforceguide.domain.entity.Accessory;
|
|||||||
import com.onixbyte.deltaforceguide.domain.entity.Firearm;
|
import com.onixbyte.deltaforceguide.domain.entity.Firearm;
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.Modification;
|
import com.onixbyte.deltaforceguide.domain.entity.Modification;
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.Tuning;
|
import com.onixbyte.deltaforceguide.domain.entity.Tuning;
|
||||||
|
import com.onixbyte.deltaforceguide.domain.entity.User;
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.AccessoryRequest;
|
import com.onixbyte.deltaforceguide.domain.dto.AccessoryRequest;
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest;
|
import com.onixbyte.deltaforceguide.domain.dto.ModificationRequest;
|
||||||
import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse;
|
import com.onixbyte.deltaforceguide.domain.dto.ModificationResponse;
|
||||||
@@ -36,16 +37,16 @@ public class ModificationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public ModificationResponse create(ModificationRequest request) {
|
public ModificationResponse create(ModificationRequest request, User user) {
|
||||||
var firearm = firearmRepository.findById(request.firearmId())
|
var firearm = firearmRepository.findById(request.firearmId())
|
||||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
|
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
|
||||||
"Firearm not found: " + request.firearmId()));
|
"Firearm not found: " + request.firearmId()));
|
||||||
var modification = toEntity(request, firearm);
|
var modification = toEntity(request, firearm, user);
|
||||||
return ModificationResponse.from(modificationRepository.save(modification));
|
return ModificationResponse.from(modificationRepository.save(modification));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public List<ModificationResponse> batchCreate(List<ModificationRequest> requests) {
|
public List<ModificationResponse> batchCreate(List<ModificationRequest> requests, User user) {
|
||||||
var firearmIds = requests.stream()
|
var firearmIds = requests.stream()
|
||||||
.map(ModificationRequest::firearmId)
|
.map(ModificationRequest::firearmId)
|
||||||
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
||||||
@@ -63,7 +64,7 @@ public class ModificationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var modifications = requests.stream()
|
var modifications = requests.stream()
|
||||||
.map(req -> toEntity(req, firearmMap.get(req.firearmId())))
|
.map(req -> toEntity(req, firearmMap.get(req.firearmId()), user))
|
||||||
.toList();
|
.toList();
|
||||||
return modificationRepository.saveAll(modifications)
|
return modificationRepository.saveAll(modifications)
|
||||||
.stream()
|
.stream()
|
||||||
@@ -86,7 +87,7 @@ public class ModificationManager {
|
|||||||
return matches.getFirst().getId();
|
return matches.getFirst().getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Modification toEntity(ModificationRequest request, Firearm firearm) {
|
private Modification toEntity(ModificationRequest request, Firearm firearm, User user) {
|
||||||
return Modification.builder()
|
return Modification.builder()
|
||||||
.firearm(firearm)
|
.firearm(firearm)
|
||||||
.name(request.name())
|
.name(request.name())
|
||||||
@@ -96,6 +97,7 @@ public class ModificationManager {
|
|||||||
.note(request.note())
|
.note(request.note())
|
||||||
.author(request.author())
|
.author(request.author())
|
||||||
.videoUrl(request.videoUrl())
|
.videoUrl(request.videoUrl())
|
||||||
|
.createBy(user == null ? null : user.getId())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.onixbyte.deltaforceguide.repository;
|
package com.onixbyte.deltaforceguide.repository;
|
||||||
|
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.UserRole;
|
import com.onixbyte.deltaforceguide.domain.entity.UserRole;
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.UserRoleId;
|
|
||||||
import org.springframework.data.jpa.repository.*;
|
import org.springframework.data.jpa.repository.*;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -14,7 +13,7 @@ import java.util.List;
|
|||||||
* @author zihluwang
|
* @author zihluwang
|
||||||
*/
|
*/
|
||||||
@Repository
|
@Repository
|
||||||
public interface UserRoleRepository extends JpaRepository<UserRole, UserRoleId>, JpaSpecificationExecutor<UserRole> {
|
public interface UserRoleRepository extends JpaRepository<UserRole, Long>, JpaSpecificationExecutor<UserRole> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all roles assigned to a given user.
|
* Find all roles assigned to a given user.
|
||||||
@@ -41,7 +40,7 @@ public interface UserRoleRepository extends JpaRepository<UserRole, UserRoleId>,
|
|||||||
select count(ur) > 0
|
select count(ur) > 0
|
||||||
from UserRole ur
|
from UserRole ur
|
||||||
where ur.user.id = :userId
|
where ur.user.id = :userId
|
||||||
and ur.id.role = :role
|
and ur.role = :role
|
||||||
""")
|
""")
|
||||||
boolean existsByUserIdAndRole(@Param("userId") Long userId, @Param("role") String role);
|
boolean existsByUserIdAndRole(@Param("userId") Long userId, @Param("role") String role);
|
||||||
|
|
||||||
@@ -55,7 +54,7 @@ public interface UserRoleRepository extends JpaRepository<UserRole, UserRoleId>,
|
|||||||
@Query("""
|
@Query("""
|
||||||
delete from UserRole ur
|
delete from UserRole ur
|
||||||
where ur.user.id = :userId
|
where ur.user.id = :userId
|
||||||
and ur.id.role = :role
|
and ur.role = :role
|
||||||
""")
|
""")
|
||||||
void deleteByUserIdAndRole(@Param("userId") Long userId, @Param("role") String role);
|
void deleteByUserIdAndRole(@Param("userId") Long userId, @Param("role") String role);
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import com.onixbyte.deltaforceguide.domain.dto.TuningRequest;
|
|||||||
import com.onixbyte.deltaforceguide.domain.entity.Accessory;
|
import com.onixbyte.deltaforceguide.domain.entity.Accessory;
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.Firearm;
|
import com.onixbyte.deltaforceguide.domain.entity.Firearm;
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.Modification;
|
import com.onixbyte.deltaforceguide.domain.entity.Modification;
|
||||||
|
import com.onixbyte.deltaforceguide.domain.entity.User;
|
||||||
import com.onixbyte.deltaforceguide.domain.entity.Tuning;
|
import com.onixbyte.deltaforceguide.domain.entity.Tuning;
|
||||||
import com.onixbyte.deltaforceguide.manager.ModificationManager;
|
import com.onixbyte.deltaforceguide.manager.ModificationManager;
|
||||||
import com.onixbyte.deltaforceguide.repository.FirearmRepository;
|
import com.onixbyte.deltaforceguide.repository.FirearmRepository;
|
||||||
@@ -104,20 +105,22 @@ public class ModificationService {
|
|||||||
* Creates a new modification for a given firearm.
|
* Creates a new modification for a given firearm.
|
||||||
*
|
*
|
||||||
* @param request the modification creation request
|
* @param request the modification creation request
|
||||||
|
* @param user the authenticated user creating the modification
|
||||||
* @return the created modification response
|
* @return the created modification response
|
||||||
*/
|
*/
|
||||||
public ModificationResponse create(ModificationRequest request) {
|
public ModificationResponse create(ModificationRequest request, User user) {
|
||||||
return modificationManager.create(request);
|
return modificationManager.create(request, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates multiple modifications in a single batch operation.
|
* Creates multiple modifications in a single batch operation.
|
||||||
*
|
*
|
||||||
* @param requests list of modification creation requests
|
* @param requests list of modification creation requests
|
||||||
|
* @param user the authenticated user creating the modifications
|
||||||
* @return list of created modification responses
|
* @return list of created modification responses
|
||||||
*/
|
*/
|
||||||
public List<ModificationResponse> batchCreate(List<ModificationRequest> requests) {
|
public List<ModificationResponse> batchCreate(List<ModificationRequest> requests, User user) {
|
||||||
return modificationManager.batchCreate(requests);
|
return modificationManager.batchCreate(requests, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public class WebhookService {
|
|||||||
private void processSingle(Long issueNumber, Map<String, Object> data) {
|
private void processSingle(Long issueNumber, Map<String, Object> data) {
|
||||||
var request = mapToRequest(data);
|
var request = mapToRequest(data);
|
||||||
log.info("Creating modification from issue #{}: name={}", issueNumber, request.name());
|
log.info("Creating modification from issue #{}: name={}", issueNumber, request.name());
|
||||||
modificationManager.create(request);
|
modificationManager.create(request, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -102,7 +102,7 @@ public class WebhookService {
|
|||||||
.map(this::mapToRequest)
|
.map(this::mapToRequest)
|
||||||
.toList();
|
.toList();
|
||||||
log.info("Batch creating {} modifications from issue #{}", requests.size(), issueNumber);
|
log.info("Batch creating {} modifications from issue #{}", requests.size(), issueNumber);
|
||||||
modificationManager.batchCreate(requests);
|
modificationManager.batchCreate(requests, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModificationRequest mapToRequest(Map<String, Object> data) {
|
private ModificationRequest mapToRequest(Map<String, Object> data) {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS modification_new
|
||||||
|
(
|
||||||
|
id BIGSERIAL NOT NULL PRIMARY KEY,
|
||||||
|
user_id BIGINT NULL REFERENCES app_user (id),
|
||||||
|
firearm_id BIGINT NOT NULL REFERENCES firearm (id),
|
||||||
|
name VARCHAR(64) NOT NULL,
|
||||||
|
code VARCHAR(64) NOT NULL,
|
||||||
|
tags JSONB NOT NULL DEFAULT '[]'::JSONB,
|
||||||
|
note TEXT NULL,
|
||||||
|
author VARCHAR(64) NULL,
|
||||||
|
video_url VARCHAR(512) NULL,
|
||||||
|
accessories JSONB NOT NULL DEFAULT '[]'::JSONB,
|
||||||
|
status INTEGER NOT NULL DEFAULT 0,
|
||||||
|
create_by BIGINT NULL,
|
||||||
|
create_time TIMESTAMPTZ NOT NULL,
|
||||||
|
CONSTRAINT tags_must_be_string_array CHECK (
|
||||||
|
jsonb_typeof(tags) = 'array' AND
|
||||||
|
NOT jsonb_path_exists(tags, '$[*] ? (@.type() != "string")')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
DO
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
v_super_user_id BIGINT;
|
||||||
|
BEGIN
|
||||||
|
-- Added explicit table reference and LIMIT 1 to prevent multiple-row errors
|
||||||
|
SELECT app_user.id
|
||||||
|
INTO v_super_user_id
|
||||||
|
FROM app_user
|
||||||
|
LEFT JOIN user_role ur ON app_user.id = ur.user_id
|
||||||
|
WHERE ur.role = 'SUPER_USER'
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
-- Explicitly check for NULL rather than evaluating the integer
|
||||||
|
IF v_super_user_id IS NOT NULL THEN
|
||||||
|
INSERT INTO modification_new(id, user_id, firearm_id, name, code, tags, note, author,
|
||||||
|
video_url, accessories, status, create_by, create_time)
|
||||||
|
SELECT m.id,
|
||||||
|
NULL,
|
||||||
|
m.firearm_id,
|
||||||
|
m.name,
|
||||||
|
m.code,
|
||||||
|
COALESCE(m.tags, '[]'::JSONB),
|
||||||
|
m.note,
|
||||||
|
m.author,
|
||||||
|
m.video_url,
|
||||||
|
COALESCE(m.accessories, '[]'::JSONB),
|
||||||
|
2,
|
||||||
|
v_super_user_id,
|
||||||
|
now()
|
||||||
|
FROM modification m;
|
||||||
|
ELSE
|
||||||
|
-- Fixed RAISE NOTICE parameter formatting
|
||||||
|
RAISE NOTICE 'Super user not found. Please execute the manual SQL commands after adding a super user.';
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
ALTER INDEX idx_modification_firearm_id RENAME TO idx_modification_lagacy_firearm_id;
|
||||||
|
|
||||||
|
CREATE INDEX idx_modification_firearm_id ON modification_new (firearm_id);
|
||||||
|
|
||||||
|
ALTER TABLE modification
|
||||||
|
RENAME TO modification_legacy;
|
||||||
|
ALTER TABLE modification_new
|
||||||
|
RENAME TO modification;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
Reference in new issue
Block a user