Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/com/ramsai/kitchen/KitchenApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/com/ramsai/kitchen/controllers/AuthController.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,27 @@ public ResponseEntity<Map<String, Object>> register(@Valid @RequestBody Register
}

@PostMapping("/login")
public ResponseEntity<Map<String, Object>> login(@Valid @RequestBody AuthenticationRequest request) {
AuthenticationResponse response = authService.authenticate(request);
public ResponseEntity<Map<String, Object>> 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<Map<String, Object>> guestLogin() {
AuthenticationResponse data = authService.guestLogin();
return ResponseEntity.ok(Map.of(
"data", data,
"message", "Guest login successful"
));
}

@PostMapping("/refresh")
public ResponseEntity<Map<String, Object>> refresh(@RequestBody Map<String, String> request) {
public ResponseEntity<Map<String, Object>> refresh(
@RequestBody Map<String, String> request) {
String refreshToken = request.get("refreshToken");
AuthenticationResponse response = authService.refreshToken(refreshToken);
return ResponseEntity.ok(Map.of(
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/ramsai/kitchen/models/entities/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ public boolean isEnabled() {
}

public enum UserRole {
CUSTOMER, WAITER, CHEF, MANAGER
CUSTOMER, GUEST, WAITER, CHEF, MANAGER
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,7 @@ public interface OrderRepository extends JpaRepository<Order, Long> {
"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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -12,4 +13,11 @@ public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);

List<User> findByRole(User.UserRole role);

@org.springframework.data.jpa.repository.Query(
"SELECT u FROM User u WHERE u.role = 'GUEST' AND u.createdAt < :threshold"
)
List<User> findInactiveGuests(@org.springframework.data.repository.query.Param("threshold") java.time.LocalDateTime threshold);
}
27 changes: 27 additions & 0 deletions src/main/java/com/ramsai/kitchen/services/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.UUID;

@Service
@RequiredArgsConstructor
Expand All @@ -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())) {
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/com/ramsai/kitchen/services/GuestCleanupService.java
Original file line number Diff line number Diff line change
@@ -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<User> 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.");
}
}
}
14 changes: 11 additions & 3 deletions src/main/resources/static/js/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -140,14 +140,22 @@ function updateUIForAuthenticatedUser(username, role) {
${role === 'MANAGER' ? '<li><a href="audit.html">Audit Logs</a></li>' : ''}
${role === 'CHEF' || role === 'MANAGER' ? `<li><a href="manager.html">${role === 'MANAGER' ? 'Manager' : 'Console'}</a></li>` : ''}
${role === 'CHEF' || role === 'MANAGER' ? '<li><a href="kitchen.html" id="navKitchen">Kitchen</a></li>' : ''}
${role === 'CUSTOMER' || role === 'WAITER' || role === 'MANAGER' ? '<li><a href="tables.html">Tables</a></li>' : ''}
${role === 'CUSTOMER' || role === 'WAITER' || role === 'MANAGER' ? '<li><a href="my-orders.html" id="navMyOrders">My Orders</a></li>' : ''}
${role === 'CUSTOMER' || role === 'GUEST' || role === 'WAITER' || role === 'MANAGER' ? '<li><a href="tables.html">Tables</a></li>' : ''}
${role === 'CUSTOMER' || role === 'GUEST' || role === 'WAITER' || role === 'MANAGER' ? '<li><a href="my-orders.html" id="navMyOrders">My Orders</a></li>' : ''}
<li><a href="#" id="logoutBtn">Logout (${username})</a></li>
<li><a href="order.html" id="cartLink"><i class="fas fa-shopping-cart"></i> <span id="cartCount">0</span></a></li>
`;

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';
Expand Down
31 changes: 31 additions & 0 deletions src/main/resources/static/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ <h2>Login</h2>
<label for="rememberMe" style="margin-bottom: 0;">Remember Me</label>
</div>
<button type="submit" class="auth-button">Login</button>
<div style="text-align: center; margin-top: 15px;">
<span style="color: var(--text-muted); font-size: 0.9rem;">or</span>
</div>
<button type="button" id="guestLoginBtn" class="auth-button" style="background-color: var(--secondary-color); margin-top: 15px;">Continue as Guest</button>
<div style="text-align: right; margin-top: 10px;">
<a href="#" id="forgotPasswordLink" style="font-size: 0.85rem; color: var(--primary-color); text-decoration: none;">Forgot Password?</a>
</div>
Expand Down Expand Up @@ -171,6 +175,33 @@ <h2>Login</h2>
}
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';
}
});
</script>
</body>
</html>
Loading