mirror of
https://gitlab.com/etc404/software-engineering-project.git
synced 2026-05-10 20:52:58 +00:00
explore page in progress
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
package com.example.demo.controller;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintViolationException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalValidationHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(ConstraintViolationException.class)
|
||||||
|
public ResponseEntity<Map<String, String>> handleConstraintViolation(ConstraintViolationException ex) {
|
||||||
|
Map<String, String> errors = new HashMap<>();
|
||||||
|
ex.getConstraintViolations().forEach(violation -> {
|
||||||
|
String fieldPath = violation.getPropertyPath().toString();
|
||||||
|
errors.put(fieldPath, violation.getMessage());
|
||||||
|
});
|
||||||
|
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||||
|
Map<String, String> errors = new HashMap<>();
|
||||||
|
ex.getBindingResult().getFieldErrors().forEach(error -> {
|
||||||
|
errors.put(error.getField(), error.getDefaultMessage());
|
||||||
|
});
|
||||||
|
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.example.demo.controller;
|
package com.example.demo.controller;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -13,20 +15,26 @@ import org.springframework.web.bind.annotation.RequestBody;
|
|||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
|
||||||
import com.example.demo.dto.UserDto;
|
import com.example.demo.dto.UserDto;
|
||||||
import com.example.demo.entity.User;
|
import com.example.demo.entity.User;
|
||||||
import com.example.demo.service.UserService;
|
import com.example.demo.service.UserService;
|
||||||
|
import com.example.demo.repository.UserRepo;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/users")
|
@RequestMapping("/api/users")
|
||||||
public class UserController {
|
public class UserController {
|
||||||
|
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
private UserRepo userRepo;
|
||||||
|
|
||||||
public UserController(UserService userService) {
|
public UserController(UserService userService, UserRepo userRepo) {
|
||||||
super();
|
super();
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
|
this.userRepo = userRepo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// build create user REST API
|
// build create user REST API
|
||||||
@@ -48,6 +56,16 @@ public class UserController {
|
|||||||
List<UserDto> users = userService.getUsersByName(string);
|
List<UserDto> users = userService.getUsersByName(string);
|
||||||
return new ResponseEntity<>(users, HttpStatus.OK);
|
return new ResponseEntity<>(users, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build get current user REST API
|
||||||
|
@GetMapping("/me")
|
||||||
|
public UserDto getLoggedInUser(Principal principal) {
|
||||||
|
if (principal == null) return null;
|
||||||
|
String username = principal.getName();
|
||||||
|
User user = (userRepo.findByUsername(username))
|
||||||
|
.orElse(null);
|
||||||
|
return userService.convertToDto(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// build get user by id REST API
|
// build get user by id REST API
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import jakarta.persistence.Entity;
|
|||||||
import jakarta.persistence.FetchType;
|
import jakarta.persistence.FetchType;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import jakarta.persistence.UniqueConstraint;
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.OneToMany;
|
import jakarta.persistence.OneToMany;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
@@ -23,7 +25,9 @@ public class Ingredient {
|
|||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
@Column(nullable = false, unique = true,columnDefinition = "TEXT")
|
||||||
|
@NotBlank(message = "Please provide an ingredient name")
|
||||||
|
@Size(max = 128, message = "Name cannot be longer than 128 characters")
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "ingredient")
|
@OneToMany(mappedBy = "ingredient")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank;
|
|||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Positive;
|
import jakarta.validation.constraints.Positive;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -19,18 +20,21 @@ public class Recipe {
|
|||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
@NotBlank(message = "Please provide a recipe title")
|
@NotBlank(message = "Please provide a recipe title")
|
||||||
|
@Size(max = 128, message = "Title cannot be longer than 128 characters")
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
|
@Size(max = 500, message = "Description cannot be longer than 500 characters")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
@NotNull(message = "Please Provide a prep time amount in mintutes")
|
@NotNull(message = "Please Provide a prep time amount in minutes")
|
||||||
@Positive(message = "This value cannot be negative")
|
@Positive(message = "This value cannot be negative")
|
||||||
private Integer prepTimeMinutes;
|
private Integer prepTimeMinutes;
|
||||||
|
|
||||||
@NotNull(message = "Please Provide a cook time amount in mintutes")
|
@NotNull(message = "Please Provide a cook time amount in minutes")
|
||||||
@Positive(message = "This value cannot be negative")
|
@Positive(message = "This value cannot be negative")
|
||||||
private Integer cookTimeMinutes;
|
private Integer cookTimeMinutes;
|
||||||
|
|
||||||
@@ -51,11 +55,14 @@ public class Recipe {
|
|||||||
|
|
||||||
// Recipe ingredients relationship
|
// Recipe ingredients relationship
|
||||||
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
@NotEmpty(message = "At least one ingredient is required")
|
@NotEmpty(message = "At least one ingredient is required")
|
||||||
|
@Size(max = 256, message = "Cannot have more than 256 ingredients")
|
||||||
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
|
private Set<RecipeIngredient> recipeIngredients = new HashSet<>();
|
||||||
|
|
||||||
// Recipe Steps relationship
|
// Recipe Steps relationship
|
||||||
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
|
@NotEmpty(message = "At least one step is required")
|
||||||
|
@Size(max = 50, message = "Cannot have more than 50 steps")
|
||||||
private Set<Step> steps = new HashSet<>();
|
private Set<Step> steps = new HashSet<>();
|
||||||
|
|
||||||
// Recipe Images relationship
|
// Recipe Images relationship
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package com.example.demo.entity;
|
package com.example.demo.entity;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Positive;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@@ -23,11 +27,17 @@ public class RecipeIngredient {
|
|||||||
@JoinColumn(name = "ingredient_id", nullable = false)
|
@JoinColumn(name = "ingredient_id", nullable = false)
|
||||||
@EqualsAndHashCode.Include
|
@EqualsAndHashCode.Include
|
||||||
private Ingredient ingredient;
|
private Ingredient ingredient;
|
||||||
|
|
||||||
|
@NotNull(message = "Please Provide a Quantity")
|
||||||
|
@Positive(message = "Quantity cannot be negative")
|
||||||
private BigDecimal quantity;
|
private BigDecimal quantity;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
@Size(max = 32, message = "Unit cannot be longer than 32 characters")
|
||||||
private String unit;
|
private String unit;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
@Size(max = 128, message = "Note cannot be longer than 128 characters")
|
||||||
private String notes;
|
private String notes;
|
||||||
|
|
||||||
public RecipeIngredient() {
|
public RecipeIngredient() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.demo.entity;
|
package com.example.demo.entity;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -15,6 +16,7 @@ public class Step {
|
|||||||
private Integer stepNumber;
|
private Integer stepNumber;
|
||||||
|
|
||||||
@Column(name = "instruction", nullable = false, columnDefinition = "TEXT")
|
@Column(name = "instruction", nullable = false, columnDefinition = "TEXT")
|
||||||
|
@Size(max = 500, message = "Instruction cannot be longer than 500 characters")
|
||||||
private String instruction;
|
private String instruction;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import jakarta.persistence.CascadeType;
|
|||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -53,6 +55,7 @@ public class User implements UserDetails {
|
|||||||
|
|
||||||
// Favorite relationship and also junction table
|
// Favorite relationship and also junction table
|
||||||
@ManyToMany(fetch = FetchType.LAZY)
|
@ManyToMany(fetch = FetchType.LAZY)
|
||||||
|
@JsonIgnore
|
||||||
@JoinTable(name = "favorites", joinColumns = { @JoinColumn(name = "userId") }, inverseJoinColumns = {
|
@JoinTable(name = "favorites", joinColumns = { @JoinColumn(name = "userId") }, inverseJoinColumns = {
|
||||||
@JoinColumn(name = "recipeId") })
|
@JoinColumn(name = "recipeId") })
|
||||||
private Set<Recipe> FavRecipes = new HashSet<>();
|
private Set<Recipe> FavRecipes = new HashSet<>();
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Create Thyme Crunch Account</title>
|
|
||||||
<link rel="stylesheet" th:href="@{css/create-account.css}">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Delius+Swash+Caps&family=Mali:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<header class="top-header">
|
|
||||||
<img th:src="@{images/header_left.png}" alt="Violin f-hole shape to the left of header." class="swirl">
|
|
||||||
<h1 class="site-name">Thyme Crunch</h1>
|
|
||||||
<img th:src="@{images/header_right.png}" alt="Violin f-hole shape to the right of header." class="swirl">
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<main class="main-content">
|
|
||||||
<div class="login-box">
|
|
||||||
<h2>Create Account</h2>
|
|
||||||
|
|
||||||
<form id="createUserForm">
|
|
||||||
<div class="rows">
|
|
||||||
<label for="username">Username</label>
|
|
||||||
<input type="text" id="username" required>
|
|
||||||
</div>
|
|
||||||
<div class="rows">
|
|
||||||
<label for="email">Email</label>
|
|
||||||
<input type="email" id="email" required>
|
|
||||||
</div>
|
|
||||||
<div class="rows">
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<input type="password" id="password" required>
|
|
||||||
</div>
|
|
||||||
<div class="rows">
|
|
||||||
<label for="confirmPassword">Confirm Password</label>
|
|
||||||
<input type="password" id="confirmPassword" required>
|
|
||||||
</div>
|
|
||||||
<p id="passwordError"></p>
|
|
||||||
|
|
||||||
<button type="submit">Create</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
|
|
||||||
const passwordField = document.getElementById("password");
|
|
||||||
const confirmPasswordField = document.getElementById("confirmPassword");
|
|
||||||
|
|
||||||
function checkPasswords() {
|
|
||||||
|
|
||||||
if (confirmPasswordField.value === "") {
|
|
||||||
confirmPasswordField.classList.remove("invalid");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (passwordField.value !== confirmPasswordField.value) {
|
|
||||||
confirmPasswordField.classList.add("invalid");
|
|
||||||
} else {
|
|
||||||
confirmPasswordField.classList.remove("invalid");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordField.addEventListener("input", checkPasswords);
|
|
||||||
confirmPasswordField.addEventListener("input", checkPasswords);
|
|
||||||
|
|
||||||
document.getElementById("createUserForm").addEventListener("submit", function(e) {
|
|
||||||
|
|
||||||
const password = passwordField.value;
|
|
||||||
const confirmPassword = confirmPasswordField.value;
|
|
||||||
|
|
||||||
if (password !== confirmPassword) {
|
|
||||||
e.preventDefault();
|
|
||||||
confirmPasswordField.classList.add("invalid");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userData = {
|
|
||||||
username: document.getElementById("username").value,
|
|
||||||
email: document.getElementById("email").value,
|
|
||||||
hashedpassword: password,
|
|
||||||
role: "USER"
|
|
||||||
};
|
|
||||||
|
|
||||||
fetch("http://localhost:8080/api/users", {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify(userData)
|
|
||||||
});
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>
|
||||||
|
<meta name="_csrf" th:content="${_csrf.token}"/>
|
||||||
<title>Create Thyme Crunch Recipe</title>
|
<title>Create Thyme Crunch Recipe</title>
|
||||||
<link rel="stylesheet" th:href="@{css/create-recipe.css}">
|
<link rel="stylesheet" th:href="@{css/create-recipe.css}">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Delius+Swash+Caps&family=Mali:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Delius+Swash+Caps&family=Mali:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700" rel="stylesheet">
|
||||||
@@ -226,6 +228,117 @@ function renderTags() {
|
|||||||
btn.addEventListener('click', () => removeTag(btn.dataset.tag));
|
btn.addEventListener('click', () => removeTag(btn.dataset.tag));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getLoggedInUser() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('http://localhost:8080/api/users/me', {
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error('Failed to get logged-in user');
|
||||||
|
return await res.json();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error fetching user:', err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRecipeJSON(user) {
|
||||||
|
const title = document.getElementById('title').value.trim();
|
||||||
|
const description = document.getElementById('desc').value.trim();
|
||||||
|
const prepTimeMinutes = Number(document.getElementById('prep').value);
|
||||||
|
const cookTimeMinutes = Number(document.getElementById('cooking').value);
|
||||||
|
const servings = Number(document.getElementById('servings').value);
|
||||||
|
const status = "DRAFT";
|
||||||
|
|
||||||
|
// Ingredients
|
||||||
|
const recipeIngredients = [...document.querySelectorAll('#ingredients-container .dynamic-row')]
|
||||||
|
.map(row => {
|
||||||
|
const qtyValue = Number(row.querySelector('.ing-qty').value.trim());
|
||||||
|
return {
|
||||||
|
ingredient: { name: row.querySelector('.ing-name').value.trim() },
|
||||||
|
quantity: qtyValue,
|
||||||
|
unit: row.querySelector('.ing-unit').value.trim(),
|
||||||
|
notes: row.querySelector('.ing-notes').value.trim()
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(item => item.ingredient.name);
|
||||||
|
|
||||||
|
// Steps
|
||||||
|
const steps = [...document.querySelectorAll('#steps-container textarea')]
|
||||||
|
.map((el, i) => ({ stepNumber: i + 1, instruction: el.value.trim() }))
|
||||||
|
.filter(item => item.instruction);
|
||||||
|
|
||||||
|
// Images
|
||||||
|
|
||||||
|
|
||||||
|
// Tags
|
||||||
|
const tagsInput = tags;
|
||||||
|
const tagsArray = tagsInput.map(t => ({ name: t }));
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
prepTimeMinutes,
|
||||||
|
cookTimeMinutes,
|
||||||
|
servings,
|
||||||
|
status,
|
||||||
|
user,
|
||||||
|
recipeIngredients,
|
||||||
|
steps,
|
||||||
|
//images,
|
||||||
|
tags: tagsArray
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById('publish-btn').addEventListener('click', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const user = await getLoggedInUser();
|
||||||
|
if (!user) {
|
||||||
|
alert("Unable to fetch logged-in user. Please refresh and try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Logged-in user:", user);
|
||||||
|
const recipeJSON = buildRecipeJSON(user);
|
||||||
|
console.log("Recipe JSON to submit:", JSON.stringify(recipeJSON, null, 2));
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content');
|
||||||
|
const csrfHeader = document.querySelector('meta[name="_csrf_header"]').getAttribute('content');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const res = await fetch('http://localhost:8080/api/recipes', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
[csrfHeader]: csrfToken,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(recipeJSON),
|
||||||
|
credentials: 'include'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
console.log("Recipe created:", data);
|
||||||
|
alert("Recipe created successfully!");
|
||||||
|
} else {
|
||||||
|
const errorData = await res.json();
|
||||||
|
console.error("Validation errors:", errorData);
|
||||||
|
|
||||||
|
|
||||||
|
const firstError = Object.values(errorData)[0];
|
||||||
|
alert(firstError);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Network error:", err);
|
||||||
|
alert("Network error. Check console for details.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user