Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package org.example.visacasemanagementsystem.audit.controller;

import org.example.visacasemanagementsystem.audit.CommentEventType;
import org.example.visacasemanagementsystem.audit.FileEventType;
import org.example.visacasemanagementsystem.audit.UserEventType;
import org.example.visacasemanagementsystem.audit.VisaEventType;
import org.example.visacasemanagementsystem.audit.service.CommentLogService;
import org.example.visacasemanagementsystem.audit.service.FileLogService;
import org.example.visacasemanagementsystem.audit.service.UserLogService;
import org.example.visacasemanagementsystem.audit.service.VisaLogService;
import org.springframework.data.domain.Page;
Expand Down Expand Up @@ -34,10 +38,17 @@ public class LogViewController {

private final VisaLogService visaLogService;
private final UserLogService userLogService;
private final CommentLogService commentLogService;
private final FileLogService fileLogService;

public LogViewController(VisaLogService visaLogService, UserLogService userLogService) {
public LogViewController(VisaLogService visaLogService,
UserLogService userLogService,
CommentLogService commentLogService,
FileLogService fileLogService) {
this.visaLogService = visaLogService;
this.userLogService = userLogService;
this.commentLogService = commentLogService;
this.fileLogService = fileLogService;
}

@GetMapping("/visa")
Expand Down Expand Up @@ -70,6 +81,36 @@ public String userLog(
UserEventType.values(), "User Log", "log/user", model);
}

@GetMapping("/comment")
public String commentLog(
@RequestParam(value = "eventType", required = false) CommentEventType eventType,
@RequestParam(value = "from", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
@RequestParam(value = "to", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = DEFAULT_PAGE_SIZE_STR) int size,
Model model) {
return renderLogPage(eventType, from, to, page, size,
commentLogService::findFiltered,
CommentEventType.values(), "Comment Log", "log/comment", model);
}

@GetMapping("/file")
public String fileLog(
@RequestParam(value = "eventType", required = false) FileEventType eventType,
@RequestParam(value = "from", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
@RequestParam(value = "to", required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = DEFAULT_PAGE_SIZE_STR) int size,
Model model) {
return renderLogPage(eventType, from, to, page, size,
fileLogService::findFiltered,
FileEventType.values(), "File Log", "log/file", model);
}

// ─── Helpers ──────────────────────────────────────────────────────────

/** Four-arity fetch function: (eventType, from, to, pageable) → Page. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import org.example.visacasemanagementsystem.audit.entity.CommentLog;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface CommentLogRepository extends JpaRepository<CommentLog, Long> {
public interface CommentLogRepository extends JpaRepository<CommentLog, Long>, JpaSpecificationExecutor<CommentLog> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import org.example.visacasemanagementsystem.audit.entity.FileLog;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

@Repository
public interface FileLogRepository extends JpaRepository<FileLog, Long> {
public interface FileLogRepository extends JpaRepository<FileLog, Long>, JpaSpecificationExecutor<FileLog> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import org.example.visacasemanagementsystem.audit.CommentEventType;
import org.example.visacasemanagementsystem.audit.dto.CommentLogDTO;
import org.example.visacasemanagementsystem.audit.entity.CommentLog;
import org.example.visacasemanagementsystem.audit.entity.CommentLog_;
import org.example.visacasemanagementsystem.audit.mapper.CommentLogMapper;
import org.example.visacasemanagementsystem.audit.repository.CommentLogRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@Service
Expand All @@ -28,12 +33,37 @@ public void createCommentLog(Long actorUserId, Long visaCaseId, Long commentId,

}

@PreAuthorize("isAuthenticated()")
@PreAuthorize("hasRole('SYSADMIN')")
public List<CommentLogDTO> findAll() {
return commentLogRepository.findAll()
.stream()
.map(commentLogMapper::toDTO)
.toList();
}

/**
* Used by the /log/comment page. Mirror of VisaLogService#findFiltered.
* Only non-null filters are added to the query, avoiding Hibernate 6/7's
* inability to bind null enum-typed parameters in JPQL.
*/
@PreAuthorize("hasRole('SYSADMIN')")
public Page<CommentLogDTO> findFiltered(CommentEventType eventType,
LocalDateTime from,
LocalDateTime to,
Pageable pageable) {
// Start from the "no filter" Specification. Spring Data JPA 4 deprecated
// the where(null) idiom in favour of this explicit factory method.
Specification<CommentLog> spec = Specification.unrestricted();
if (eventType != null) {
spec = spec.and((root, query, cb) -> cb.equal(root.get(CommentLog_.commentEventType), eventType));
}
if (from != null) {
spec = spec.and((root, query, cb) -> cb.greaterThanOrEqualTo(root.get(CommentLog_.timeStamp), from));
}
if (to != null) {
spec = spec.and((root, query, cb) -> cb.lessThanOrEqualTo(root.get(CommentLog_.timeStamp), to));
}
return commentLogRepository.findAll(spec, pageable).map(commentLogMapper::toDTO);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import org.example.visacasemanagementsystem.audit.FileEventType;
import org.example.visacasemanagementsystem.audit.dto.FileLogDTO;
import org.example.visacasemanagementsystem.audit.entity.FileLog;
import org.example.visacasemanagementsystem.audit.entity.FileLog_;
import org.example.visacasemanagementsystem.audit.mapper.FileLogMapper;
import org.example.visacasemanagementsystem.audit.repository.FileLogRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;

@Service
Expand All @@ -27,11 +32,36 @@ public void createFileLog(Long actorUserId, Long visaCaseId, String fileName, Fi

}

@PreAuthorize("isAuthenticated()")
@PreAuthorize("hasRole('SYSADMIN')")
public List<FileLogDTO> findAll() {
return fileLogRepository.findAll()
.stream()
.map(fileLogMapper::toDTO)
.toList();
}

/**
* Used by the /log/file page. Mirror of VisaLogService#findFiltered.
* Only non-null filters are added to the query, avoiding Hibernate 6/7's
* inability to bind null enum-typed parameters in JPQL.
*/
@PreAuthorize("hasRole('SYSADMIN')")
public Page<FileLogDTO> findFiltered(FileEventType eventType,
LocalDateTime from,
LocalDateTime to,
Pageable pageable) {
// Start from the "no filter" Specification. Spring Data JPA 4 deprecated
// the where(null) idiom in favour of this explicit factory method.
Specification<FileLog> spec = Specification.unrestricted();
if (eventType != null) {
spec = spec.and((root, query, cb) -> cb.equal(root.get(FileLog_.fileEventType), eventType));
}
if (from != null) {
spec = spec.and((root, query, cb) -> cb.greaterThanOrEqualTo(root.get(FileLog_.timeStamp), from));
}
if (to != null) {
spec = spec.and((root, query, cb) -> cb.lessThanOrEqualTo(root.get(FileLog_.timeStamp), to));
}
return fileLogRepository.findAll(spec, pageable).map(fileLogMapper::toDTO);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void createUserLog(Long actorUserId, Long targetUserId, UserEventType use
userLogRepository.save(userLog);
}

@PreAuthorize("isAuthenticated()")
@PreAuthorize("hasRole('SYSADMIN')")
public List<UserLogDTO> findAll() {
return userLogRepository.findAll()
.stream()
Expand All @@ -44,7 +44,7 @@ public List<UserLogDTO> findAll() {
* Only non-null filters are added to the query, avoiding Hibernate 6/7's
* inability to bind null enum-typed parameters in JPQL.
*/
@PreAuthorize("isAuthenticated()")
@PreAuthorize("hasRole('SYSADMIN')")
public Page<UserLogDTO> findFiltered(UserEventType eventType,
LocalDateTime from,
LocalDateTime to,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void createVisaLog(Long userId, Long visaCaseId, VisaEventType visaEventT
visaLogRepository.save(visaLog);
}

@PreAuthorize("isAuthenticated()")
@PreAuthorize("hasRole('SYSADMIN')")
public List<VisaLogDTO> findAll() {
return visaLogRepository.findAll()
.stream()
Expand All @@ -44,6 +44,7 @@ public List<VisaLogDTO> findAll() {
* only non-null filters are added to the query, so Hibernate never has to
* bind a null enum parameter (which fails in Hibernate 6/7).
*/
@PreAuthorize("hasRole('SYSADMIN')")
public Page<VisaLogDTO> findFiltered(VisaEventType eventType,
LocalDateTime from,
LocalDateTime to,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public VisaService(VisaRepository visaRepository,
}

// --- For filtering in Frontend list-view ---
@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SYSADMIN')")
public List<VisaDTO> findAll() {
return visaRepository.findAll(Sort.by(Sort.Direction.DESC, "updatedAt"))
.stream()
Expand Down Expand Up @@ -329,7 +329,10 @@ public void removeVisaDocument(Long visaId, String s3Key, Long userId) {
}
}

@PreAuthorize("hasRole('ADMIN')")
// SYSADMIN is intentionally included in all business-action methods below.
// SYSADMINs can act as full admins when needed (e.g. covering for absent staff)
// while retaining their additional audit/user-management privileges.
@PreAuthorize("hasAnyRole('ADMIN', 'SYSADMIN')")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
@Transactional
public VisaDTO approveVisa(Long visaId, Long adminId) {
User admin = validateHandler(adminId);
Expand All @@ -354,7 +357,7 @@ public VisaDTO approveVisa(Long visaId, Long adminId) {
return visaMapper.toDTO(savedVisa);
}

@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SYSADMIN')")
@Transactional
public VisaDTO rejectVisa(Long visaId, Long adminId, String reason) {
if (reason == null || reason.isBlank()) {
Expand Down Expand Up @@ -382,7 +385,7 @@ public VisaDTO rejectVisa(Long visaId, Long adminId, String reason) {
return visaMapper.toDTO(savedVisa);
}

@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SYSADMIN')")
@Transactional
public VisaDTO requestMoreInformation(Long visaId, Long adminId, String infoText) {
if (infoText == null || infoText.isBlank()) {
Expand Down Expand Up @@ -411,7 +414,7 @@ public VisaDTO requestMoreInformation(Long visaId, Long adminId, String infoText
return visaMapper.toDTO(savedVisa);
}

@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SYSADMIN')")
@Transactional
public VisaDTO assignHandler(Long visaId, Long adminId) {
User admin = validateHandler(adminId);
Expand Down Expand Up @@ -468,7 +471,7 @@ public List<VisaDTO> findVisasByApplicantId(Long applicantId) {
.toList();
}

@PreAuthorize("hasRole('ADMIN')")
@PreAuthorize("hasAnyRole('ADMIN', 'SYSADMIN')")
public List<VisaDTO> findVisasByHandlerId(Long handlerId) {
return visaRepository.findVisasByHandlerId(handlerId,
Sort.by("updatedAt").descending())
Expand Down
Loading