Fixed Log filters and fixed chat attachment download
This commit is contained in:
@@ -6,9 +6,15 @@ import java.util.List;
|
|||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
public interface ActivityLogApi {
|
public interface ActivityLogApi {
|
||||||
|
|
||||||
@GET("api/v1/activity-logs")
|
@GET("api/v1/activity-logs")
|
||||||
Call<List<ActivityLogDTO>> getActivityLogs();
|
Call<List<ActivityLogDTO>> getActivityLogs(
|
||||||
|
@Query("limit") int limit,
|
||||||
|
@Query("storeId") Long storeId,
|
||||||
|
@Query("role") String role,
|
||||||
|
@Query("search") String search
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,43 +227,46 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) {
|
private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) {
|
||||||
try {
|
android.os.Handler mainHandler = new android.os.Handler(android.os.Looper.getMainLooper());
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
new Thread(() -> {
|
||||||
ContentValues values = new ContentValues();
|
try {
|
||||||
values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
|
ContentValues values = new ContentValues();
|
||||||
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
|
values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
|
||||||
|
values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
|
||||||
|
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
|
||||||
Uri uri = requireContext().getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
|
Uri uri = requireContext().getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
try (OutputStream outputStream = requireContext().getContentResolver().openOutputStream(uri);
|
try (OutputStream outputStream = requireContext().getContentResolver().openOutputStream(uri);
|
||||||
|
InputStream inputStream = body.byteStream()) {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mainHandler.post(() -> Toast.makeText(requireContext(), "File saved to Downloads", Toast.LENGTH_SHORT).show());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
File file = new File(downloadsDir, fileName);
|
||||||
|
try (OutputStream outputStream = new FileOutputStream(file);
|
||||||
InputStream inputStream = body.byteStream()) {
|
InputStream inputStream = body.byteStream()) {
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
int bytesRead;
|
int bytesRead;
|
||||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
outputStream.write(buffer, 0, bytesRead);
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
}
|
}
|
||||||
Toast.makeText(requireContext(), "File saved to Downloads", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
|
mainHandler.post(() -> Toast.makeText(requireContext(), "File saved to Downloads: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show());
|
||||||
}
|
}
|
||||||
} else {
|
body.close();
|
||||||
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
} catch (Exception e) {
|
||||||
File file = new File(downloadsDir, fileName);
|
Log.e(TAG, "Error saving file", e);
|
||||||
try (OutputStream outputStream = new FileOutputStream(file);
|
mainHandler.post(() -> Toast.makeText(requireContext(), "Error saving file", Toast.LENGTH_SHORT).show());
|
||||||
InputStream inputStream = body.byteStream()) {
|
|
||||||
byte[] buffer = new byte[4096];
|
|
||||||
int bytesRead;
|
|
||||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
||||||
outputStream.write(buffer, 0, bytesRead);
|
|
||||||
}
|
|
||||||
Toast.makeText(requireContext(), "File saved to Downloads: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
body.close();
|
}).start();
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error saving file", e);
|
|
||||||
Toast.makeText(requireContext(), "Error saving file", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import com.example.petstoremobile.adapters.ActivityLogAdapter;
|
import com.example.petstoremobile.adapters.ActivityLogAdapter;
|
||||||
import com.example.petstoremobile.databinding.FragmentActivityLogBinding;
|
import com.example.petstoremobile.databinding.FragmentActivityLogBinding;
|
||||||
import com.example.petstoremobile.dtos.ActivityLogDTO;
|
import com.example.petstoremobile.dtos.ActivityLogDTO;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ActivityLogListViewModel;
|
import com.example.petstoremobile.viewmodels.ActivityLogListViewModel;
|
||||||
@@ -29,6 +30,7 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
private ActivityLogListViewModel viewModel;
|
private ActivityLogListViewModel viewModel;
|
||||||
private ActivityLogAdapter adapter;
|
private ActivityLogAdapter adapter;
|
||||||
private final List<ActivityLogDTO> logList = new ArrayList<>();
|
private final List<ActivityLogDTO> logList = new ArrayList<>();
|
||||||
|
private List<DropdownDTO> storeList = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -40,7 +42,7 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
setupFilters();
|
setupFilters();
|
||||||
observeViewModel();
|
observeViewModel();
|
||||||
|
|
||||||
binding.swipeRefreshActivityLog.setOnRefreshListener(() -> viewModel.loadLogs());
|
binding.swipeRefreshActivityLog.setOnRefreshListener(() -> viewModel.loadInitialData());
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerActivityLog, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerActivityLog, this);
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
@@ -49,7 +51,7 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
viewModel.loadLogs();
|
viewModel.loadInitialData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
@@ -65,10 +67,18 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
UIUtils.attachSearch(binding.etSearchLog, () ->
|
UIUtils.attachSearch(binding.etSearchLog, () ->
|
||||||
viewModel.setSearchQuery(binding.etSearchLog.getText().toString()));
|
viewModel.setSearchQuery(binding.etSearchLog.getText().toString()));
|
||||||
|
|
||||||
String[] roles = {"All Roles", "ADMIN", "STAFF"};
|
String[] roles = {"All Roles", "Admin", "Staff", "Customer"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRoleFilter, roles, () ->
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRoleFilter, roles, () ->
|
||||||
viewModel.setRoleFilter(binding.spinnerRoleFilter.getSelectedItem() != null
|
viewModel.setRoleFilter(binding.spinnerRoleFilter.getSelectedItem() != null
|
||||||
? binding.spinnerRoleFilter.getSelectedItem().toString() : "All Roles"));
|
? binding.spinnerRoleFilter.getSelectedItem().toString() : "All Roles"));
|
||||||
|
|
||||||
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreFilter, this::onStoreSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStoreSelected() {
|
||||||
|
int pos = binding.spinnerStoreFilter.getSelectedItemPosition();
|
||||||
|
Long storeId = (pos > 0 && !storeList.isEmpty()) ? storeList.get(pos - 1).getId() : null;
|
||||||
|
viewModel.setStoreFilter(storeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
@@ -78,16 +88,14 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
viewModel.getStoreOptions().observe(getViewLifecycleOwner(), options -> {
|
viewModel.getStoreOptions().observe(getViewLifecycleOwner(), stores -> {
|
||||||
String[] arr = options.toArray(new String[0]);
|
storeList = stores;
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStoreFilter, arr, () ->
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStoreFilter,
|
||||||
viewModel.setStoreFilter(binding.spinnerStoreFilter.getSelectedItem() != null
|
stores, DropdownDTO::getLabel, "All Stores", -1L, DropdownDTO::getId);
|
||||||
? binding.spinnerStoreFilter.getSelectedItem().toString() : "All Stores"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading ->
|
||||||
binding.swipeRefreshActivityLog.setRefreshing(loading);
|
binding.swipeRefreshActivityLog.setRefreshing(loading));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class ActivityLogRepository extends BaseRepository {
|
|||||||
this.activityLogApi = activityLogApi;
|
this.activityLogApi = activityLogApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<List<ActivityLogDTO>>> getActivityLogs() {
|
public LiveData<Resource<List<ActivityLogDTO>>> getActivityLogs(int limit, Long storeId, String role, String search) {
|
||||||
return executeCall(activityLogApi.getActivityLogs());
|
return executeCall(activityLogApi.getActivityLogs(limit, storeId, role, search));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import androidx.lifecycle.Observer;
|
|||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.ActivityLogDTO;
|
import com.example.petstoremobile.dtos.ActivityLogDTO;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.repositories.ActivityLogRepository;
|
import com.example.petstoremobile.repositories.ActivityLogRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -20,35 +20,48 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
public class ActivityLogListViewModel extends ViewModel {
|
public class ActivityLogListViewModel extends ViewModel {
|
||||||
private final ActivityLogRepository repository;
|
private static final int LIMIT = 2000;
|
||||||
|
|
||||||
|
private final ActivityLogRepository repository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
private final List<ActivityLogDTO> allLogs = new ArrayList<>();
|
|
||||||
private final MutableLiveData<List<ActivityLogDTO>> logs = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<ActivityLogDTO>> logs = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<String>> storeOptions = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> storeOptions = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
private String searchQuery = "";
|
private Long currentStoreId = null;
|
||||||
private String roleFilter = "All Roles";
|
private String currentRole = null;
|
||||||
private String storeFilter = "All Stores";
|
private String currentSearch = null;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ActivityLogListViewModel(ActivityLogRepository repository) {
|
public ActivityLogListViewModel(ActivityLogRepository repository, StoreRepository storeRepository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<ActivityLogDTO>> getLogs() { return logs; }
|
public LiveData<List<ActivityLogDTO>> getLogs() { return logs; }
|
||||||
public LiveData<List<String>> getStoreOptions() { return storeOptions; }
|
public LiveData<List<DropdownDTO>> getStoreOptions() { return storeOptions; }
|
||||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadInitialData() {
|
||||||
|
loadStores();
|
||||||
|
loadLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadStores() {
|
||||||
|
observeOnce(storeRepository.getStoreDropdowns(), resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
storeOptions.setValue(resource.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void loadLogs() {
|
public void loadLogs() {
|
||||||
isLoading.setValue(true);
|
isLoading.setValue(true);
|
||||||
observeOnce(repository.getActivityLogs(), resource -> {
|
observeOnce(repository.getActivityLogs(LIMIT, currentStoreId, currentRole, currentSearch), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
allLogs.clear();
|
logs.setValue(resource.data);
|
||||||
allLogs.addAll(resource.data);
|
|
||||||
buildStoreOptions();
|
|
||||||
applyFilters();
|
|
||||||
isLoading.setValue(false);
|
isLoading.setValue(false);
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
isLoading.setValue(false);
|
isLoading.setValue(false);
|
||||||
@@ -57,66 +70,19 @@ public class ActivityLogListViewModel extends ViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSearchQuery(String query) {
|
|
||||||
searchQuery = query == null ? "" : query.trim();
|
|
||||||
applyFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRoleFilter(String role) {
|
public void setRoleFilter(String role) {
|
||||||
roleFilter = role == null ? "All Roles" : role;
|
currentRole = "All Roles".equals(role) ? null : role;
|
||||||
applyFilters();
|
loadLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStoreFilter(String store) {
|
public void setStoreFilter(Long storeId) {
|
||||||
storeFilter = store == null ? "All Stores" : store;
|
currentStoreId = storeId;
|
||||||
applyFilters();
|
loadLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildStoreOptions() {
|
public void setSearchQuery(String query) {
|
||||||
LinkedHashSet<String> names = new LinkedHashSet<>();
|
currentSearch = (query == null || query.trim().isEmpty()) ? null : query.trim();
|
||||||
for (ActivityLogDTO log : allLogs) {
|
loadLogs();
|
||||||
String name = log.getStoreNameSnapshot() != null ? log.getStoreNameSnapshot() : log.getStoreName();
|
|
||||||
if (name != null && !name.isEmpty()) names.add(name);
|
|
||||||
}
|
|
||||||
List<String> options = new ArrayList<>();
|
|
||||||
options.add("All Stores");
|
|
||||||
options.addAll(names);
|
|
||||||
storeOptions.setValue(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyFilters() {
|
|
||||||
List<ActivityLogDTO> filtered = new ArrayList<>();
|
|
||||||
String query = searchQuery.toLowerCase(Locale.ROOT);
|
|
||||||
|
|
||||||
for (ActivityLogDTO log : allLogs) {
|
|
||||||
// Role filter
|
|
||||||
if (!"All Roles".equals(roleFilter)) {
|
|
||||||
String role = log.getRoleSnapshot() != null ? log.getRoleSnapshot() : log.getRole();
|
|
||||||
if (!roleFilter.equalsIgnoreCase(role)) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store filter
|
|
||||||
if (!"All Stores".equals(storeFilter)) {
|
|
||||||
String store = log.getStoreNameSnapshot() != null ? log.getStoreNameSnapshot() : log.getStoreName();
|
|
||||||
if (!storeFilter.equals(store)) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search filter
|
|
||||||
if (!query.isEmpty()) {
|
|
||||||
String activity = log.getActivity() != null ? log.getActivity().toLowerCase(Locale.ROOT) : "";
|
|
||||||
String fullName = log.getFullNameSnapshot() != null
|
|
||||||
? log.getFullNameSnapshot().toLowerCase(Locale.ROOT)
|
|
||||||
: (log.getFullName() != null ? log.getFullName().toLowerCase(Locale.ROOT) : "");
|
|
||||||
String username = log.getUsernameSnapshot() != null
|
|
||||||
? log.getUsernameSnapshot().toLowerCase(Locale.ROOT)
|
|
||||||
: (log.getUsername() != null ? log.getUsername().toLowerCase(Locale.ROOT) : "");
|
|
||||||
if (!activity.contains(query) && !fullName.contains(query) && !username.contains(query)) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
filtered.add(log);
|
|
||||||
}
|
|
||||||
|
|
||||||
logs.setValue(filtered);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ public class ActivityLogController {
|
|||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<List<ActivityLogResponse>> getActivityLogs(
|
public ResponseEntity<List<ActivityLogResponse>> getActivityLogs(
|
||||||
@RequestParam(defaultValue = "2000") int limit) {
|
@RequestParam(defaultValue = "2000") int limit,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) String role,
|
||||||
|
@RequestParam(required = false) String search) {
|
||||||
int safeLimit = Math.min(Math.max(1, limit), 10000);
|
int safeLimit = Math.min(Math.max(1, limit), 10000);
|
||||||
return ResponseEntity.ok(activityLogService.getLogs(safeLimit));
|
return ResponseEntity.ok(activityLogService.getLogs(safeLimit, storeId, role, search));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ package com.petshop.backend.repository;
|
|||||||
|
|
||||||
import com.petshop.backend.entity.ActivityLog;
|
import com.petshop.backend.entity.ActivityLog;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
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 org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface ActivityLogRepository extends JpaRepository<ActivityLog, Long> {
|
public interface ActivityLogRepository extends JpaRepository<ActivityLog, Long>, JpaSpecificationExecutor<ActivityLog> {
|
||||||
boolean existsByUser_Id(Long userId);
|
boolean existsByUser_Id(Long userId);
|
||||||
|
|
||||||
@Query("select a from ActivityLog a order by a.logTimestamp desc, a.logId desc")
|
@Query("select a from ActivityLog a order by a.logTimestamp desc, a.logId desc")
|
||||||
|
|||||||
@@ -6,12 +6,16 @@ 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.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.data.domain.PageRequest;
|
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 org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -60,9 +64,41 @@ public class ActivityLogService {
|
|||||||
record(user.getId(), activity);
|
record(user.getId(), activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<ActivityLogResponse> getLogs(int limit, Long storeId, String role, String search) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
@Transactional(readOnly = true)
|
||||||
public List<ActivityLogResponse> getLogs(int limit) {
|
public List<ActivityLogResponse> getLogs(int limit) {
|
||||||
return activityLogRepository.findRecent(PageRequest.of(0, limit)).stream().map(this::toResponse).toList();
|
return getLogs(limit, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ActivityLogResponse toResponse(ActivityLog entry) {
|
private ActivityLogResponse toResponse(ActivityLog entry) {
|
||||||
|
|||||||
Reference in New Issue
Block a user