diff --git a/src/main/java/com/ramsai/kitchen/KitchenApplication.java b/src/main/java/com/ramsai/kitchen/KitchenApplication.java index e9b4457..57622d5 100644 --- a/src/main/java/com/ramsai/kitchen/KitchenApplication.java +++ b/src/main/java/com/ramsai/kitchen/KitchenApplication.java @@ -2,10 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; import java.util.TimeZone; @SpringBootApplication +@EnableScheduling public class KitchenApplication { public static void main(String[] args) { diff --git a/src/main/java/com/ramsai/kitchen/controllers/AuthController.java b/src/main/java/com/ramsai/kitchen/controllers/AuthController.java index e5acf21..372fed8 100644 --- a/src/main/java/com/ramsai/kitchen/controllers/AuthController.java +++ b/src/main/java/com/ramsai/kitchen/controllers/AuthController.java @@ -31,16 +31,27 @@ public ResponseEntity> register(@Valid @RequestBody Register } @PostMapping("/login") - public ResponseEntity> login(@Valid @RequestBody AuthenticationRequest request) { - AuthenticationResponse response = authService.authenticate(request); + public ResponseEntity> authenticate( + @RequestBody AuthenticationRequest request) { + AuthenticationResponse data = authService.authenticate(request); return ResponseEntity.ok(Map.of( - "data", response, + "data", data, "message", "Login successful" )); } + @PostMapping("/guest") + public ResponseEntity> guestLogin() { + AuthenticationResponse data = authService.guestLogin(); + return ResponseEntity.ok(Map.of( + "data", data, + "message", "Guest login successful" + )); + } + @PostMapping("/refresh") - public ResponseEntity> refresh(@RequestBody Map request) { + public ResponseEntity> refresh( + @RequestBody Map request) { String refreshToken = request.get("refreshToken"); AuthenticationResponse response = authService.refreshToken(refreshToken); return ResponseEntity.ok(Map.of( diff --git a/src/main/java/com/ramsai/kitchen/models/entities/User.java b/src/main/java/com/ramsai/kitchen/models/entities/User.java index e7ccf50..c2a3602 100644 --- a/src/main/java/com/ramsai/kitchen/models/entities/User.java +++ b/src/main/java/com/ramsai/kitchen/models/entities/User.java @@ -94,6 +94,6 @@ public boolean isEnabled() { } public enum UserRole { - CUSTOMER, WAITER, CHEF, MANAGER + CUSTOMER, GUEST, WAITER, CHEF, MANAGER } } diff --git a/src/main/java/com/ramsai/kitchen/repositories/OrderRepository.java b/src/main/java/com/ramsai/kitchen/repositories/OrderRepository.java index b8c6736..db462fe 100644 --- a/src/main/java/com/ramsai/kitchen/repositories/OrderRepository.java +++ b/src/main/java/com/ramsai/kitchen/repositories/OrderRepository.java @@ -52,4 +52,7 @@ public interface OrderRepository extends JpaRepository { "FROM Order o JOIN o.items i " + "WHERE o.customerId = :customerId AND i.product.id = :productId AND o.status = :status") boolean hasCustomerOrderedProduct(@Param("customerId") Long customerId, @Param("productId") Long productId, @Param("status") OrderStatus status); + + @Query("SELECT COUNT(o) FROM Order o WHERE o.customerId = :customerId AND o.status NOT IN ('COMPLETED', 'CANCELLED', 'SERVED')") + long countActiveOrdersByCustomerId(@Param("customerId") Long customerId); } diff --git a/src/main/java/com/ramsai/kitchen/repositories/UserRepository.java b/src/main/java/com/ramsai/kitchen/repositories/UserRepository.java index 2bb7281..e1830b0 100644 --- a/src/main/java/com/ramsai/kitchen/repositories/UserRepository.java +++ b/src/main/java/com/ramsai/kitchen/repositories/UserRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -12,4 +13,11 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); boolean existsByUsername(String username); boolean existsByEmail(String email); + + List findByRole(User.UserRole role); + + @org.springframework.data.jpa.repository.Query( + "SELECT u FROM User u WHERE u.role = 'GUEST' AND u.createdAt < :threshold" + ) + List findInactiveGuests(@org.springframework.data.repository.query.Param("threshold") java.time.LocalDateTime threshold); } diff --git a/src/main/java/com/ramsai/kitchen/services/AuthService.java b/src/main/java/com/ramsai/kitchen/services/AuthService.java index 11305ca..f308daa 100644 --- a/src/main/java/com/ramsai/kitchen/services/AuthService.java +++ b/src/main/java/com/ramsai/kitchen/services/AuthService.java @@ -15,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.UUID; @Service @RequiredArgsConstructor @@ -27,6 +28,32 @@ public class AuthService { private final AuditLogService auditLogService; private final AuthenticationManager authenticationManager; + @Transactional + public AuthenticationResponse guestLogin() { + String guestId = UUID.randomUUID().toString().substring(0, 8); + String username = "GUEST_" + guestId; + String email = "guest_" + guestId + "@temp.com"; + String password = UUID.randomUUID().toString(); + + User user = User.builder() + .username(username) + .passwordHash(passwordEncoder.encode(password)) + .email(email) + .role(User.UserRole.GUEST) + .isActive(true) + .isEmailVerified(false) + .createdAt(LocalDateTime.now()) + .build(); + + User savedUser = userRepository.save(user); + String jwtToken = jwtService.generateToken(savedUser); + RefreshToken refreshToken = refreshTokenService.createRefreshToken(savedUser.getId()); + + auditLogService.logAction(savedUser, "GUEST_LOGIN", "SUCCESS", "Guest account created successfully"); + + return new AuthenticationResponse(jwtToken, refreshToken.getToken(), savedUser.getUsername(), savedUser.getRole().name()); + } + @Transactional public AuthenticationResponse register(RegisterRequest request) { if (userRepository.existsByUsername(request.username())) { diff --git a/src/main/java/com/ramsai/kitchen/services/GuestCleanupService.java b/src/main/java/com/ramsai/kitchen/services/GuestCleanupService.java new file mode 100644 index 0000000..91ba672 --- /dev/null +++ b/src/main/java/com/ramsai/kitchen/services/GuestCleanupService.java @@ -0,0 +1,42 @@ +package com.ramsai.kitchen.services; + +import com.ramsai.kitchen.models.entities.User; +import com.ramsai.kitchen.repositories.OrderRepository; +import com.ramsai.kitchen.repositories.UserRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +@Service +@RequiredArgsConstructor +@Slf4j +public class GuestCleanupService { + + private final UserRepository userRepository; + private final OrderRepository orderRepository; + + /** + * Runs every hour to clean up guest accounts older than 24 hours + * that have no active orders. + */ + @Scheduled(cron = "0 0 * * * *") + @Transactional + public void cleanupInactiveGuests() { + log.info("Starting guest account cleanup task..."); + + LocalDateTime threshold = LocalDateTime.now().minusHours(24); + List inactiveGuests = userRepository.findInactiveGuests(threshold); + + if (!inactiveGuests.isEmpty()) { + userRepository.deleteAll(inactiveGuests); + log.info("Successfully cleaned up {} guest accounts older than 24 hours.", inactiveGuests.size()); + } else { + log.info("No inactive guest accounts found for cleanup."); + } + } +} diff --git a/src/main/resources/static/js/auth.js b/src/main/resources/static/js/auth.js index 0fa3028..29ef311 100644 --- a/src/main/resources/static/js/auth.js +++ b/src/main/resources/static/js/auth.js @@ -23,7 +23,7 @@ function checkAuth() { if (userRole === 'CHEF' || userRole === 'MANAGER') { initKitchenNotifications(); } - if (userRole === 'CUSTOMER' || userRole === 'WAITER' || userRole === 'MANAGER') { + if (userRole === 'CUSTOMER' || userRole === 'GUEST' || userRole === 'WAITER' || userRole === 'MANAGER') { initOrderNotifications(); } } @@ -140,14 +140,22 @@ function updateUIForAuthenticatedUser(username, role) { ${role === 'MANAGER' ? '
  • Audit Logs
  • ' : ''} ${role === 'CHEF' || role === 'MANAGER' ? `
  • ${role === 'MANAGER' ? 'Manager' : 'Console'}
  • ` : ''} ${role === 'CHEF' || role === 'MANAGER' ? '
  • Kitchen
  • ' : ''} - ${role === 'CUSTOMER' || role === 'WAITER' || role === 'MANAGER' ? '
  • Tables
  • ' : ''} - ${role === 'CUSTOMER' || role === 'WAITER' || role === 'MANAGER' ? '
  • My Orders
  • ' : ''} + ${role === 'CUSTOMER' || role === 'GUEST' || role === 'WAITER' || role === 'MANAGER' ? '
  • Tables
  • ' : ''} + ${role === 'CUSTOMER' || role === 'GUEST' || role === 'WAITER' || role === 'MANAGER' ? '
  • My Orders
  • ' : ''}
  • Logout (${username})
  • 0
  • `; document.getElementById('logoutBtn').addEventListener('click', (e) => { e.preventDefault(); + const storage = getStorage(); + const role = storage.getItem('role'); + + if (role === 'GUEST') { + const confirmed = confirm("Warning: You are logged in as a Guest. If you logout, you will lose access to this session and your order tracking. Do you want to proceed?"); + if (!confirmed) return; + } + localStorage.clear(); sessionStorage.clear(); window.location.href = 'index.html'; diff --git a/src/main/resources/static/login.html b/src/main/resources/static/login.html index d25e40e..dab6aae 100644 --- a/src/main/resources/static/login.html +++ b/src/main/resources/static/login.html @@ -113,6 +113,10 @@

    Login

    +
    + or +
    + @@ -171,6 +175,33 @@

    Login

    } alert('Feature coming soon: This will trigger a password reset request.'); }); + + document.getElementById('guestLoginBtn').addEventListener('click', async () => { + const errorDiv = document.getElementById('errorMessage'); + try { + const response = await fetch('/api/v1/auth/guest', { + method: 'POST', + headers: { 'Content-Type': 'application/json' } + }); + + const result = await response.json(); + + if (response.ok) { + // Guest session always persists in localStorage + localStorage.setItem('token', result.data.token); + localStorage.setItem('refreshToken', result.data.refreshToken); + localStorage.setItem('username', result.data.username); + localStorage.setItem('role', result.data.role); + window.location.href = 'index.html'; + } else { + errorDiv.textContent = result.message || 'Guest login failed'; + errorDiv.style.display = 'block'; + } + } catch (error) { + errorDiv.textContent = 'An error occurred. Please try again.'; + errorDiv.style.display = 'block'; + } + });