dev add violation notion
Test Workflow / test (push) Successful in 4s

This commit is contained in:
vladp
2026-03-17 22:23:19 +07:00
parent 2d8f904bfd
commit 921d439e0a
7 changed files with 74 additions and 92 deletions
@@ -5,8 +5,8 @@ import lombok.Data;
@Data @Data
public class ViolationNotionRequest { public class ViolationNotionRequest {
private String token; private String token;
private String fileId;
private String message;
private String action; private String action;
private Long violationId;
private Long notionId; private Long notionId;
private String message;
} }
@@ -1,5 +1,4 @@
package ru.soune.nocopy.dto.violation; package ru.soune.nocopy.dto.violation;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -12,8 +11,7 @@ public class ViolationNotionResponse {
private LocalDateTime createdAt; private LocalDateTime createdAt;
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
private UserInfo user; private UserInfo user;
@JsonIgnore private ViolationInfo violation;
private FileInfo file;
@Data @Data
@Builder @Builder
@@ -25,8 +23,9 @@ public class ViolationNotionResponse {
@Data @Data
@Builder @Builder
public static class FileInfo { public static class ViolationInfo {
private String id; private Long id;
private String originalFileName; private String url;
private String host;
} }
} }
@@ -8,8 +8,9 @@ import java.util.List;
@Data @Data
@Builder @Builder
public class ViolationNotionsListResponse { public class ViolationNotionsListResponse {
private String fileId; private Long violationId;
private String fileName; private String url;
private String host;
private int totalCount; private int totalCount;
private List<ViolationNotionResponse> notions; private List<ViolationNotionResponse> notions;
} }
@@ -34,6 +34,11 @@ public class ViolationNotion {
@JsonIgnore @JsonIgnore
private FileEntity file; private FileEntity file;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "violation_id", nullable = false)
@JsonIgnore
private Violation violation;
@CreatedDate @CreatedDate
@Column(name = "created_at", updatable = false, nullable = false) @Column(name = "created_at", updatable = false, nullable = false)
private LocalDateTime createdAt; private LocalDateTime createdAt;
@@ -61,19 +61,14 @@ public class ViolationNotionHandler implements RequestHandler {
switch (action) { switch (action) {
case "get_notions": case "get_notions":
return handleGetNotions(request, notionRequest, userId); return handleGetNotions(request, notionRequest, userId);
case "get_notion": case "get_notion":
return handleGetNotion(request, notionRequest, userId); return handleGetNotion(request, notionRequest, userId);
case "add_notion": case "add_notion":
return handleAddNotion(request, notionRequest, userId); return handleAddNotion(request, notionRequest, userId);
case "update_notion": case "update_notion":
return handleUpdateNotion(request, notionRequest, userId); return handleUpdateNotion(request, notionRequest, userId);
case "delete_notion": case "delete_notion":
return handleDeleteNotion(request, notionRequest, userId); return handleDeleteNotion(request, notionRequest, userId);
default: default:
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
@@ -83,21 +78,21 @@ public class ViolationNotionHandler implements RequestHandler {
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.error("Validation error in ViolationNotionHandler: {}", e.getMessage()); log.error("Validation error: {}", e.getMessage());
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.VALIDATION_ERROR.getCode(), MessageCode.VALIDATION_ERROR.getCode(),
e.getMessage(), e.getMessage(),
null); null);
} catch (SecurityException e) { } catch (SecurityException e) {
log.error("Security error in ViolationNotionHandler: {}", e.getMessage()); log.error("Security error: {}", e.getMessage());
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.ACCESS_DENIED.getCode(), MessageCode.ACCESS_DENIED.getCode(),
e.getMessage(), e.getMessage(),
null); null);
} catch (Exception e) { } catch (Exception e) {
log.error("Unexpected error in ViolationNotionHandler", e); log.error("Unexpected error", e);
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.INTERNAL_ERROR.getCode(), MessageCode.INTERNAL_ERROR.getCode(),
@@ -107,26 +102,25 @@ public class ViolationNotionHandler implements RequestHandler {
} }
private BaseResponse handleGetNotions(BaseRequest request, ViolationNotionRequest notionRequest, Long userId) { private BaseResponse handleGetNotions(BaseRequest request, ViolationNotionRequest notionRequest, Long userId) {
if (notionRequest.getFileId() == null) { if (notionRequest.getViolationId() == null) {
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.INVALID_JSON_BODY.getCode(), MessageCode.INVALID_JSON_BODY.getCode(),
"FileId is required for get_notions action", "ViolationId is required for get_notions action",
null); null);
} }
try { try {
ViolationNotionsListResponse notions = notionService.getNotionsByFileId( ViolationNotionsListResponse notions = notionService.getNotionsByViolationId(
notionRequest.getFileId(), userId); notionRequest.getViolationId(), userId);
return BaseResponse.builder() return BaseResponse.builder()
.msgId(request.getMsgId()) .msgId(request.getMsgId())
.messageCode(MessageCode.SUCCESS.getCode())
.messageBody(notions) .messageBody(notions)
.build(); .build();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.NOTION_NOT_FOUND.getCode(),
e.getMessage(), e.getMessage(),
null); null);
} }
@@ -136,7 +130,7 @@ public class ViolationNotionHandler implements RequestHandler {
if (notionRequest.getNotionId() == null) { if (notionRequest.getNotionId() == null) {
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.NOTION_NOT_FOUND.getCode(), MessageCode.INVALID_JSON_BODY.getCode(),
"NotionId is required for get_notion action", "NotionId is required for get_notion action",
null); null);
} }
@@ -146,7 +140,6 @@ public class ViolationNotionHandler implements RequestHandler {
notionRequest.getNotionId(), userId); notionRequest.getNotionId(), userId);
return BaseResponse.builder() return BaseResponse.builder()
.msgId(request.getMsgId()) .msgId(request.getMsgId())
.messageCode(MessageCode.SUCCESS.getCode())
.messageBody(notion) .messageBody(notion)
.build(); .build();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -159,28 +152,27 @@ public class ViolationNotionHandler implements RequestHandler {
} }
private BaseResponse handleAddNotion(BaseRequest request, ViolationNotionRequest notionRequest, Long userId) { private BaseResponse handleAddNotion(BaseRequest request, ViolationNotionRequest notionRequest, Long userId) {
if (notionRequest.getFileId() == null) { if (notionRequest.getViolationId() == null) {
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.INVALID_JSON_BODY.getCode(),
"FileId is required for add_notion action", "ViolationId is required for add_notion action",
null); null);
} }
if (notionRequest.getMessage() == null || notionRequest.getMessage().trim().isEmpty()) { if (notionRequest.getMessage() == null || notionRequest.getMessage().trim().isEmpty()) {
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.MESSAGE_IS_REQUIRED_FOR_NOTION.getCode(), MessageCode.INVALID_JSON_BODY.getCode(),
"Message is required for add_notion action", "Message is required for add_notion action",
null); null);
} }
try { try {
ViolationNotionResponse created = notionService.createNotion( ViolationNotionResponse created = notionService.createNotion(
notionRequest.getFileId(), userId, notionRequest.getMessage()); notionRequest.getViolationId(), userId, notionRequest.getMessage());
return BaseResponse.builder() return BaseResponse.builder()
.msgId(request.getMsgId()) .msgId(request.getMsgId())
.messageCode(MessageCode.SUCCESS.getCode())
.messageBody(created) .messageBody(created)
.build(); .build();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -196,7 +188,7 @@ public class ViolationNotionHandler implements RequestHandler {
if (notionRequest.getNotionId() == null) { if (notionRequest.getNotionId() == null) {
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.NOTION_NOT_FOUND.getCode(), MessageCode.INVALID_JSON_BODY.getCode(),
"NotionId is required for update_notion action", "NotionId is required for update_notion action",
null); null);
} }
@@ -204,7 +196,7 @@ public class ViolationNotionHandler implements RequestHandler {
if (notionRequest.getMessage() == null || notionRequest.getMessage().trim().isEmpty()) { if (notionRequest.getMessage() == null || notionRequest.getMessage().trim().isEmpty()) {
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.MESSAGE_IS_REQUIRED_FOR_NOTION.getCode(), MessageCode.INVALID_JSON_BODY.getCode(),
"Message is required for update_notion action", "Message is required for update_notion action",
null); null);
} }
@@ -214,7 +206,6 @@ public class ViolationNotionHandler implements RequestHandler {
notionRequest.getNotionId(), userId, notionRequest.getMessage()); notionRequest.getNotionId(), userId, notionRequest.getMessage());
return BaseResponse.builder() return BaseResponse.builder()
.msgId(request.getMsgId()) .msgId(request.getMsgId())
.messageCode(MessageCode.SUCCESS.getCode())
.messageBody(updated) .messageBody(updated)
.build(); .build();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -236,7 +227,7 @@ public class ViolationNotionHandler implements RequestHandler {
if (notionRequest.getNotionId() == null) { if (notionRequest.getNotionId() == null) {
return new BaseResponse( return new BaseResponse(
request.getMsgId(), request.getMsgId(),
MessageCode.NOTION_NOT_FOUND.getCode(), MessageCode.INVALID_JSON_BODY.getCode(),
"NotionId is required for delete_notion action", "NotionId is required for delete_notion action",
null); null);
} }
@@ -250,7 +241,6 @@ public class ViolationNotionHandler implements RequestHandler {
.build(); .build();
return BaseResponse.builder() return BaseResponse.builder()
.msgId(request.getMsgId()) .msgId(request.getMsgId())
.messageCode(MessageCode.SUCCESS.getCode())
.messageBody(deleteResponse) .messageBody(deleteResponse)
.build(); .build();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -264,7 +254,7 @@ public class ViolationNotionHandler implements RequestHandler {
request.getMsgId(), request.getMsgId(),
MessageCode.ACCESS_DENIED.getCode(), MessageCode.ACCESS_DENIED.getCode(),
e.getMessage(), e.getMessage(),
MessageCode.ACCESS_DENIED.getDescription()); null);
} }
} }
} }
@@ -10,15 +10,13 @@ import java.util.List;
@Repository @Repository
public interface ViolationNotionRepository extends JpaRepository<ViolationNotion, Long> { public interface ViolationNotionRepository extends JpaRepository<ViolationNotion, Long> {
@Query("SELECT vn FROM ViolationNotion vn " + @Query("SELECT vn FROM ViolationNotion vn " +
"JOIN FETCH vn.user " + "JOIN FETCH vn.user " +
"WHERE vn.file.id = :fileId " + "WHERE vn.violation.id = :violationId " +
"ORDER BY vn.createdAt DESC") "ORDER BY vn.createdAt DESC")
List<ViolationNotion> findByFileId(@Param("fileId") String fileId); List<ViolationNotion> findByViolationId(@Param("violationId") Long violationId);
@Query("SELECT COUNT(vn) FROM ViolationNotion vn WHERE vn.file.id = :fileId") @Query("SELECT COUNT(vn) FROM ViolationNotion vn WHERE vn.violation.id = :violationId")
long countByFileId(@Param("fileId") String fileId); long countByViolationId(@Param("violationId") Long violationId);
void deleteByFileId(String fileId);
} }
@@ -6,13 +6,12 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import ru.soune.nocopy.dto.violation.ViolationNotionResponse; import ru.soune.nocopy.dto.violation.ViolationNotionResponse;
import ru.soune.nocopy.dto.violation.ViolationNotionsListResponse; import ru.soune.nocopy.dto.violation.ViolationNotionsListResponse;
import ru.soune.nocopy.entity.company.Company;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.user.User; import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.entity.violation.Violation;
import ru.soune.nocopy.entity.violation.ViolationNotion; import ru.soune.nocopy.entity.violation.ViolationNotion;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.repository.UserRepository; import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.repository.ViolationNotionRepository; import ru.soune.nocopy.repository.ViolationNotionRepository;
import ru.soune.nocopy.repository.ViolationRepository;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -23,27 +22,25 @@ import java.util.stream.Collectors;
public class ViolationNotionService { public class ViolationNotionService {
private final ViolationNotionRepository notionRepository; private final ViolationNotionRepository notionRepository;
private final UserRepository userRepository; private final UserRepository userRepository;
private final ViolationRepository violationRepository;
private final FileEntityRepository fileRepository;
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ViolationNotionsListResponse getNotionsByFileId(String fileId, Long userId) { public ViolationNotionsListResponse getNotionsByViolationId(Long violationId, Long userId) {
FileEntity file = fileRepository.findById(fileId) Violation violation = violationRepository.findById(violationId)
.orElseThrow(() -> new IllegalArgumentException("File not found: " + fileId)); .orElseThrow(() -> new IllegalArgumentException("Violation not found: " + violationId));
checkPermission(userId, file);
List<ViolationNotion> notions = notionRepository.findByFileId(fileId); List<ViolationNotion> notions = notionRepository.findByViolationId(violationId);
long totalCount = notionRepository.countByFileId(fileId); long totalCount = notionRepository.countByViolationId(violationId);
List<ViolationNotionResponse> notionResponses = notions.stream() List<ViolationNotionResponse> notionResponses = notions.stream()
.map(this::mapToResponse) .map(this::mapToResponse)
.collect(Collectors.toList()); .collect(Collectors.toList());
return ViolationNotionsListResponse.builder() return ViolationNotionsListResponse.builder()
.fileId(fileId) .violationId(violationId)
.fileName(file.getOriginalFileName()) .url(violation.getUrl())
.host(violation.getHost())
.totalCount((int) totalCount) .totalCount((int) totalCount)
.notions(notionResponses) .notions(notionResponses)
.build(); .build();
@@ -54,7 +51,7 @@ public class ViolationNotionService {
ViolationNotion notion = notionRepository.findById(notionId) ViolationNotion notion = notionRepository.findById(notionId)
.orElseThrow(() -> new IllegalArgumentException("Notion not found: " + notionId)); .orElseThrow(() -> new IllegalArgumentException("Notion not found: " + notionId));
if (!notion.getFile().getUserId().equals(userId) && !notion.getUser().getId().equals(userId)) { if (!notion.getUser().getId().equals(userId)) {
throw new SecurityException("User does not have access to this notion"); throw new SecurityException("User does not have access to this notion");
} }
@@ -62,33 +59,24 @@ public class ViolationNotionService {
} }
@Transactional @Transactional
public ViolationNotionResponse createNotion(String fileId, Long userId, String message) { public ViolationNotionResponse createNotion(Long violationId, Long userId, String message) {
User user = userRepository.findById(userId) User user = userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("User not found: " + userId)); .orElseThrow(() -> new IllegalArgumentException("User not found: " + userId));
FileEntity file = fileRepository.findById(fileId) Violation violation = violationRepository.findById(violationId)
.orElseThrow(() -> new IllegalArgumentException("File not found: " + fileId)); .orElseThrow(() -> new IllegalArgumentException("Violation not found: " + violationId));
ViolationNotion notion = new ViolationNotion(); ViolationNotion notion = new ViolationNotion();
notion.setUser(user); notion.setUser(user);
notion.setFile(file); notion.setViolation(violation);
notion.setMessage(message); notion.setMessage(message);
ViolationNotion saved = notionRepository.save(notion); ViolationNotion saved = notionRepository.save(notion);
log.info("Created notion for violation: {} by user: {}", violationId, userId);
return mapToResponse(saved); return mapToResponse(saved);
} }
@Transactional
public void deleteNotion(Long notionId, Long userId) {
ViolationNotion notion = notionRepository.findById(notionId)
.orElseThrow(() -> new IllegalArgumentException("Notion not found: " + notionId));
checkPermission(userId, notion.getFile());
notionRepository.delete(notion);
}
@Transactional @Transactional
public ViolationNotionResponse updateNotion(Long notionId, Long userId, String message) { public ViolationNotionResponse updateNotion(Long notionId, Long userId, String message) {
ViolationNotion notion = notionRepository.findById(notionId) ViolationNotion notion = notionRepository.findById(notionId)
@@ -100,39 +88,40 @@ public class ViolationNotionService {
notion.setMessage(message); notion.setMessage(message);
ViolationNotion updated = notionRepository.save(notion); ViolationNotion updated = notionRepository.save(notion);
log.info("Updated notion with id: {} by user: {}", notionId, userId); log.info("Updated notion: {} by user: {}", notionId, userId);
return mapToResponse(updated); return mapToResponse(updated);
} }
@Transactional
public void deleteNotion(Long notionId, Long userId) {
ViolationNotion notion = notionRepository.findById(notionId)
.orElseThrow(() -> new IllegalArgumentException("Notion not found: " + notionId));
if (!notion.getUser().getId().equals(userId)) {
throw new SecurityException("Only author can delete this notion");
}
notionRepository.delete(notion);
log.info("Deleted notion: {} by user: {}", notionId, userId);
}
private ViolationNotionResponse mapToResponse(ViolationNotion notion) { private ViolationNotionResponse mapToResponse(ViolationNotion notion) {
return ViolationNotionResponse.builder() return ViolationNotionResponse.builder()
.id(notion.getId()) .id(notion.getId())
.message(notion.getMessage()) .message(notion.getMessage())
.createdAt(notion.getCreatedAt()) .createdAt(notion.getCreatedAt())
.updatedAt(notion.getUpdatedAt())
.user(ViolationNotionResponse.UserInfo.builder() .user(ViolationNotionResponse.UserInfo.builder()
.id(notion.getUser().getId()) .id(notion.getUser().getId())
.fullName(notion.getUser().getFullName()) .fullName(notion.getUser().getFullName())
.email(notion.getUser().getEmail()) .email(notion.getUser().getEmail())
.build()) .build())
.violation(ViolationNotionResponse.ViolationInfo.builder()
.id(notion.getViolation().getId())
.url(notion.getViolation().getUrl())
.host(notion.getViolation().getHost())
.build())
.build(); .build();
} }
private void checkPermission(long userId, FileEntity file) {
User user = userRepository.findById(userId).orElseThrow(() ->
new IllegalArgumentException("User not found: " + userId));
Company company = user.getCompany();
if (company != null) {
List<Long> companyUsers = company.getUsers()
.stream()
.map(User::getId)
.toList();
if (!companyUsers.contains(userId)) {
throw new SecurityException("User does not have access to this file");
}
} else if (!file.getUserId().equals(userId)) {
throw new SecurityException("User does not have access to this file");
}
}
} }