Desktop fixes #320
@@ -10,7 +10,7 @@ public class ActivityLoggingFilterRegistrationConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public FilterRegistrationBean<ActivityLoggingFilter> activityLoggingFilterRegistration(ActivityLoggingFilter activityLoggingFilter) {
|
public FilterRegistrationBean<ActivityLoggingFilter> activityLoggingFilterRegistration(ActivityLoggingFilter activityLoggingFilter) {
|
||||||
FilterRegistrationBean<ActivityLoggingFilter> registrationBean = new FilterRegistrationBean<>(activityLoggingFilter);
|
FilterRegistrationBean<ActivityLoggingFilter> registrationBean = new FilterRegistrationBean<>(activityLoggingFilter);
|
||||||
registrationBean.setEnabled(false);
|
registrationBean.setEnabled(true);
|
||||||
return registrationBean;
|
return registrationBean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.activity.ActivityLogResponse;
|
||||||
|
import com.petshop.backend.service.ActivityLogService;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
import org.springframework.format.annotation.DateTimeFormat;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/activity-logs")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public class ActivityLogController {
|
||||||
|
|
||||||
|
private final ActivityLogService activityLogService;
|
||||||
|
|
||||||
|
public ActivityLogController(ActivityLogService activityLogService) {
|
||||||
|
this.activityLogService = activityLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<List<ActivityLogResponse>> getActivityLogs(
|
||||||
|
@RequestParam(defaultValue = "2000") int limit,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String role,
|
||||||
|
@RequestParam(required = false) String search,
|
||||||
|
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
|
||||||
|
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
|
||||||
|
int safeLimit = Math.min(Math.max(1, limit), 10000);
|
||||||
|
return ResponseEntity.ok(activityLogService.getLogs(safeLimit, storeId, role, search, startDate, endDate));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.petshop.backend.dto.activity;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class ActivityLogResponse {
|
||||||
|
private Long logId;
|
||||||
|
private Long userId;
|
||||||
|
private String username;
|
||||||
|
private String fullName;
|
||||||
|
private String role;
|
||||||
|
private Long storeId;
|
||||||
|
private String storeName;
|
||||||
|
private String usernameSnapshot;
|
||||||
|
private String fullNameSnapshot;
|
||||||
|
private String roleSnapshot;
|
||||||
|
private String storeNameSnapshot;
|
||||||
|
private String activity;
|
||||||
|
private LocalDateTime logTimestamp;
|
||||||
|
|
||||||
|
public ActivityLogResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLogId() {
|
||||||
|
return logId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogId(Long logId) {
|
||||||
|
this.logId = logId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(Long userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFullName(String fullName) {
|
||||||
|
this.fullName = fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRole() {
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRole(String role) {
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getStoreId() {
|
||||||
|
return storeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoreId(Long storeId) {
|
||||||
|
this.storeId = storeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStoreName() {
|
||||||
|
return storeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoreName(String storeName) {
|
||||||
|
this.storeName = storeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsernameSnapshot() {
|
||||||
|
return usernameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsernameSnapshot(String usernameSnapshot) {
|
||||||
|
this.usernameSnapshot = usernameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullNameSnapshot() {
|
||||||
|
return fullNameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFullNameSnapshot(String fullNameSnapshot) {
|
||||||
|
this.fullNameSnapshot = fullNameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoleSnapshot() {
|
||||||
|
return roleSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleSnapshot(String roleSnapshot) {
|
||||||
|
this.roleSnapshot = roleSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStoreNameSnapshot() {
|
||||||
|
return storeNameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoreNameSnapshot(String storeNameSnapshot) {
|
||||||
|
this.storeNameSnapshot = storeNameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActivity() {
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivity(String activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getLogTimestamp() {
|
||||||
|
return logTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogTimestamp(LocalDateTime logTimestamp) {
|
||||||
|
this.logTimestamp = logTimestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package com.petshop.backend.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.Immutable;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Immutable
|
||||||
|
@Table(name = "activityLog")
|
||||||
|
public class ActivityLog {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long logId;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "userId", nullable = false)
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "storeId")
|
||||||
|
private StoreLocation store;
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String usernameSnapshot;
|
||||||
|
|
||||||
|
@Column(length = 100)
|
||||||
|
private String fullNameSnapshot;
|
||||||
|
|
||||||
|
@Column(length = 20)
|
||||||
|
private String roleSnapshot;
|
||||||
|
|
||||||
|
@Column(length = 100)
|
||||||
|
private String storeNameSnapshot;
|
||||||
|
|
||||||
|
@Column(nullable = false, columnDefinition = "TEXT")
|
||||||
|
private String activity;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private LocalDateTime logTimestamp = LocalDateTime.now();
|
||||||
|
|
||||||
|
public ActivityLog() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActivityLog(Long logId, User user, String activity, LocalDateTime logTimestamp) {
|
||||||
|
this.logId = logId;
|
||||||
|
this.user = user;
|
||||||
|
this.activity = activity;
|
||||||
|
this.logTimestamp = logTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getLogId() {
|
||||||
|
return logId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogId(Long logId) {
|
||||||
|
this.logId = logId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(User user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StoreLocation getStore() {
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStore(StoreLocation store) {
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsernameSnapshot() {
|
||||||
|
return usernameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsernameSnapshot(String usernameSnapshot) {
|
||||||
|
this.usernameSnapshot = usernameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullNameSnapshot() {
|
||||||
|
return fullNameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFullNameSnapshot(String fullNameSnapshot) {
|
||||||
|
this.fullNameSnapshot = fullNameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoleSnapshot() {
|
||||||
|
return roleSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoleSnapshot(String roleSnapshot) {
|
||||||
|
this.roleSnapshot = roleSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStoreNameSnapshot() {
|
||||||
|
return storeNameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoreNameSnapshot(String storeNameSnapshot) {
|
||||||
|
this.storeNameSnapshot = storeNameSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActivity() {
|
||||||
|
return activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivity(String activity) {
|
||||||
|
this.activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDateTime getLogTimestamp() {
|
||||||
|
return logTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogTimestamp(LocalDateTime logTimestamp) {
|
||||||
|
this.logTimestamp = logTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ActivityLog that = (ActivityLog) o;
|
||||||
|
return Objects.equals(logId, that.logId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(logId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ActivityLog{" +
|
||||||
|
"logId=" + logId +
|
||||||
|
", user=" + user +
|
||||||
|
", activity='" + activity + '\'' +
|
||||||
|
", logTimestamp=" + logTimestamp +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.ActivityLog;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ActivityLogRepository extends JpaRepository<ActivityLog, Long>, JpaSpecificationExecutor<ActivityLog> {
|
||||||
|
boolean existsByUser_Id(Long userId);
|
||||||
|
|
||||||
|
@Query("select a from ActivityLog a order by a.logTimestamp desc, a.logId desc")
|
||||||
|
List<ActivityLog> findRecent(Pageable pageable);
|
||||||
|
}
|
||||||
@@ -43,8 +43,8 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
|
|||||||
@Query("SELECT p FROM Pet p WHERE p.id = :id")
|
@Query("SELECT p FROM Pet p WHERE p.id = :id")
|
||||||
Optional<Pet> findByIdForUpdate(@Param("id") Long id);
|
Optional<Pet> findByIdForUpdate(@Param("id") Long id);
|
||||||
|
|
||||||
@Query("SELECT p FROM Pet p WHERE " +
|
@Query("SELECT p FROM Pet p LEFT JOIN p.owner o WHERE " +
|
||||||
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(o.firstName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(o.lastName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(CONCAT(COALESCE(o.firstName, ''), ' ', COALESCE(o.lastName, ''))) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||||
"(:breed IS NULL OR LOWER(COALESCE(p.petBreed, '')) = LOWER(:breed)) AND " +
|
"(:breed IS NULL OR LOWER(COALESCE(p.petBreed, '')) = LOWER(:breed)) AND " +
|
||||||
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status)) AND " +
|
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status)) AND " +
|
||||||
|
|||||||
@@ -1,48 +1,174 @@
|
|||||||
package com.petshop.backend.service;
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.activity.ActivityLogResponse;
|
||||||
|
import com.petshop.backend.entity.ActivityLog;
|
||||||
import com.petshop.backend.entity.StoreLocation;
|
import com.petshop.backend.entity.StoreLocation;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.repository.ActivityLogRepository;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import jakarta.persistence.criteria.Predicate;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class ActivityLogService {
|
public class ActivityLogService {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger("activity");
|
private static final Logger log = LoggerFactory.getLogger(ActivityLogService.class);
|
||||||
|
|
||||||
|
private final ActivityLogRepository activityLogRepository;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
public ActivityLogService(UserRepository userRepository) {
|
public ActivityLogService(ActivityLogRepository activityLogRepository, UserRepository userRepository) {
|
||||||
|
this.activityLogRepository = activityLogRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Async
|
@Transactional
|
||||||
public void record(Long userId, String activity) {
|
public void record(Long userId, String activity) {
|
||||||
if (userId == null || activity == null || activity.isBlank()) {
|
if (userId == null || activity == null || activity.isBlank()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
User user = userRepository.findById(userId).orElse(null);
|
User managedUser = userRepository.findById(userId).orElse(null);
|
||||||
if (user == null) {
|
if (managedUser == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StoreLocation store = user.getPrimaryStore();
|
StoreLocation store = managedUser.getPrimaryStore();
|
||||||
String role = user.getRole() != null ? user.getRole().name() : "UNKNOWN";
|
ActivityLog entry = new ActivityLog();
|
||||||
String storeName = store != null ? store.getStoreName() : "no store";
|
entry.setUser(managedUser);
|
||||||
log.info("{} | {} | {} | {}", role, user.getUsername(), storeName, activity.trim());
|
entry.setStore(store);
|
||||||
|
entry.setUsernameSnapshot(managedUser.getUsername());
|
||||||
|
entry.setFullNameSnapshot(resolveFullName(managedUser));
|
||||||
|
entry.setRoleSnapshot(managedUser.getRole() != null ? managedUser.getRole().name() : null);
|
||||||
|
entry.setStoreNameSnapshot(store != null ? store.getStoreName() : null);
|
||||||
|
entry.setActivity(activity.trim());
|
||||||
|
activityLogRepository.save(entry);
|
||||||
|
log.info("[ACTIVITY] {} | {} | {} | {}",
|
||||||
|
entry.getRoleSnapshot(),
|
||||||
|
entry.getUsernameSnapshot(),
|
||||||
|
entry.getStoreNameSnapshot() != null ? entry.getStoreNameSnapshot() : "no store",
|
||||||
|
entry.getActivity());
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.warn("Failed to record activity", ex);
|
log.warn("Failed to persist activity log", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Async
|
|
||||||
public void record(User user, String activity) {
|
public void record(User user, String activity) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
record(user.getId(), activity);
|
record(user.getId(), activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<ActivityLogResponse> getLogs(int limit, Long storeId, String role, String search, LocalDate startDate, LocalDate endDate) {
|
||||||
|
Specification<ActivityLog> spec = (root, query, cb) -> {
|
||||||
|
List<Predicate> predicates = new ArrayList<>();
|
||||||
|
|
||||||
|
if (storeId != null) {
|
||||||
|
predicates.add(cb.equal(root.get("store").get("storeId"), storeId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role != null && !role.isBlank()) {
|
||||||
|
predicates.add(cb.equal(root.get("roleSnapshot"), role));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search != null && !search.isBlank()) {
|
||||||
|
String pattern = "%" + search.toLowerCase() + "%";
|
||||||
|
Predicate searchPredicate = cb.or(
|
||||||
|
cb.like(cb.lower(root.get("activity")), pattern),
|
||||||
|
cb.like(cb.lower(root.get("fullNameSnapshot")), pattern),
|
||||||
|
cb.like(cb.lower(root.get("usernameSnapshot")), pattern)
|
||||||
|
);
|
||||||
|
predicates.add(searchPredicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startDate != null) {
|
||||||
|
predicates.add(cb.greaterThanOrEqualTo(root.get("logTimestamp"), startDate.atStartOfDay()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate != null) {
|
||||||
|
predicates.add(cb.lessThan(root.get("logTimestamp"), endDate.plusDays(1).atStartOfDay()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cb.and(predicates.toArray(new Predicate[0]));
|
||||||
|
};
|
||||||
|
|
||||||
|
PageRequest pageRequest = PageRequest.of(0, limit, Sort.by(Sort.Direction.DESC, "logTimestamp", "logId"));
|
||||||
|
return activityLogRepository.findAll(spec, pageRequest).stream()
|
||||||
|
.map(this::toResponse)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<ActivityLogResponse> getLogs(int limit, Long storeId, String role, String search) {
|
||||||
|
return getLogs(limit, storeId, role, search, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<ActivityLogResponse> getLogs(int limit) {
|
||||||
|
return getLogs(limit, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActivityLogResponse toResponse(ActivityLog entry) {
|
||||||
|
ActivityLogResponse response = new ActivityLogResponse();
|
||||||
|
response.setLogId(entry.getLogId());
|
||||||
|
|
||||||
|
if (entry.getUser() != null) {
|
||||||
|
response.setUserId(entry.getUser().getId());
|
||||||
|
response.setUsername(firstNonBlank(entry.getUsernameSnapshot(), entry.getUser().getUsername()));
|
||||||
|
response.setFullName(firstNonBlank(entry.getFullNameSnapshot(), resolveFullName(entry.getUser())));
|
||||||
|
response.setRole(firstNonBlank(entry.getRoleSnapshot(), entry.getUser().getRole() != null ? entry.getUser().getRole().name() : null));
|
||||||
|
}
|
||||||
|
|
||||||
|
StoreLocation store = entry.getStore();
|
||||||
|
if (store != null) {
|
||||||
|
response.setStoreId(store.getStoreId());
|
||||||
|
response.setStoreName(firstNonBlank(entry.getStoreNameSnapshot(), store.getStoreName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
response.setUsernameSnapshot(entry.getUsernameSnapshot());
|
||||||
|
response.setFullNameSnapshot(entry.getFullNameSnapshot());
|
||||||
|
response.setRoleSnapshot(entry.getRoleSnapshot());
|
||||||
|
response.setStoreNameSnapshot(entry.getStoreNameSnapshot());
|
||||||
|
|
||||||
|
response.setActivity(entry.getActivity());
|
||||||
|
response.setLogTimestamp(entry.getLogTimestamp());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveFullName(User user) {
|
||||||
|
if (user == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (user.getFullName() != null && !user.getFullName().isBlank()) {
|
||||||
|
return user.getFullName();
|
||||||
|
}
|
||||||
|
String first = user.getFirstName();
|
||||||
|
String last = user.getLastName();
|
||||||
|
if (first == null || first.isBlank()) {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
if (last == null || last.isBlank()) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
return first.trim() + " " + last.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String preferred, String fallback) {
|
||||||
|
if (preferred != null && !preferred.isBlank()) {
|
||||||
|
return preferred;
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,8 +90,9 @@ public class ChatRealtimeClient implements WebSocket.Listener {
|
|||||||
for (ConversationResponse conv : globalConversations.values()) {
|
for (ConversationResponse conv : globalConversations.values()) {
|
||||||
if ("CLOSED".equals(conv.getStatus())) continue;
|
if ("CLOSED".equals(conv.getStatus())) continue;
|
||||||
|
|
||||||
// Needs pickup
|
// Needs pickup - only if we haven't already replied
|
||||||
if (conv.getHumanRequestedAt() != null && conv.getStaffId() == null) {
|
if (conv.getHumanRequestedAt() != null && conv.getStaffId() == null
|
||||||
|
&& (currentUserId == null || !currentUserId.equals(conv.getLastSenderId()))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user