turned logs to laymen terms and added to android
This commit is contained in:
@@ -10,11 +10,19 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.dtos.ActivityLogDTO;
|
import com.example.petstoremobile.dtos.ActivityLogDTO;
|
||||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.ViewHolder> {
|
public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
private static final String SEPARATOR = " | ";
|
||||||
|
private static final SimpleDateFormat INPUT_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
|
||||||
|
private static final SimpleDateFormat OUTPUT_FORMAT = new SimpleDateFormat("MMM d, HH:mm", Locale.getDefault());
|
||||||
|
|
||||||
private final List<ActivityLogDTO> items;
|
private final List<ActivityLogDTO> items;
|
||||||
|
|
||||||
public ActivityLogAdapter(List<ActivityLogDTO> items) {
|
public ActivityLogAdapter(List<ActivityLogDTO> items) {
|
||||||
@@ -32,26 +40,77 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
ActivityLogDTO log = items.get(position);
|
ActivityLogDTO log = items.get(position);
|
||||||
holder.tvActivity.setText(log.getActivity());
|
|
||||||
holder.tvUser.setText(log.getFullName() + " (" + log.getUsername() + ")");
|
String activity = log.getActivity() != null ? log.getActivity() : "";
|
||||||
holder.tvMeta.setText(log.getStoreName() + " · " + log.getRole());
|
int separatorIndex = activity.indexOf(SEPARATOR);
|
||||||
String timestamp = log.getLogTimestamp();
|
if (separatorIndex >= 0) {
|
||||||
String date = DateTimeUtils.extractDate(timestamp);
|
holder.tvActivity.setText(activity.substring(0, separatorIndex).trim());
|
||||||
String time = (timestamp != null && timestamp.length() >= 16) ? timestamp.substring(11, 16) : null;
|
holder.tvTechnical.setText(activity.substring(separatorIndex + SEPARATOR.length()).trim());
|
||||||
holder.tvTimestamp.setText(date != null && time != null ? date + " " + time : date);
|
holder.tvTechnical.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.tvActivity.setText(activity);
|
||||||
|
holder.tvTechnical.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
String fullName = firstNonBlank(log.getFullName(), log.getFullNameSnapshot(), "Unknown");
|
||||||
|
String username = firstNonBlank(log.getUsername(), log.getUsernameSnapshot(), "");
|
||||||
|
holder.tvUser.setText(username.isEmpty() ? fullName : fullName + " (" + username + ")");
|
||||||
|
|
||||||
|
String role = firstNonBlank(log.getRole(), log.getRoleSnapshot(), "");
|
||||||
|
String store = firstNonBlank(log.getStoreName(), log.getStoreNameSnapshot(), "");
|
||||||
|
if (!role.isEmpty() && !store.isEmpty()) {
|
||||||
|
holder.tvMeta.setText(store + " · " + formatRole(role));
|
||||||
|
} else if (!role.isEmpty()) {
|
||||||
|
holder.tvMeta.setText(formatRole(role));
|
||||||
|
} else if (!store.isEmpty()) {
|
||||||
|
holder.tvMeta.setText(store);
|
||||||
|
} else {
|
||||||
|
holder.tvMeta.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.tvTimestamp.setText(formatTimestamp(log.getLogTimestamp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() { return items.size(); }
|
public int getItemCount() { return items.size(); }
|
||||||
|
|
||||||
|
private String formatTimestamp(String raw) {
|
||||||
|
if (raw == null) return "";
|
||||||
|
try {
|
||||||
|
String normalized = raw.length() > 19 ? raw.substring(0, 19) : raw;
|
||||||
|
Date date = INPUT_FORMAT.parse(normalized);
|
||||||
|
return date != null ? OUTPUT_FORMAT.format(date) : raw.substring(0, Math.min(16, raw.length())).replace("T", " ");
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return raw.length() >= 16 ? raw.substring(0, 16).replace("T", " ") : raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatRole(String role) {
|
||||||
|
if (role == null) return "";
|
||||||
|
switch (role.toUpperCase(Locale.ROOT)) {
|
||||||
|
case "ADMIN": return "Admin";
|
||||||
|
case "STAFF": return "Staff";
|
||||||
|
case "CUSTOMER": return "Customer";
|
||||||
|
default: return role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String firstNonBlank(String... values) {
|
||||||
|
for (String v : values) {
|
||||||
|
if (v != null && !v.isBlank()) return v;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView tvActivity, tvUser, tvMeta, tvTimestamp;
|
TextView tvActivity, tvTechnical, tvUser, tvMeta, tvTimestamp;
|
||||||
|
|
||||||
public ViewHolder(@NonNull View itemView) {
|
public ViewHolder(@NonNull View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
tvActivity = itemView.findViewById(R.id.tvLogActivity);
|
tvActivity = itemView.findViewById(R.id.tvLogActivity);
|
||||||
tvUser = itemView.findViewById(R.id.tvLogUser);
|
tvTechnical = itemView.findViewById(R.id.tvLogTechnical);
|
||||||
tvMeta = itemView.findViewById(R.id.tvLogMeta);
|
tvUser = itemView.findViewById(R.id.tvLogUser);
|
||||||
|
tvMeta = itemView.findViewById(R.id.tvLogMeta);
|
||||||
tvTimestamp = itemView.findViewById(R.id.tvLogTimestamp);
|
tvTimestamp = itemView.findViewById(R.id.tvLogTimestamp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public interface ActivityLogApi {
|
|||||||
@Query("limit") int limit,
|
@Query("limit") int limit,
|
||||||
@Query("storeId") Long storeId,
|
@Query("storeId") Long storeId,
|
||||||
@Query("role") String role,
|
@Query("role") String role,
|
||||||
@Query("search") String search
|
@Query("search") String search,
|
||||||
|
@Query("startDate") String startDate,
|
||||||
|
@Query("endDate") String endDate
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -28,6 +29,7 @@ import com.example.petstoremobile.viewmodels.ActivityLogListViewModel;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
@@ -39,6 +41,8 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
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<>();
|
private List<DropdownDTO> storeList = new ArrayList<>();
|
||||||
|
private String selectedStartDate = null;
|
||||||
|
private String selectedEndDate = null;
|
||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@@ -106,6 +110,35 @@ public class ActivityLogFragment extends Fragment {
|
|||||||
? binding.spinnerRoleFilter.getSelectedItem().toString() : "All Roles"));
|
? binding.spinnerRoleFilter.getSelectedItem().toString() : "All Roles"));
|
||||||
|
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreFilter, this::onStoreSelected);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreFilter, this::onStoreSelected);
|
||||||
|
|
||||||
|
binding.btnStartDate.setOnClickListener(v -> showDatePicker(true));
|
||||||
|
binding.btnEndDate.setOnClickListener(v -> showDatePicker(false));
|
||||||
|
binding.btnClearDates.setOnClickListener(v -> {
|
||||||
|
selectedStartDate = null;
|
||||||
|
selectedEndDate = null;
|
||||||
|
binding.btnStartDate.setText("Start Date");
|
||||||
|
binding.btnEndDate.setText("End Date");
|
||||||
|
binding.btnClearDates.setVisibility(View.GONE);
|
||||||
|
viewModel.setDateRange(null, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDatePicker(boolean isStart) {
|
||||||
|
Calendar cal = Calendar.getInstance();
|
||||||
|
new DatePickerDialog(requireContext(), (view, year, month, day) -> {
|
||||||
|
String date = String.format("%04d-%02d-%02d", year, month + 1, day);
|
||||||
|
String label = String.format("%02d/%02d/%04d", day, month + 1, year);
|
||||||
|
if (isStart) {
|
||||||
|
selectedStartDate = date;
|
||||||
|
binding.btnStartDate.setText(label);
|
||||||
|
} else {
|
||||||
|
selectedEndDate = date;
|
||||||
|
binding.btnEndDate.setText(label);
|
||||||
|
}
|
||||||
|
binding.btnClearDates.setVisibility(
|
||||||
|
selectedStartDate != null || selectedEndDate != null ? View.VISIBLE : View.GONE);
|
||||||
|
viewModel.setDateRange(selectedStartDate, selectedEndDate);
|
||||||
|
}, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onStoreSelected() {
|
private void onStoreSelected() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class ActivityLogRepository extends BaseRepository {
|
|||||||
this.activityLogApi = activityLogApi;
|
this.activityLogApi = activityLogApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<List<ActivityLogDTO>>> getActivityLogs(int limit, Long storeId, String role, String search) {
|
public LiveData<Resource<List<ActivityLogDTO>>> getActivityLogs(int limit, Long storeId, String role, String search, String startDate, String endDate) {
|
||||||
return executeCall(activityLogApi.getActivityLogs(limit, storeId, role, search));
|
return executeCall(activityLogApi.getActivityLogs(limit, storeId, role, search, startDate, endDate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ public class ActivityLogListViewModel extends ViewModel {
|
|||||||
private Long currentStoreId = null;
|
private Long currentStoreId = null;
|
||||||
private String currentRole = null;
|
private String currentRole = null;
|
||||||
private String currentSearch = null;
|
private String currentSearch = null;
|
||||||
|
private String currentStartDate = null;
|
||||||
|
private String currentEndDate = null;
|
||||||
|
|
||||||
private int currentLimit = PAGE_SIZE;
|
private int currentLimit = PAGE_SIZE;
|
||||||
private boolean isLastPage = false;
|
private boolean isLastPage = false;
|
||||||
@@ -71,7 +73,7 @@ public class ActivityLogListViewModel extends ViewModel {
|
|||||||
if (isLastPage) return;
|
if (isLastPage) return;
|
||||||
|
|
||||||
isLoading.setValue(true);
|
isLoading.setValue(true);
|
||||||
observeOnce(repository.getActivityLogs(currentLimit, currentStoreId, currentRole, currentSearch), resource -> {
|
observeOnce(repository.getActivityLogs(currentLimit, currentStoreId, currentRole, currentSearch, currentStartDate, currentEndDate), 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) {
|
||||||
List<ActivityLogDTO> result = resource.data;
|
List<ActivityLogDTO> result = resource.data;
|
||||||
@@ -101,6 +103,13 @@ public class ActivityLogListViewModel extends ViewModel {
|
|||||||
loadLogs(true);
|
loadLogs(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDateRange(String startDate, String endDate) {
|
||||||
|
currentStartDate = startDate;
|
||||||
|
currentEndDate = endDate;
|
||||||
|
loadLogs(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
|
||||||
liveData.observeForever(new Observer<Resource<T>>() {
|
liveData.observeForever(new Observer<Resource<T>>() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -88,6 +88,57 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Date range pickers -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnStartDate"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Start Date"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:backgroundTint="@color/white"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="8dp"
|
||||||
|
android:layout_height="0dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnEndDate"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="End Date"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:backgroundTint="@color/white"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="8dp"
|
||||||
|
android:layout_height="0dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnClearDates"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:text="Clear"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:backgroundTint="#e2e8f0"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Role and Store spinners -->
|
<!-- Role and Store spinners -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -119,6 +170,7 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
|||||||
@@ -13,17 +13,17 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_marginBottom="4dp">
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="2dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvLogActivity"
|
android:id="@+id/tvLogActivity"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:textSize="13sp"
|
android:textSize="14sp"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"/>
|
||||||
android:fontFamily="monospace"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvLogTimestamp"
|
android:id="@+id/tvLogTimestamp"
|
||||||
@@ -36,18 +36,42 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvLogUser"
|
android:id="@+id/tvLogTechnical"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:layout_marginBottom="2dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvLogMeta"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="11sp"
|
android:textSize="11sp"
|
||||||
android:textColor="@color/text_light"/>
|
android:textColor="@color/text_light"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:layout_marginBottom="6dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#F0F0F0"
|
||||||
|
android:layout_marginBottom="6dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLogUser"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLogMeta"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="@color/text_light"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Order(Ordered.LOWEST_PRECEDENCE - 20)
|
@Order(Ordered.LOWEST_PRECEDENCE - 20)
|
||||||
public class ActivityLoggingFilter extends OncePerRequestFilter {
|
public class ActivityLoggingFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final ActivityLogService activityLogService;
|
private final ActivityLogService activityLogService;
|
||||||
|
|
||||||
@@ -30,13 +30,8 @@ import java.io.IOException;
|
|||||||
@Override
|
@Override
|
||||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
String uri = request.getRequestURI();
|
String uri = request.getRequestURI();
|
||||||
if (uri == null || uri.isBlank()) {
|
if (uri == null || uri.isBlank()) return true;
|
||||||
return true;
|
if (!uri.startsWith("/api/")) return true;
|
||||||
}
|
|
||||||
if (!uri.startsWith("/api/")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String lower = uri.toLowerCase(java.util.Locale.ROOT);
|
String lower = uri.toLowerCase(java.util.Locale.ROOT);
|
||||||
return lower.startsWith("/api/v1/health")
|
return lower.startsWith("/api/v1/health")
|
||||||
|| lower.startsWith("/api/v1/activity-logs")
|
|| lower.startsWith("/api/v1/activity-logs")
|
||||||
@@ -46,16 +41,15 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
recordActivity(request, response);
|
recordActivity(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void recordActivity(HttpServletRequest request, HttpServletResponse response) {
|
private void recordActivity(HttpServletRequest request, HttpServletResponse response) {
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
if (authentication == null || !authentication.isAuthenticated()) {
|
if (authentication == null || !authentication.isAuthenticated()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Long userId = null;
|
Long userId = null;
|
||||||
User.Role role = null;
|
User.Role role = null;
|
||||||
@@ -69,11 +63,132 @@ import java.io.IOException;
|
|||||||
role = appPrincipal.getRole();
|
role = appPrincipal.getRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId == null || role == null || role == User.Role.CUSTOMER) {
|
if (userId == null || role == null) return;
|
||||||
return;
|
if ("GET".equalsIgnoreCase(request.getMethod())) return;
|
||||||
}
|
|
||||||
|
String method = request.getMethod();
|
||||||
|
String uri = request.getRequestURI();
|
||||||
|
int status = response.getStatus();
|
||||||
|
|
||||||
|
String technical = method + " " + uri + " → " + status;
|
||||||
|
String description = describe(method, uri, status);
|
||||||
|
String activity = description != null ? description + " | " + technical : technical;
|
||||||
|
|
||||||
String activity = String.format("%s %s -> %d", request.getMethod(), request.getRequestURI(), response.getStatus());
|
|
||||||
activityLogService.record(userId, activity);
|
activityLogService.record(userId, activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String describe(String method, String rawUri, int status) {
|
||||||
|
String uri = rawUri.contains("?") ? rawUri.substring(0, rawUri.indexOf('?')) : rawUri;
|
||||||
|
String[] parts = uri.replaceFirst("^/+", "").split("/");
|
||||||
|
if (parts.length < 3) return null;
|
||||||
|
|
||||||
|
String r = parts[2];
|
||||||
|
String seg3 = parts.length > 3 ? parts[3] : null;
|
||||||
|
String seg4 = parts.length > 4 ? parts[4] : null;
|
||||||
|
String seg5 = parts.length > 5 ? parts[5] : null;
|
||||||
|
|
||||||
|
boolean seg3IsId = seg3 != null && seg3.matches("\\d+");
|
||||||
|
boolean seg4IsId = seg4 != null && seg4.matches("\\d+");
|
||||||
|
String id = seg3IsId ? seg3 : null;
|
||||||
|
String sub = seg3IsId ? seg4 : seg3;
|
||||||
|
String subsub = seg3IsId ? seg5 : seg4;
|
||||||
|
|
||||||
|
boolean isGet = "GET".equalsIgnoreCase(method);
|
||||||
|
boolean isPost = "POST".equalsIgnoreCase(method);
|
||||||
|
boolean isPut = "PUT".equalsIgnoreCase(method);
|
||||||
|
boolean isPatch = "PATCH".equalsIgnoreCase(method);
|
||||||
|
boolean isDelete = "DELETE".equalsIgnoreCase(method);
|
||||||
|
boolean failed = status >= 400;
|
||||||
|
|
||||||
|
switch (r) {
|
||||||
|
case "auth" -> {
|
||||||
|
if ("login".equals(seg3) && isPost) return failed ? "Failed login attempt" : "Logged in";
|
||||||
|
if ("logout".equals(seg3) && isPost) return "Logged out";
|
||||||
|
if ("register".equals(seg3) && isPost) return failed ? "Failed registration attempt" : "Registered a new account";
|
||||||
|
if ("forgot-password".equals(seg3) && isPost) return "Requested a password reset";
|
||||||
|
if ("reset-password".equals(seg3) && isPost) return "Reset their password";
|
||||||
|
if ("me".equals(seg3) && isPut) return "Updated their profile";
|
||||||
|
if ("me".equals(seg3) && "avatar".equals(sub) && isPost) return "Uploaded a profile picture";
|
||||||
|
if ("me".equals(seg3) && "avatar".equals(sub) && isDelete) return "Removed their profile picture";
|
||||||
|
}
|
||||||
|
case "cart" -> {
|
||||||
|
if ("add".equals(seg3) && isPost) return "Added an item to cart";
|
||||||
|
if ("update".equals(seg3) && isPut) return "Updated a cart item";
|
||||||
|
if ("remove".equals(seg3) && isDelete) return "Removed an item from cart";
|
||||||
|
if ("clear".equals(seg3) && isDelete) return "Cleared their cart";
|
||||||
|
if ("apply-coupon".equals(seg3) && isPost) return "Applied a coupon to cart";
|
||||||
|
if ("checkout".equals(seg3)) {
|
||||||
|
if ("complete".equals(seg4) && isPost) return "Completed a purchase";
|
||||||
|
if ("cancel".equals(seg4) && isPost) return "Cancelled checkout";
|
||||||
|
if (isPost) return "Started checkout";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "chat" -> {
|
||||||
|
if ("conversations".equals(seg3) && isPost) return "Started a new chat conversation";
|
||||||
|
if (seg3IsId && "messages".equals(sub) && isPost) return "Sent a chat message";
|
||||||
|
if (seg3IsId && "attachments".equals(sub) && isPost) return "Sent a file in chat";
|
||||||
|
if (seg3IsId && "request-human".equals(sub) && isPost) return "Requested human support in chat";
|
||||||
|
if (seg3IsId && isPut) return "Updated chat conversation #" + id;
|
||||||
|
}
|
||||||
|
case "ai-chat" -> {
|
||||||
|
if ("message".equals(seg3) && isPost) return "Sent a message to the AI assistant";
|
||||||
|
}
|
||||||
|
case "product-suppliers" -> {
|
||||||
|
if (isPost && seg3 == null) return "Linked a product to a supplier";
|
||||||
|
if (seg3IsId && seg4IsId && isPut) return "Updated product-supplier link";
|
||||||
|
if (seg3IsId && seg4IsId && isDelete) return "Removed product-supplier link";
|
||||||
|
if (isDelete && !seg3IsId) return "Removed multiple product-supplier links";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String label = resourceLabel(r);
|
||||||
|
if (label == null) return null;
|
||||||
|
|
||||||
|
if ("image".equals(sub) && id != null) {
|
||||||
|
if (isPost) return "Uploaded image for " + label + " #" + id;
|
||||||
|
if (isDelete) return "Removed image from " + label + " #" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("cancel".equals(sub) && id != null && isPatch) return "Cancelled " + label + " #" + id;
|
||||||
|
|
||||||
|
if (isPost && id == null) {
|
||||||
|
if ("request".equals(seg3)) return "Submitted an adoption request";
|
||||||
|
if ("bulk-delete".equals(seg3)) return "Deleted multiple " + plural(label);
|
||||||
|
return "Created a new " + label;
|
||||||
|
}
|
||||||
|
if ((isPut || isPatch) && id != null && sub == null) return "Updated " + label + " #" + id;
|
||||||
|
if (isDelete && id != null && sub == null) return "Deleted " + label + " #" + id;
|
||||||
|
if (isDelete && id == null) return "Deleted multiple " + plural(label);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resourceLabel(String resource) {
|
||||||
|
return switch (resource) {
|
||||||
|
case "products" -> "product";
|
||||||
|
case "categories" -> "category";
|
||||||
|
case "customers" -> "customer";
|
||||||
|
case "employees" -> "employee";
|
||||||
|
case "users" -> "user";
|
||||||
|
case "pets" -> "pet";
|
||||||
|
case "my-pets" -> "pet";
|
||||||
|
case "appointments" -> "appointment";
|
||||||
|
case "adoptions" -> "adoption";
|
||||||
|
case "sales" -> "sale";
|
||||||
|
case "refunds" -> "refund";
|
||||||
|
case "inventory" -> "inventory record";
|
||||||
|
case "services" -> "service";
|
||||||
|
case "suppliers" -> "supplier";
|
||||||
|
case "purchase-orders" -> "purchase order";
|
||||||
|
case "coupons" -> "coupon";
|
||||||
|
case "stores" -> "store";
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String plural(String label) {
|
||||||
|
if (label.endsWith("y")) return label.substring(0, label.length() - 1) + "ies";
|
||||||
|
if (label.endsWith("s")) return label + "es";
|
||||||
|
return label + "s";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ public class AuthController {
|
|||||||
|
|
||||||
String token = jwtUtil.generateToken(user);
|
String token = jwtUtil.generateToken(user);
|
||||||
if (user.getRole() != User.Role.CUSTOMER) {
|
if (user.getRole() != User.Role.CUSTOMER) {
|
||||||
activityLogService.record(user.getId(), "POST /api/v1/auth/login -> 200");
|
activityLogService.record(user.getId(), "Logged in | POST /api/v1/auth/login → 200");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok(new LoginResponse(
|
return ResponseEntity.ok(new LoginResponse(
|
||||||
|
|||||||
Reference in New Issue
Block a user