verification update

This commit is contained in:
durn
2026-04-29 12:14:28 -06:00
5 changed files with 99 additions and 61 deletions
@@ -71,10 +71,18 @@ public class SiteController {
@GetMapping("/recipes/{id}")
public String viewRecipe(@PathVariable Integer id, Model model, Principal principal) {
RecipeDto recipe = recipeService.getRecipeById(id);
boolean saved = false;
if (principal != null) {
saved = userService.isFavorite(principal.getName(), id);
}
model.addAttribute("recipe", recipe);
model.addAttribute("guest", principal == null);
model.addAttribute("saved", saved);
return "view-recipe";
}
}
@GetMapping("/recipes/{id}/edit")
public String viewEditRecipePage(@PathVariable Integer id, Model model) {
@@ -17,6 +17,9 @@ import com.example.demo.repository.RecipeRepo;
import com.example.demo.repository.UserRepo;
import com.example.demo.service.RecipeService;
import com.example.demo.service.UserService;
import com.example.demo.entity.Favorite;
import com.example.demo.entity.FavoriteId;
import com.example.demo.repository.FavoriteRepo;
import jakarta.transaction.Transactional;
@@ -27,14 +30,16 @@ public class UserServiceImpl implements UserService {
private RecipeRepo recipeRepository;
private PasswordEncoder passwordEncoder;
private RecipeService recipeService;
private FavoriteRepo favoriteRepository;
public UserServiceImpl(UserRepo userRepository, RecipeRepo recipeRepository,
PasswordEncoder passwordEncoder, RecipeService recipeService) {
PasswordEncoder passwordEncoder, RecipeService recipeService, FavoriteRepo favoriteRepository) {
super();
this.userRepository = userRepository;
this.recipeRepository = recipeRepository;
this.passwordEncoder = passwordEncoder;
this.recipeService = recipeService;
this.favoriteRepository = favoriteRepository;
}
@Override
@@ -78,13 +83,16 @@ public class UserServiceImpl implements UserService {
@Transactional
public UserDto saveFavorite(Integer userId, Integer recipeId) {
User existingUser = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException("User", "id", userId));
.orElseThrow(() -> new NotFoundException("User", "id", userId));
Recipe existingRecipe = recipeRepository.findById(recipeId)
.orElseThrow(() -> new NotFoundException("Recipe", "id", recipeId));
recipeRepository.findById(recipeId)
.orElseThrow(() -> new NotFoundException("Recipe", "id", recipeId));
existingUser.getFavRecipes().add(existingRecipe);
userRepository.save(existingUser);
FavoriteId favoriteId = new FavoriteId(userId, recipeId);
if (!favoriteRepository.existsById(favoriteId)) {
favoriteRepository.save(new Favorite(userId, recipeId));
}
return convertToDto(existingUser);
}
@@ -112,14 +120,17 @@ public class UserServiceImpl implements UserService {
@Override
@Transactional
public void deleteFavorite(Integer userId, Integer recipeId) {
User existingUser = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException("User", "id", userId));
userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException("User", "id", userId));
Recipe existingRecipe = recipeRepository.findById(recipeId)
.orElseThrow(() -> new NotFoundException("Recipe", "id", recipeId));
recipeRepository.findById(recipeId)
.orElseThrow(() -> new NotFoundException("Recipe", "id", recipeId));
existingUser.getFavRecipes().remove(existingRecipe);
userRepository.save(existingUser);
FavoriteId favoriteId = new FavoriteId(userId, recipeId);
if (favoriteRepository.existsById(favoriteId)) {
favoriteRepository.deleteById(favoriteId);
}
}
@Override
@@ -235,4 +246,13 @@ public class UserServiceImpl implements UserService {
return favoriteDtos;
}
@Override
@Transactional
public boolean isFavorite(String username, Integer recipeId) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new NotFoundException("User", "username", username));
return favoriteRepository.existsById(new FavoriteId(user.getId(), recipeId));
}
}
@@ -42,4 +42,6 @@ public interface UserService {
ProfileDto updateProfile(String username, UpdateProfileDto dto);
List<RecipeDto> getFavoriteRecipesByUsername(String username);
boolean isFavorite(String username, Integer recipeId);
}
+1 -1
View File
@@ -40,7 +40,7 @@
<button onclick="verifyOtp()">Verify</button>
<p class="resend-text">
Didnt get the code?
Didnt get the code? Check your spam folder or wait a minute and try again.
<button id="resendBtn" onclick="resendOtp()" disabled>
Resend code (60s)
</button>
+55 -47
View File
@@ -119,7 +119,13 @@
</div>
</section>
<button type="button" class="save-btn" th:data-recipe-id="${recipe.id}">Save Recipe</button>
<button type="button"
class="save-btn"
th:data-recipe-id="${recipe.id}"
th:data-saved="${saved}"
th:text="${saved} ? 'Remove Saved Recipe' : 'Save Recipe'">
Save Recipe
</button>
</div>
<div th:unless="${recipe != null}" class="recipe-not-found">
@@ -131,55 +137,57 @@
</div>
<script>
async function getCurrentUser() {
const response = await fetch('/api/users/me', {
credentials: 'same-origin'
});
if (!response.ok) {
return null;
}
const user = await response.json();
return user || null;
}
async function saveRecipe(recipeId, button) {
const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.content;
const csrfToken = document.querySelector('meta[name="_csrf"]')?.content;
const user = await getCurrentUser();
if (!user || !user.id) {
window.location.href = '/login';
return;
}
const response = await fetch(`/api/users/${user.id}/favorites/${recipeId}`, {
method: 'POST',
credentials: 'same-origin',
headers: {
...(csrfHeader && csrfToken ? { [csrfHeader]: csrfToken } : {})
async function getCurrentUser() {
const response = await fetch('/api/users/me', {
credentials: 'same-origin'
});
if (!response.ok) {
return null;
}
});
if (!response.ok) {
const text = await response.text();
alert(text || 'Could not save recipe.');
return;
const user = await response.json();
return user || null;
}
button.textContent = 'Saved';
button.disabled = true;
}
document.querySelectorAll('.save-btn').forEach(button => {
button.addEventListener('click', async () => {
const recipeId = button.dataset.recipeId;
await saveRecipe(recipeId, button);
async function toggleSaveRecipe(recipeId, button) {
const csrfHeader = document.querySelector('meta[name="_csrf_header"]')?.content;
const csrfToken = document.querySelector('meta[name="_csrf"]')?.content;
const user = await getCurrentUser();
if (!user || !user.id) {
window.location.href = '/login';
return;
}
const isSaved = button.dataset.saved === 'true';
const response = await fetch(`/api/users/${user.id}/favorites/${recipeId}`, {
method: isSaved ? 'DELETE' : 'POST',
credentials: 'same-origin',
headers: {
...(csrfHeader && csrfToken ? { [csrfHeader]: csrfToken } : {})
}
});
if (!response.ok) {
const text = await response.text();
alert(text || 'Could not update saved recipe.');
return;
}
button.dataset.saved = isSaved ? 'false' : 'true';
button.textContent = isSaved ? 'Save Recipe' : 'Un-save Recipe';
}
document.querySelectorAll('.save-btn').forEach(button => {
button.addEventListener('click', async () => {
const recipeId = button.dataset.recipeId;
await toggleSaveRecipe(recipeId, button);
});
});
});
</script>
</script>
</body>
</html>