merge main into branch
This commit is contained in:
@@ -4,10 +4,12 @@ import com.petshop.backend.config.FlywayContextInitializer;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.data.web.config.EnableSpringDataWebSupport;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO)
|
||||
public class BackendApplication {
|
||||
public static void main(String[] args) {
|
||||
|
||||
@@ -10,7 +10,7 @@ public class ActivityLoggingFilterRegistrationConfig {
|
||||
@Bean
|
||||
public FilterRegistrationBean<ActivityLoggingFilter> activityLoggingFilterRegistration(ActivityLoggingFilter activityLoggingFilter) {
|
||||
FilterRegistrationBean<ActivityLoggingFilter> registrationBean = new FilterRegistrationBean<>(activityLoggingFilter);
|
||||
registrationBean.setEnabled(false);
|
||||
registrationBean.setEnabled(true);
|
||||
return registrationBean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.petshop.backend.config;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfig {
|
||||
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
CaffeineCacheManager manager = new CaffeineCacheManager("userAuthCache");
|
||||
manager.setCaffeine(Caffeine.newBuilder()
|
||||
.expireAfterWrite(60, TimeUnit.SECONDS)
|
||||
.maximumSize(1000));
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import com.petshop.backend.entity.StoreLocation;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.security.JwtUtil;
|
||||
import com.petshop.backend.security.UserAuthCacheService;
|
||||
import com.petshop.backend.service.ActivityLogService;
|
||||
import com.petshop.backend.service.AvatarStorageService;
|
||||
import com.petshop.backend.service.EmailService;
|
||||
@@ -58,8 +59,9 @@ public class AuthController {
|
||||
private final ActivityLogService activityLogService;
|
||||
private final PasswordResetService passwordResetService;
|
||||
private final EmailService emailService;
|
||||
private final UserAuthCacheService userAuthCacheService;
|
||||
|
||||
public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, AvatarStorageService avatarStorageService, ActivityLogService activityLogService, PasswordResetService passwordResetService, EmailService emailService) {
|
||||
public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, AvatarStorageService avatarStorageService, ActivityLogService activityLogService, PasswordResetService passwordResetService, EmailService emailService, UserAuthCacheService userAuthCacheService) {
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.userRepository = userRepository;
|
||||
this.jwtUtil = jwtUtil;
|
||||
@@ -68,6 +70,7 @@ public class AuthController {
|
||||
this.activityLogService = activityLogService;
|
||||
this.passwordResetService = passwordResetService;
|
||||
this.emailService = emailService;
|
||||
this.userAuthCacheService = userAuthCacheService;
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
@@ -263,6 +266,7 @@ public class AuthController {
|
||||
error.put("message", "Username, email, or phone already exists");
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||
}
|
||||
userAuthCacheService.evict(updatedUser.getId());
|
||||
return ResponseEntity.ok(toUserInfoResponse(updatedUser));
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.petshop.backend.controller;
|
||||
import com.petshop.backend.dto.sale.SaleRequest;
|
||||
import com.petshop.backend.dto.sale.SaleResponse;
|
||||
import com.petshop.backend.service.SaleService;
|
||||
import com.petshop.backend.util.AuthenticationHelper;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -21,6 +22,13 @@ public class SaleController {
|
||||
this.saleService = saleService;
|
||||
}
|
||||
|
||||
@GetMapping("/my")
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'ADMIN')")
|
||||
public ResponseEntity<Page<SaleResponse>> getMyOrders(Pageable pageable) {
|
||||
Long userId = AuthenticationHelper.getAuthenticatedUserId();
|
||||
return ResponseEntity.ok(saleService.getAllSales(null, null, null, false, userId, pageable));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<Page<SaleResponse>> getAllSales(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package com.petshop.backend.dto.chat;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class ConversationRequest {
|
||||
@NotBlank(message = "Initial message is required")
|
||||
private String message;
|
||||
|
||||
public ConversationRequest() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -41,11 +41,11 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
|
||||
Optional<Pet> findByIdAndOwner_Id(Long id, Long ownerId);
|
||||
|
||||
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||
@Query("SELECT p FROM Pet p WHERE p.petId = :id")
|
||||
@Query("SELECT p FROM Pet p WHERE p.id = :id")
|
||||
Optional<Pet> findByIdForUpdate(@Param("id") Long id);
|
||||
|
||||
@Query("SELECT p FROM Pet p 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 " +
|
||||
@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, '%')) 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 " +
|
||||
"(:breed IS NULL OR LOWER(COALESCE(p.petBreed, '')) = LOWER(:breed)) AND " +
|
||||
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status)) AND " +
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.petshop.backend.security;
|
||||
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.exception.ApiErrorResponder;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
@@ -16,16 +15,18 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtUtil jwtUtil;
|
||||
private final UserRepository userRepository;
|
||||
private final UserAuthCacheService userAuthCacheService;
|
||||
private final ApiErrorResponder apiErrorResponder;
|
||||
|
||||
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserRepository userRepository, ApiErrorResponder apiErrorResponder) {
|
||||
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserAuthCacheService userAuthCacheService, ApiErrorResponder apiErrorResponder) {
|
||||
this.jwtUtil = jwtUtil;
|
||||
this.userRepository = userRepository;
|
||||
this.userAuthCacheService = userAuthCacheService;
|
||||
this.apiErrorResponder = apiErrorResponder;
|
||||
}
|
||||
|
||||
@@ -44,30 +45,44 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
jwt = authHeader.substring(7);
|
||||
Long userId;
|
||||
String username;
|
||||
String roleStr;
|
||||
Integer jwtTokenVersion;
|
||||
try {
|
||||
userId = jwtUtil.extractUserId(jwt);
|
||||
username = jwtUtil.extractUsername(jwt);
|
||||
roleStr = jwtUtil.extractRole(jwt);
|
||||
jwtTokenVersion = jwtUtil.extractTokenVersion(jwt);
|
||||
} catch (JwtException | IllegalArgumentException ex) {
|
||||
writeUnauthorized(request, response, "Invalid or expired token", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
User user = userRepository.findById(userId).orElse(null);
|
||||
if (user == null || user.getActive() == null || !user.getActive()) {
|
||||
writeUnauthorized(request, response, "User account is inactive", null);
|
||||
return;
|
||||
}
|
||||
if (!jwtUtil.validateToken(jwt, user)) {
|
||||
if (jwtUtil.extractExpiration(jwt).before(new Date())) {
|
||||
writeUnauthorized(request, response, "Invalid or expired token", null);
|
||||
return;
|
||||
}
|
||||
|
||||
AppPrincipal principal = new AppPrincipal(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
user.getRole(),
|
||||
user.getTokenVersion()
|
||||
);
|
||||
UserAuthCacheService.UserAuthData authData = userAuthCacheService.loadAuthData(userId);
|
||||
if (authData == null || !Boolean.TRUE.equals(authData.active())) {
|
||||
writeUnauthorized(request, response, "User account is inactive", null);
|
||||
return;
|
||||
}
|
||||
if (!authData.tokenVersion().equals(jwtTokenVersion)) {
|
||||
writeUnauthorized(request, response, "Invalid or expired token", null);
|
||||
return;
|
||||
}
|
||||
|
||||
User.Role role;
|
||||
try {
|
||||
role = User.Role.valueOf(roleStr);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
writeUnauthorized(request, response, "Invalid or expired token", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
AppPrincipal principal = new AppPrincipal(userId, username, role, jwtTokenVersion);
|
||||
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
|
||||
principal,
|
||||
null,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.petshop.backend.security;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -21,6 +22,8 @@ import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.petshop.backend.config.ActivityLoggingFilter;
|
||||
|
||||
import java.util.List;
|
||||
@@ -30,6 +33,9 @@ import java.util.List;
|
||||
@EnableMethodSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Value("${app.allowed-origins}")
|
||||
private String allowedOriginsRaw;
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthFilter;
|
||||
private final RateLimitFilter rateLimitFilter;
|
||||
private final UserDetailsService userDetailsService;
|
||||
@@ -101,13 +107,13 @@ public class SecurityConfig {
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowedOriginPatterns(List.of("http://localhost:*", "http://127.0.0.1:*"));
|
||||
config.setAllowedOriginPatterns(Arrays.asList(allowedOriginsRaw.split(",")));
|
||||
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
|
||||
config.setAllowedHeaders(List.of("*"));
|
||||
config.setAllowCredentials(true);
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.petshop.backend.security;
|
||||
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserAuthCacheService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public UserAuthCacheService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
public record UserAuthData(Boolean active, Integer tokenVersion) {}
|
||||
|
||||
@Cacheable(value = "userAuthCache", key = "#userId")
|
||||
public UserAuthData loadAuthData(Long userId) {
|
||||
return userRepository.findById(userId)
|
||||
.map(u -> new UserAuthData(u.getActive(), u.getTokenVersion()))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@CacheEvict(value = "userAuthCache", key = "#userId")
|
||||
public void evict(Long userId) {
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,65 @@
|
||||
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.User;
|
||||
import com.petshop.backend.repository.ActivityLogRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
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.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
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;
|
||||
|
||||
public ActivityLogService(UserRepository userRepository) {
|
||||
public ActivityLogService(ActivityLogRepository activityLogRepository, UserRepository userRepository) {
|
||||
this.activityLogRepository = activityLogRepository;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void record(Long userId, String activity) {
|
||||
if (userId == null || activity == null || activity.isBlank()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
User user = userRepository.findById(userId).orElse(null);
|
||||
if (user == null) {
|
||||
User managedUser = userRepository.findById(userId).orElse(null);
|
||||
if (managedUser == null) {
|
||||
return;
|
||||
}
|
||||
StoreLocation store = user.getPrimaryStore();
|
||||
String role = user.getRole() != null ? user.getRole().name() : "UNKNOWN";
|
||||
String storeName = store != null ? store.getStoreName() : "no store";
|
||||
log.info("{} | {} | {} | {}", role, user.getUsername(), storeName, activity.trim());
|
||||
StoreLocation store = managedUser.getPrimaryStore();
|
||||
ActivityLog entry = new ActivityLog();
|
||||
entry.setUser(managedUser);
|
||||
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) {
|
||||
log.warn("Failed to record activity", ex);
|
||||
log.warn("Failed to persist activity log", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,4 +69,106 @@ public class ActivityLogService {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +127,6 @@ public class AppointmentService {
|
||||
}
|
||||
}
|
||||
|
||||
validateSpeciesServiceCompatibility(pet, service);
|
||||
|
||||
validateStoreAccess(store.getStoreId(), authenticatedUser);
|
||||
validatePetServiceCompatibility(pet, service);
|
||||
validateAvailability(employee, service, request.getAppointmentDate(), request.getAppointmentTime(), null);
|
||||
@@ -398,32 +396,6 @@ public class AppointmentService {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateSpeciesServiceCompatibility(Pet pet, com.petshop.backend.entity.Service service) {
|
||||
if (pet == null || service == null) return;
|
||||
String species = pet.getPetSpecies();
|
||||
if (species == null) return;
|
||||
String serviceName = service.getServiceName().toLowerCase();
|
||||
|
||||
switch (species.toLowerCase()) {
|
||||
case "bird":
|
||||
if (!serviceName.contains("wing clipping") && !serviceName.contains("beak and nail")) {
|
||||
throw new IllegalArgumentException(
|
||||
"Service '" + service.getServiceName() + "' is not available for birds. " +
|
||||
"Allowed services: Wing Clipping, Beak and Nail Care.");
|
||||
}
|
||||
break;
|
||||
case "fish":
|
||||
if (!serviceName.contains("aquarium health")) {
|
||||
throw new IllegalArgumentException(
|
||||
"Service '" + service.getServiceName() + "' is not available for fish. " +
|
||||
"Allowed service: Aquarium Health Check.");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void validateStoreAccess(Long requestedStoreId, User user) {
|
||||
if (user.getRole() != User.Role.STAFF) {
|
||||
return;
|
||||
|
||||
@@ -59,14 +59,20 @@ public class ChatService {
|
||||
conversation.setMode(Conversation.ConversationMode.AUTOMATED);
|
||||
conversation = conversationRepository.save(conversation);
|
||||
|
||||
Message message = new Message();
|
||||
message.setConversationId(conversation.getId());
|
||||
message.setSenderId(userId);
|
||||
message.setContent(request.getMessage());
|
||||
message.setIsRead(false);
|
||||
messageRepository.save(message);
|
||||
User botUser = getBotUser();
|
||||
String firstName = user.getFirstName();
|
||||
String greeting = (firstName != null && !firstName.isBlank())
|
||||
? "Hi " + firstName + "! I'm Leon's Pet Assistant. Ask me anything about pet care, adoption advice, or your pets."
|
||||
: "Hi! I'm Leon's Pet Assistant. Ask me anything about pet care, adoption advice, or your pets.";
|
||||
|
||||
return ConversationResponse.fromEntity(conversation, request.getMessage(), userId);
|
||||
Message greetingMsg = new Message();
|
||||
greetingMsg.setConversationId(conversation.getId());
|
||||
greetingMsg.setSenderId(botUser.getId());
|
||||
greetingMsg.setContent(greeting);
|
||||
greetingMsg.setIsRead(false);
|
||||
messageRepository.save(greetingMsg);
|
||||
|
||||
return ConversationResponse.fromEntity(conversation, greeting, botUser.getId());
|
||||
}
|
||||
|
||||
public List<ConversationResponse> getConversations(Long userId, User.Role role, boolean mine) {
|
||||
|
||||
@@ -5,8 +5,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import com.petshop.backend.entity.Conversation;
|
||||
import com.petshop.backend.entity.Message;
|
||||
import com.petshop.backend.entity.Pet;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.MessageRepository;
|
||||
import com.petshop.backend.repository.PetRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -31,7 +33,7 @@ public class OpenRouterAiService {
|
||||
@Value("${openrouter.api-key:}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${openrouter.model:openai/gpt-oss-120b:free}")
|
||||
@Value("${openrouter.model:google/gemma-4-31b-it:free}")
|
||||
private String model;
|
||||
|
||||
private final String openRouterUrl = "https://openrouter.ai/api/v1/chat/completions";
|
||||
@@ -39,6 +41,7 @@ public class OpenRouterAiService {
|
||||
private final ChatRealtimeService chatRealtimeService;
|
||||
private final MessageRepository messageRepository;
|
||||
private final UserRepository userRepository;
|
||||
private final PetRepository petRepository;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@@ -46,12 +49,14 @@ public class OpenRouterAiService {
|
||||
ChatService chatService,
|
||||
ChatRealtimeService chatRealtimeService,
|
||||
MessageRepository messageRepository,
|
||||
UserRepository userRepository
|
||||
UserRepository userRepository,
|
||||
PetRepository petRepository
|
||||
) {
|
||||
this.chatService = chatService;
|
||||
this.chatRealtimeService = chatRealtimeService;
|
||||
this.messageRepository = messageRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.petRepository = petRepository;
|
||||
this.objectMapper = JsonMapper.builder().findAndAddModules().build();
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
@@ -117,10 +122,15 @@ public class OpenRouterAiService {
|
||||
return;
|
||||
}
|
||||
|
||||
User customer = userRepository.findById(conversation.getCustomerId()).orElse(null);
|
||||
List<Pet> customerPets = customer != null
|
||||
? petRepository.findAllByOwner_IdOrderByPetNameAsc(customer.getId())
|
||||
: List.of();
|
||||
|
||||
List<Map<String, String>> messages = new ArrayList<>();
|
||||
messages.add(Map.of(
|
||||
"role", "system",
|
||||
"content", "You are a helpful pet shop assistant. Provide concise and friendly answers. Do not output markdown, just plain text."
|
||||
"content", buildSystemPrompt(customer, customerPets)
|
||||
));
|
||||
|
||||
for (Message message : history) {
|
||||
@@ -177,6 +187,43 @@ public class OpenRouterAiService {
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSystemPrompt(User customer, List<Pet> pets) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("You are Leon's Pet Assistant, a helpful AI for Leon's Pet Store. ");
|
||||
sb.append("Be concise, friendly, and focused on pet care, adoption, products, and appointments. ");
|
||||
sb.append("Do not output markdown, just plain text.\n\n");
|
||||
|
||||
if (customer != null) {
|
||||
sb.append("Customer profile:\n");
|
||||
sb.append("- Name: ").append(customer.getFirstName()).append(" ").append(customer.getLastName()).append("\n");
|
||||
if (customer.getLoyaltyPoints() != null) {
|
||||
sb.append("- Loyalty points: ").append(customer.getLoyaltyPoints()).append("\n");
|
||||
}
|
||||
if (customer.getPrimaryStore() != null && customer.getPrimaryStore().getStoreName() != null) {
|
||||
sb.append("- Preferred store: ").append(customer.getPrimaryStore().getStoreName()).append("\n");
|
||||
}
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
if (pets != null && !pets.isEmpty()) {
|
||||
sb.append("Their registered pets:\n");
|
||||
for (Pet pet : pets) {
|
||||
sb.append("- ").append(pet.getPetName()).append(" (").append(pet.getPetSpecies());
|
||||
if (pet.getPetBreed() != null && !pet.getPetBreed().isBlank()) {
|
||||
sb.append(", ").append(pet.getPetBreed());
|
||||
}
|
||||
if (pet.getPetAge() != null) {
|
||||
sb.append(", ").append(pet.getPetAge()).append(" yr");
|
||||
}
|
||||
sb.append(")\n");
|
||||
}
|
||||
} else {
|
||||
sb.append("They have no pets registered yet.\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String resolveRole(Message message, Long botUserId) {
|
||||
if (message.getSenderId() != null && message.getSenderId().equals(botUserId)) {
|
||||
return "assistant";
|
||||
|
||||
@@ -24,7 +24,7 @@ public class OpenRouterService {
|
||||
@Value("${openrouter.api-key:}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${openrouter.model:meta-llama/llama-3.3-70b-instruct:free}")
|
||||
@Value("${openrouter.model:google/gemma-4-31b-it:free}")
|
||||
private String model;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.exception.BusinessException;
|
||||
import com.petshop.backend.repository.PasswordResetTokenRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.security.UserAuthCacheService;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -29,16 +30,19 @@ public class PasswordResetService {
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final EmailService emailService;
|
||||
private final UserAuthCacheService userAuthCacheService;
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
|
||||
public PasswordResetService(PasswordResetTokenRepository passwordResetTokenRepository,
|
||||
UserRepository userRepository,
|
||||
PasswordEncoder passwordEncoder,
|
||||
EmailService emailService) {
|
||||
EmailService emailService,
|
||||
UserAuthCacheService userAuthCacheService) {
|
||||
this.passwordResetTokenRepository = passwordResetTokenRepository;
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.emailService = emailService;
|
||||
this.userAuthCacheService = userAuthCacheService;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -97,6 +101,7 @@ public class PasswordResetService {
|
||||
user.setPassword(passwordEncoder.encode(newPassword));
|
||||
user.setTokenVersion(user.getTokenVersion() + 1);
|
||||
userRepository.save(user);
|
||||
userAuthCacheService.evict(user.getId());
|
||||
|
||||
token.setUsedAt(now);
|
||||
passwordResetTokenRepository.save(token);
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||
import com.petshop.backend.repository.StoreRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.security.UserAuthCacheService;
|
||||
import com.petshop.backend.util.AuthenticationHelper;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -33,11 +34,13 @@ public class UserService {
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final StoreRepository storeRepository;
|
||||
private final UserAuthCacheService userAuthCacheService;
|
||||
|
||||
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, StoreRepository storeRepository) {
|
||||
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, StoreRepository storeRepository, UserAuthCacheService userAuthCacheService) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.storeRepository = storeRepository;
|
||||
this.userAuthCacheService = userAuthCacheService;
|
||||
}
|
||||
|
||||
public Page<UserResponse> getAllUsers(String query, String role, Pageable pageable) {
|
||||
@@ -147,6 +150,7 @@ public class UserService {
|
||||
}
|
||||
|
||||
user = userRepository.save(user);
|
||||
userAuthCacheService.evict(user.getId());
|
||||
return mapToResponse(user);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ spring:
|
||||
username: ${SPRING_DATASOURCE_USERNAME:petshop}
|
||||
password: ${SPRING_DATASOURCE_PASSWORD:petshop}
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
hikari:
|
||||
maximum-pool-size: 20
|
||||
minimum-idle: 5
|
||||
connection-timeout: 30000
|
||||
|
||||
sql:
|
||||
init:
|
||||
@@ -46,14 +50,16 @@ server:
|
||||
springdoc:
|
||||
api-docs:
|
||||
path: /v3/api-docs
|
||||
enabled: ${SWAGGER_ENABLED:false}
|
||||
swagger-ui:
|
||||
path: /swagger-ui
|
||||
enabled: ${SWAGGER_ENABLED:false}
|
||||
|
||||
app:
|
||||
upload:
|
||||
base-dir: ${UPLOAD_BASE_DIR:uploads}
|
||||
frontend-url: ${FRONTEND_URL:http://localhost:3000}
|
||||
allowed-origins: ${ALLOWED_ORIGINS:http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000}
|
||||
allowed-origins: ${ALLOWED_ORIGINS:http://localhost:3000,http://localhost:3001,http://127.0.0.1:3000,https://petshop-web.nicepond-c7280126.westus2.azurecontainerapps.io}
|
||||
|
||||
azure:
|
||||
storage:
|
||||
@@ -82,9 +88,3 @@ logging:
|
||||
com.petshop: ${LOG_LEVEL:INFO}
|
||||
org.springframework.security: ${LOG_LEVEL_SECURITY:WARN}
|
||||
org.springdoc.core.events.SpringDocAppInitializer: ERROR
|
||||
|
||||
jackson:
|
||||
serialization:
|
||||
write-dates-as-timestamps: false
|
||||
deserialization:
|
||||
fail-on-unknown-properties: false
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
DELETE FROM service_species WHERE serviceId = 2 AND species = 'Bird';
|
||||
|
||||
INSERT INTO service_species (serviceId, species) VALUES
|
||||
(1, 'Guinea Pig'),
|
||||
(1, 'Hamster'),
|
||||
(1, 'Other'),
|
||||
(2, 'Reptile'),
|
||||
(2, 'Other'),
|
||||
(3, 'Reptile'),
|
||||
(3, 'Other'),
|
||||
(4, 'Reptile'),
|
||||
(4, 'Other'),
|
||||
(5, 'Reptile'),
|
||||
(5, 'Other');
|
||||
@@ -0,0 +1,90 @@
|
||||
INSERT INTO activityLog (userId, storeId, usernameSnapshot, fullNameSnapshot, roleSnapshot, storeNameSnapshot, activity, logTimestamp) VALUES
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 08:02:11'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-01-15 08:15:44'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new product | POST /api/v1/products → 201', '2026-01-15 08:31:07'),
|
||||
(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 09:00:00'),
|
||||
(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 09:04:22'),
|
||||
(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-15 10:12:33'),
|
||||
(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-01-15 10:18:05'),
|
||||
(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-01-15 10:25:50'),
|
||||
(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated appointment #12 | PUT /api/v1/appointments/12 → 200', '2026-01-16 11:05:30'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-17 07:58:44'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated pet #8 | PUT /api/v1/pets/8 → 200', '2026-01-17 08:10:19'),
|
||||
(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-20 09:01:55'),
|
||||
(16, NULL,'alex.brown', 'Alex Brown', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-20 14:30:22'),
|
||||
(16, NULL,'alex.brown', 'Alex Brown', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-01-20 14:45:08'),
|
||||
(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-22 08:55:00'),
|
||||
(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Updated pet #14 | PUT /api/v1/pets/14 → 200', '2026-01-22 09:20:17'),
|
||||
(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-01-25 08:00:00'),
|
||||
(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new service | POST /api/v1/services → 201', '2026-01-25 08:22:41'),
|
||||
(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new employee | POST /api/v1/employees → 201', '2026-01-25 09:05:14'),
|
||||
(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-01-28 18:30:00'),
|
||||
(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-01-28 18:35:12'),
|
||||
(17, NULL,'alex.clark', 'Alex Clark', 'CUSTOMER', NULL, 'Updated their profile | PUT /api/v1/auth/me → 200', '2026-01-28 18:40:55'),
|
||||
(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-03 09:00:00'),
|
||||
(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-02-03 10:15:38'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-05 07:50:00'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Deleted pet #3 | DELETE /api/v1/pets/3 → 200', '2026-02-05 08:05:33'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated product #22 | PUT /api/v1/products/22 → 200', '2026-02-05 08:30:44'),
|
||||
(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-07 12:00:00'),
|
||||
(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-02-07 12:08:17'),
|
||||
(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Applied a coupon to cart | POST /api/v1/cart/apply-coupon → 200', '2026-02-07 12:12:05'),
|
||||
(18, NULL,'alex.wilson', 'Alex Wilson', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-02-07 12:20:30'),
|
||||
(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-10 09:00:00'),
|
||||
(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Created a new appointment | POST /api/v1/appointments → 201', '2026-02-10 09:30:22'),
|
||||
(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Updated appointment #25 | PUT /api/v1/appointments/25 → 200', '2026-02-10 11:45:09'),
|
||||
(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-14 15:00:00'),
|
||||
(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Started a new chat conversation | POST /api/v1/chat/conversations → 201','2026-02-14 15:05:44'),
|
||||
(19, NULL,'alex.martinez', 'Alex Martinez', 'CUSTOMER', NULL, 'Sent a chat message | POST /api/v1/chat/conversations/5/messages → 201', '2026-02-14 15:08:22'),
|
||||
(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-17 09:00:00'),
|
||||
(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Uploaded image for pet #31 | POST /api/v1/pets/31/image → 200', '2026-02-17 09:25:11'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-02-20 08:00:00'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-02-20 08:20:35'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated user #18 | PUT /api/v1/users/18 → 200', '2026-02-20 09:10:00'),
|
||||
(20, NULL,'alex.anderson', 'Alex Anderson', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-02-22 19:00:00'),
|
||||
(20, NULL,'alex.anderson', 'Alex Anderson', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-02-22 19:15:40'),
|
||||
(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-01 09:00:00'),
|
||||
(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-01 10:30:15'),
|
||||
(7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Updated appointment #33 | PUT /api/v1/appointments/33 → 200', '2026-03-01 14:05:22'),
|
||||
(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-05 11:00:00'),
|
||||
(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-03-05 11:10:30'),
|
||||
(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Started checkout | POST /api/v1/cart/checkout → 200', '2026-03-05 11:18:44'),
|
||||
(21, NULL,'alex.taylor', 'Alex Taylor', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-05 11:22:17'),
|
||||
(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-08 08:00:00'),
|
||||
(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Deleted multiple pets | POST /api/v1/pets/bulk-delete → 200', '2026-03-08 08:30:00'),
|
||||
(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-10 09:00:00'),
|
||||
(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-10 10:45:33'),
|
||||
(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-12 16:00:00'),
|
||||
(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-03-12 16:05:22'),
|
||||
(22, NULL,'alex.parker', 'Alex Parker', 'CUSTOMER', NULL, 'Updated their profile | PUT /api/v1/auth/me → 200', '2026-03-12 16:20:00'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-15 07:55:00'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new product | POST /api/v1/products → 201', '2026-03-15 08:10:45'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated product #35 | PUT /api/v1/products/35 → 200', '2026-03-15 08:40:12'),
|
||||
(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-18 09:00:00'),
|
||||
(5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Updated appointment #45 | PUT /api/v1/appointments/45 → 200', '2026-03-18 09:35:08'),
|
||||
(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-03-20 20:00:00'),
|
||||
(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Started a new chat conversation | POST /api/v1/chat/conversations → 201','2026-03-20 20:05:30'),
|
||||
(23, NULL,'alex.evans', 'Alex Evans', 'CUSTOMER', NULL, 'Sent a chat message | POST /api/v1/chat/conversations/9/messages → 201', '2026-03-20 20:08:11'),
|
||||
(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-24 09:00:00'),
|
||||
(11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Created a new appointment | POST /api/v1/appointments → 201', '2026-03-24 09:20:44'),
|
||||
(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-03-27 09:00:00'),
|
||||
(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Updated appointment #52 | PUT /api/v1/appointments/52 → 200', '2026-03-27 10:10:19'),
|
||||
(8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-03-27 11:30:00'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-01 08:00:00'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Created a new pet | POST /api/v1/pets → 201', '2026-04-01 08:15:00'),
|
||||
(1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated user #25 | PUT /api/v1/users/25 → 200', '2026-04-01 09:00:22'),
|
||||
(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-05 10:00:00'),
|
||||
(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-04-05 10:15:00'),
|
||||
(15, NULL,'customer', 'Test Customer', 'CUSTOMER', NULL, 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-05 10:25:44'),
|
||||
(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-07 09:00:00'),
|
||||
(3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Updated pet #47 | PUT /api/v1/pets/47 → 200', '2026-04-07 09:40:55'),
|
||||
(24, NULL,'alex.scott', 'Alex Scott', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-09 17:00:00'),
|
||||
(24, NULL,'alex.scott', 'Alex Scott', 'CUSTOMER', NULL, 'Submitted an adoption request | POST /api/v1/adoptions/request → 201', '2026-04-09 17:20:33'),
|
||||
(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-12 08:00:00'),
|
||||
(2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Created a new service | POST /api/v1/services → 201', '2026-04-12 08:25:18'),
|
||||
(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Logged in | POST /api/v1/auth/login → 200', '2026-04-14 09:00:00'),
|
||||
(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a purchase | POST /api/v1/cart/checkout/complete → 200', '2026-04-14 10:55:30'),
|
||||
(4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated appointment #60 | PUT /api/v1/appointments/60 → 200', '2026-04-14 14:20:00'),
|
||||
(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Logged in | POST /api/v1/auth/login → 200', '2026-04-15 11:00:00'),
|
||||
(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Added an item to cart | POST /api/v1/cart/add → 200', '2026-04-15 11:10:22'),
|
||||
(25, NULL,'alex.adams', 'Alex Adams', 'CUSTOMER', NULL, 'Sent a message to the AI assistant | POST /api/v1/ai-chat/message → 200','2026-04-15 11:30:00');
|
||||
@@ -0,0 +1,51 @@
|
||||
INSERT IGNORE INTO sale (saleId, saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId, isRefund, originalSaleId, channel, cartId, couponId, subtotalAmount, couponDiscountAmount, employeeDiscountAmount, pointsEarned) VALUES
|
||||
(134, '2026-04-10 09:15:00', 57.67, 'Cash', 4, 1, 16, 0, NULL, 'IN_STORE', NULL, NULL, 57.67, 0.00, 0.00, 5),
|
||||
(135, '2026-04-10 11:30:00', 143.55, 'Card', 8, 2, 17, 0, NULL, 'ONLINE', NULL, NULL, 143.55, 0.00, 0.00, 14),
|
||||
(136, '2026-04-10 14:45:00', 50.09, 'Cash', 12, 3, 18, 0, NULL, 'IN_STORE', NULL, NULL, 50.09, 0.00, 0.00, 5),
|
||||
(137, '2026-04-11 10:00:00', 114.48, 'Card', 5, 1, 19, 0, NULL, 'ONLINE', NULL, NULL, 114.48, 0.00, 0.00, 11),
|
||||
(138, '2026-04-11 13:20:00', 93.55, 'Cash', 9, 2, 20, 0, NULL, 'IN_STORE', NULL, NULL, 93.55, 0.00, 0.00, 9),
|
||||
(139, '2026-04-12 09:45:00', 100.71, 'Card', 13, 3, 21, 0, NULL, 'ONLINE', NULL, NULL, 100.71, 0.00, 0.00, 10),
|
||||
(140, '2026-04-12 11:00:00', 51.07, 'Cash', 6, 1, 22, 0, NULL, 'IN_STORE', NULL, NULL, 51.07, 0.00, 0.00, 5),
|
||||
(141, '2026-04-12 15:30:00', 139.66, 'Card', 7, 2, 23, 0, NULL, 'ONLINE', NULL, NULL, 139.66, 0.00, 0.00, 13),
|
||||
(142, '2026-04-13 09:00:00', 73.98, 'Cash', 14, 3, 24, 0, NULL, 'IN_STORE', NULL, NULL, 73.98, 0.00, 0.00, 7),
|
||||
(143, '2026-04-13 12:15:00', 134.76, 'Card', 4, 1, 25, 0, NULL, 'ONLINE', NULL, NULL, 134.76, 0.00, 0.00, 13),
|
||||
(144, '2026-04-14 10:30:00', 80.40, 'Cash', 10, 2, 26, 0, NULL, 'IN_STORE', NULL, NULL, 80.40, 0.00, 0.00, 8),
|
||||
(145, '2026-04-14 14:00:00', 125.90, 'Card', 11, 3, 27, 0, NULL, 'ONLINE', NULL, NULL, 125.90, 0.00, 0.00, 12),
|
||||
(146, '2026-04-15 10:45:00', 80.62, 'Cash', 5, 1, 28, 0, NULL, 'IN_STORE', NULL, NULL, 80.62, 0.00, 0.00, 8),
|
||||
(147, '2026-04-15 13:00:00', 141.28, 'Card', 8, 2, 29, 0, NULL, 'ONLINE', NULL, NULL, 141.28, 0.00, 0.00, 14),
|
||||
(148, '2026-04-16 09:30:00', 97.85, 'Cash', 12, 3, 30, 0, NULL, 'IN_STORE', NULL, NULL, 97.85, 0.00, 0.00, 9),
|
||||
(149, '2026-04-16 11:45:00', 89.36, 'Card', 6, 1, 31, 0, NULL, 'ONLINE', NULL, NULL, 89.36, 0.00, 0.00, 8);
|
||||
|
||||
INSERT IGNORE INTO saleItem (saleItemId, saleId, prodId, quantity, unitPrice) VALUES
|
||||
(264, 134, 1, 2, 25.09),
|
||||
(265, 134, 11, 1, 7.49),
|
||||
(266, 135, 7, 2, 57.62),
|
||||
(267, 135, 25, 1, 28.31),
|
||||
(268, 136, 3, 1, 35.93),
|
||||
(269, 136, 14, 1, 14.16),
|
||||
(270, 137, 15, 3, 16.38),
|
||||
(271, 137, 26, 2, 32.67),
|
||||
(272, 138, 8, 1, 63.05),
|
||||
(273, 138, 22, 2, 15.25),
|
||||
(274, 139, 20, 2, 27.49),
|
||||
(275, 139, 29, 1, 45.73),
|
||||
(276, 140, 4, 1, 41.36),
|
||||
(277, 140, 12, 1, 9.71),
|
||||
(278, 141, 34, 2, 59.42),
|
||||
(279, 141, 17, 1, 20.82),
|
||||
(280, 142, 6, 1, 52.20),
|
||||
(281, 142, 21, 2, 10.89),
|
||||
(282, 143, 37, 1, 97.56),
|
||||
(283, 143, 16, 2, 18.60),
|
||||
(284, 144, 9, 1, 68.47),
|
||||
(285, 144, 13, 1, 11.93),
|
||||
(286, 145, 19, 3, 25.27),
|
||||
(287, 145, 30, 1, 50.09),
|
||||
(288, 146, 2, 2, 30.51),
|
||||
(289, 146, 23, 1, 19.60),
|
||||
(290, 147, 35, 1, 72.13),
|
||||
(291, 147, 18, 3, 23.05),
|
||||
(292, 148, 10, 1, 73.89),
|
||||
(293, 148, 24, 1, 23.96),
|
||||
(294, 149, 31, 2, 21.29),
|
||||
(295, 149, 5, 1, 46.78);
|
||||
@@ -8,6 +8,7 @@ import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.StoreRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.security.AppPrincipal;
|
||||
import com.petshop.backend.security.UserAuthCacheService;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -38,12 +39,13 @@ class UserServiceTest {
|
||||
@Mock private UserRepository userRepository;
|
||||
@Mock private PasswordEncoder passwordEncoder;
|
||||
@Mock private StoreRepository storeRepository;
|
||||
@Mock private UserAuthCacheService userAuthCacheService;
|
||||
|
||||
private UserService userService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
userService = new UserService(userRepository, passwordEncoder, storeRepository);
|
||||
userService = new UserService(userRepository, passwordEncoder, storeRepository, userAuthCacheService);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
||||
Reference in New Issue
Block a user