mirror of
https://gitlab.com/etc404/software-engineering-project.git
synced 2026-05-10 20:52:58 +00:00
Merge branch 'main' of gitlab.com:etc404/software-engineering-project
This commit is contained in:
@@ -1,5 +1,50 @@
|
||||
package com.example.demo.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
|
||||
http
|
||||
// Uses your CorsConfigurationSource bean from CorsConfig.java
|
||||
.cors(Customizer.withDefaults())
|
||||
|
||||
// For now, disable CSRF so you can POST from a separate frontend easily.
|
||||
// If you later use cookies/sessions, revisit CSRF.
|
||||
.csrf(csrf -> csrf.disable())
|
||||
|
||||
// Auth rules
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// Allow health check + auth endpoints without login
|
||||
.requestMatchers("/api/health").permitAll()
|
||||
.requestMatchers("/api/auth/**").permitAll()
|
||||
|
||||
// Everything else requires authentication (you can loosen this later)
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
|
||||
// Temporary: enables basic auth popup in browser tools.
|
||||
// Later you’ll likely switch to JWT or session login.
|
||||
.httpBasic(Customizer.withDefaults());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,28 @@
|
||||
package com.example.demo.controller;
|
||||
|
||||
import com.example.demo.dto.LoginRequest;
|
||||
import com.example.demo.dto.RegisterRequest;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
// TEMP: Register endpoint (service logic added later)
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
|
||||
|
||||
// For now just return what was sent (test validation first)
|
||||
return ResponseEntity.ok("User registered: " + request.getUsername());
|
||||
}
|
||||
|
||||
// TEMP: Login endpoint
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
|
||||
|
||||
return ResponseEntity.ok("Login attempt for: " + request.getUsernameOrEmail());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,18 @@
|
||||
package com.example.demo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class LoginRequest {
|
||||
|
||||
@NotBlank(message = "usernameOrEmail is required")
|
||||
private String usernameOrEmail;
|
||||
|
||||
@NotBlank(message = "password is required")
|
||||
private String password;
|
||||
|
||||
public String getUsernameOrEmail() { return usernameOrEmail; }
|
||||
public void setUsernameOrEmail(String usernameOrEmail) { this.usernameOrEmail = usernameOrEmail; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
package com.example.demo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.PositiveOrZero;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.List;
|
||||
|
||||
public class RecipeCreateRequest {
|
||||
|
||||
@NotBlank(message = "title is required")
|
||||
@Size(max = 100, message = "title must be 100 characters or less")
|
||||
private String title;
|
||||
|
||||
@Size(max = 1000, message = "description must be 1000 characters or less")
|
||||
private String description;
|
||||
|
||||
@PositiveOrZero(message = "prepTimeMinutes must be 0 or greater")
|
||||
private int prepTimeMinutes;
|
||||
|
||||
@PositiveOrZero(message = "cookTimeMinutes must be 0 or greater")
|
||||
private int cookTimeMinutes;
|
||||
|
||||
@PositiveOrZero(message = "servings must be 0 or greater")
|
||||
private int servings;
|
||||
|
||||
@NotNull(message = "ingredients list is required")
|
||||
private List<IngredientDto> ingredients;
|
||||
|
||||
@NotNull(message = "steps list is required")
|
||||
private List<StepDto> steps;
|
||||
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
|
||||
public int getPrepTimeMinutes() { return prepTimeMinutes; }
|
||||
public void setPrepTimeMinutes(int prepTimeMinutes) { this.prepTimeMinutes = prepTimeMinutes; }
|
||||
|
||||
public int getCookTimeMinutes() { return cookTimeMinutes; }
|
||||
public void setCookTimeMinutes(int cookTimeMinutes) { this.cookTimeMinutes = cookTimeMinutes; }
|
||||
|
||||
public int getServings() { return servings; }
|
||||
public void setServings(int servings) { this.servings = servings; }
|
||||
|
||||
public List<IngredientDto> getIngredients() { return ingredients; }
|
||||
public void setIngredients(List<IngredientDto> ingredients) { this.ingredients = ingredients; }
|
||||
|
||||
public List<StepDto> getSteps() { return steps; }
|
||||
public void setSteps(List<StepDto> steps) { this.steps = steps; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
package com.example.demo.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class RegisterRequest {
|
||||
|
||||
@NotBlank(message = "username is required")
|
||||
@Size(min = 3, max = 30, message = "username must be 3-30 characters")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "email is required")
|
||||
@Email(message = "email must be valid")
|
||||
private String email;
|
||||
|
||||
@NotBlank(message = "password is required")
|
||||
@Size(min = 8, max = 100, message = "password must be at least 8 characters")
|
||||
private String password;
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.example.demo.exception;
|
||||
|
||||
public class BadRequestException {
|
||||
|
||||
}
|
||||
@SuppressWarnings("serial")
|
||||
public class BadRequestException extends RuntimeException {
|
||||
public BadRequestException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,25 @@
|
||||
package com.example.demo.exception;
|
||||
|
||||
public class ErrorResponse {
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
}
|
||||
public class ErrorResponse {
|
||||
private LocalDateTime timestamp;
|
||||
private int status;
|
||||
private String error;
|
||||
private String message;
|
||||
private String path;
|
||||
|
||||
public ErrorResponse(LocalDateTime timestamp, int status, String error, String message, String path) {
|
||||
this.timestamp = timestamp;
|
||||
this.status = status;
|
||||
this.error = error;
|
||||
this.message = message;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public int getStatus() { return status; }
|
||||
public String getError() { return error; }
|
||||
public String getMessage() { return message; }
|
||||
public String getPath() { return path; }
|
||||
}
|
||||
@@ -1,5 +1,57 @@
|
||||
package com.example.demo.exception;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(NotFoundException.class)
|
||||
public ResponseEntity<ErrorResponse> handleNotFound(NotFoundException ex, HttpServletRequest request) {
|
||||
return buildError(HttpStatus.NOT_FOUND, ex.getMessage(), request.getRequestURI());
|
||||
}
|
||||
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public ResponseEntity<ErrorResponse> handleBadRequest(BadRequestException ex, HttpServletRequest request) {
|
||||
return buildError(HttpStatus.BAD_REQUEST, ex.getMessage(), request.getRequestURI());
|
||||
}
|
||||
|
||||
// Handles @Valid validation failures from DTOs
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex,
|
||||
HttpServletRequest request) {
|
||||
|
||||
String msg = ex.getBindingResult()
|
||||
.getFieldErrors()
|
||||
.stream()
|
||||
.map(err -> err.getField() + ": " + err.getDefaultMessage())
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
return buildError(HttpStatus.BAD_REQUEST, msg, request.getRequestURI());
|
||||
}
|
||||
|
||||
// Fallback for anything you didn't explicitly handle
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex, HttpServletRequest request) {
|
||||
// In production you'd avoid returning raw exception messages.
|
||||
return buildError(HttpStatus.INTERNAL_SERVER_ERROR, "Unexpected error occurred", request.getRequestURI());
|
||||
}
|
||||
|
||||
private ResponseEntity<ErrorResponse> buildError(HttpStatus status, String message, String path) {
|
||||
ErrorResponse body = new ErrorResponse(
|
||||
LocalDateTime.now(),
|
||||
status.value(),
|
||||
status.getReasonPhrase(),
|
||||
message,
|
||||
path
|
||||
);
|
||||
return ResponseEntity.status(status).body(body);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package com.example.demo.exception;
|
||||
|
||||
public class NotFoundException {
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class NotFoundException extends RuntimeException {
|
||||
public NotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user