Further updates on 03-11-2025
This commit is contained in:
@ -62,15 +62,20 @@ public class CourseController {
|
||||
course.setImageUrl(courseDto.getImageUrl());
|
||||
course.setEligibility(courseDto.getEligibility());
|
||||
course.setObjectives(courseDto.getObjectives());
|
||||
course.setActive(courseDto.isActive());
|
||||
|
||||
// FIXED: Use getIsActive() and setIsActive() for both DTO and Entity
|
||||
// Handle null case - default to true
|
||||
Boolean isActiveValue = courseDto.getIsActive() != null ? courseDto.getIsActive() : true;
|
||||
course.setIsActive(isActiveValue);
|
||||
|
||||
courseRepository.save(course);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).build();
|
||||
Course savedCourse = courseRepository.save(course);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(savedCourse);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<?> updateCourse(@PathVariable Long id, @RequestBody CourseDto courseDto) {
|
||||
Course course = courseRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Course not found"));
|
||||
Course course = courseRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Course not found"));
|
||||
|
||||
course.setTitle(courseDto.getTitle());
|
||||
course.setDescription(courseDto.getDescription());
|
||||
@ -84,10 +89,14 @@ public class CourseController {
|
||||
course.setImageUrl(courseDto.getImageUrl());
|
||||
course.setEligibility(courseDto.getEligibility());
|
||||
course.setObjectives(courseDto.getObjectives());
|
||||
course.setActive(courseDto.isActive());
|
||||
|
||||
// FIXED: Use getIsActive() and setIsActive() for both DTO and Entity
|
||||
// Handle null case - default to true
|
||||
Boolean isActiveValue = courseDto.getIsActive() != null ? courseDto.getIsActive() : true;
|
||||
course.setIsActive(isActiveValue);
|
||||
|
||||
courseRepository.save(course);
|
||||
return ResponseEntity.ok().build();
|
||||
Course updatedCourse = courseRepository.save(course);
|
||||
return ResponseEntity.ok(updatedCourse);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
|
||||
@ -22,7 +22,6 @@ public class JobController {
|
||||
@Autowired
|
||||
private JobRepository jobRepository;
|
||||
|
||||
// Get all active jobs (for public display)
|
||||
@GetMapping("/active")
|
||||
public ResponseEntity<List<Job>> getActiveJobs() {
|
||||
try {
|
||||
@ -33,13 +32,11 @@ public class JobController {
|
||||
}
|
||||
}
|
||||
|
||||
// Get all jobs (for admin)
|
||||
@GetMapping
|
||||
public List<Job> getAllJobs() {
|
||||
return jobRepository.findAll();
|
||||
}
|
||||
|
||||
// Get a single job by ID
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Job> getJobById(@PathVariable Long id) {
|
||||
return jobRepository.findById(id)
|
||||
@ -51,8 +48,7 @@ public class JobController {
|
||||
public ResponseEntity<?> createJob(@RequestBody JobDto jobDto) {
|
||||
System.out.println("=== BACKEND DEBUG ===");
|
||||
System.out.println("Received JobDto: " + jobDto);
|
||||
System.out.println("isActive value: " + jobDto.isActive());
|
||||
// Remove the .getClass() line since boolean is a primitive
|
||||
System.out.println("isActive value: " + jobDto.getIsActive());
|
||||
|
||||
Job job = new Job();
|
||||
job.setTitle(jobDto.getTitle());
|
||||
@ -64,19 +60,22 @@ public class JobController {
|
||||
job.setDescription(jobDto.getDescription());
|
||||
job.setRequirements(jobDto.getRequirements());
|
||||
job.setResponsibilities(jobDto.getResponsibilities());
|
||||
job.setActive(jobDto.isActive());
|
||||
|
||||
Boolean isActiveValue = jobDto.getIsActive() != null ? jobDto.getIsActive() : true;
|
||||
job.setIsActive(isActiveValue);
|
||||
|
||||
System.out.println("Job before save - isActive: " + job.isActive());
|
||||
System.out.println("Job before save - isActive: " + job.getIsActive());
|
||||
|
||||
Job savedJob = jobRepository.save(job);
|
||||
System.out.println("Job after save - isActive: " + savedJob.isActive());
|
||||
System.out.println("Job after save - isActive: " + savedJob.getIsActive());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.CREATED).build();
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(savedJob);
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<?> updateJob(@PathVariable Long id, @RequestBody JobDto jobDto) {
|
||||
Job job = jobRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Job not found"));
|
||||
Job job = jobRepository.findById(id)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Job not found"));
|
||||
|
||||
job.setTitle(jobDto.getTitle());
|
||||
job.setDepartment(jobDto.getDepartment());
|
||||
@ -87,10 +86,12 @@ public class JobController {
|
||||
job.setDescription(jobDto.getDescription());
|
||||
job.setRequirements(jobDto.getRequirements());
|
||||
job.setResponsibilities(jobDto.getResponsibilities());
|
||||
job.setActive(jobDto.isActive());
|
||||
|
||||
Boolean isActiveValue = jobDto.getIsActive() != null ? jobDto.getIsActive() : true;
|
||||
job.setIsActive(isActiveValue);
|
||||
|
||||
jobRepository.save(job);
|
||||
return ResponseEntity.ok().build();
|
||||
Job updatedJob = jobRepository.save(job);
|
||||
return ResponseEntity.ok(updatedJob);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
|
||||
@ -0,0 +1,63 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.controller;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.Milestone;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.MilestoneDTO;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.MilestoneService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/milestones")
|
||||
@RequiredArgsConstructor
|
||||
@CrossOrigin(origins = {"http://localhost:4200", "http://localhost:3000"})
|
||||
public class MilestoneController {
|
||||
|
||||
private final MilestoneService milestoneService;
|
||||
|
||||
// Public endpoint - for user interface
|
||||
@GetMapping("/public")
|
||||
public ResponseEntity<List<Milestone>> getActiveMilestones() {
|
||||
return ResponseEntity.ok(milestoneService.getActiveMilestones());
|
||||
}
|
||||
|
||||
// Admin endpoints - NO SECURITY (matching EventController pattern)
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Milestone>> getAllMilestones() {
|
||||
return ResponseEntity.ok(milestoneService.getAllMilestones());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Milestone> getMilestoneById(@PathVariable Long id) {
|
||||
return ResponseEntity.ok(milestoneService.getMilestoneById(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Milestone> createMilestone(@Valid @RequestBody MilestoneDTO milestoneDTO) {
|
||||
return ResponseEntity.status(HttpStatus.CREATED)
|
||||
.body(milestoneService.createMilestone(milestoneDTO));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<Milestone> updateMilestone(
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody MilestoneDTO milestoneDTO) {
|
||||
return ResponseEntity.ok(milestoneService.updateMilestone(id, milestoneDTO));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteMilestone(@PathVariable Long id) {
|
||||
milestoneService.deleteMilestone(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
// @PutMapping("/reorder")
|
||||
// public ResponseEntity<Void> reorderMilestones(@RequestBody List<Long> orderedIds) {
|
||||
// milestoneService.reorderMilestones(orderedIds);
|
||||
// return ResponseEntity.ok().build();
|
||||
// }
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.shyshkin.study.fullstack.supportportal.controller;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Simple test controller to verify Spring Boot is working
|
||||
* Test URL: http://localhost:8080/api/test
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class TestController {
|
||||
|
||||
@GetMapping("/test")
|
||||
public Map<String, String> test() {
|
||||
Map<String, String> response = new HashMap<>();
|
||||
response.put("status", "success");
|
||||
response.put("message", "Spring Boot is working!");
|
||||
response.put("timestamp", String.valueOf(System.currentTimeMillis()));
|
||||
return response;
|
||||
}
|
||||
|
||||
@GetMapping("/health")
|
||||
public String health() {
|
||||
return "Application is healthy!";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.controller;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.Testimonial;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.TestimonialDTO;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.TestimonialService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/testimonials")
|
||||
@RequiredArgsConstructor
|
||||
@CrossOrigin(origins = {"http://localhost:4200", "http://localhost:3000"})
|
||||
public class TestimonialController {
|
||||
|
||||
private final TestimonialService testimonialService;
|
||||
|
||||
// Public endpoint - for user interface
|
||||
@GetMapping("/public")
|
||||
public ResponseEntity<List<Testimonial>> getActiveTestimonials() {
|
||||
return ResponseEntity.ok(testimonialService.getActiveTestimonials());
|
||||
}
|
||||
|
||||
// Admin endpoints - no security (matching your pattern)
|
||||
@GetMapping
|
||||
public ResponseEntity<List<Testimonial>> getAllTestimonials() {
|
||||
return ResponseEntity.ok(testimonialService.getAllTestimonials());
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<Testimonial> getTestimonialById(@PathVariable Long id) {
|
||||
return ResponseEntity.ok(testimonialService.getTestimonialById(id));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Testimonial> createTestimonial(@Valid @RequestBody TestimonialDTO testimonialDTO) {
|
||||
return ResponseEntity.status(HttpStatus.CREATED)
|
||||
.body(testimonialService.createTestimonial(testimonialDTO));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<Testimonial> updateTestimonial(
|
||||
@PathVariable Long id,
|
||||
@Valid @RequestBody TestimonialDTO testimonialDTO) {
|
||||
return ResponseEntity.ok(testimonialService.updateTestimonial(id, testimonialDTO));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteTestimonial(@PathVariable Long id) {
|
||||
testimonialService.deleteTestimonial(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
// Course.java - Entity for courses
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.domain;
|
||||
|
||||
import javax.persistence.*;
|
||||
@ -56,6 +57,7 @@ public class Course extends BaseEntity {
|
||||
@Column(name = "objective", columnDefinition = "TEXT")
|
||||
private List<String> objectives;
|
||||
|
||||
// FIXED: Changed from primitive boolean to Boolean wrapper class
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private boolean isActive = true;
|
||||
private Boolean isActive = true;
|
||||
}
|
||||
@ -77,6 +77,10 @@ public class Event {
|
||||
@Column(name = "is_deleted", nullable = false)
|
||||
private Boolean isDeleted = false;
|
||||
|
||||
// NEW FIELD: Book Seat Link
|
||||
@Column(name = "book_seat_link", length = 500)
|
||||
private String bookSeatLink;
|
||||
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
name = "event_professors",
|
||||
|
||||
@ -49,6 +49,8 @@ public class Job extends BaseEntity {
|
||||
@Column(name = "responsibility")
|
||||
private List<String> responsibilities;
|
||||
|
||||
// CRITICAL FIX: Changed from primitive boolean to Boolean wrapper class
|
||||
// This ensures proper handling of the value from JSON and prevents default false
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private boolean isActive = true;
|
||||
private Boolean isActive = true;
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Table(name = "milestones")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class Milestone implements Serializable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String title;
|
||||
|
||||
@Column(nullable = false, length = 500)
|
||||
private String description;
|
||||
|
||||
// @Column(nullable = false)
|
||||
// private Integer displayOrder;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Boolean isActive = true;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "UTC")
|
||||
@Temporal(TemporalType.DATE)
|
||||
private Date milestoneDate;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
|
||||
@Column(nullable = false, updatable = false)
|
||||
private Date createdAt;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
|
||||
private Date updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = new Date();
|
||||
updatedAt = new Date();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = new Date();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.domain;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@Entity
|
||||
@Table(name = "testimonials")
|
||||
public class Testimonial {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, length = 100)
|
||||
private String name;
|
||||
|
||||
@Column(nullable = false)
|
||||
private Integer age;
|
||||
|
||||
@Column(nullable = false, length = 200)
|
||||
private String title;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "TEXT")
|
||||
private String story;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "TEXT")
|
||||
private String outcome;
|
||||
|
||||
@Column(nullable = false, columnDefinition = "TEXT")
|
||||
private String impact;
|
||||
|
||||
@Column(nullable = false, length = 100)
|
||||
private String category;
|
||||
|
||||
@Column(name = "is_active")
|
||||
private Boolean isActive = true;
|
||||
|
||||
@Column(name = "created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
createdAt = LocalDateTime.now();
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@ -40,17 +40,10 @@ public class CourseDto {
|
||||
private String imageUrl;
|
||||
private List<String> eligibility;
|
||||
private List<String> objectives;
|
||||
private boolean active;
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
public void setIsActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
// FIXED: Changed from primitive boolean to Boolean wrapper
|
||||
// Changed field name from "active" to "isActive"
|
||||
// Removed all manual getter/setter methods
|
||||
// Lombok @Data will generate: getIsActive() and setIsActive()
|
||||
private Boolean isActive;
|
||||
}
|
||||
@ -37,20 +37,5 @@ public class JobDto {
|
||||
private List<String> requirements;
|
||||
private List<String> responsibilities;
|
||||
|
||||
// Explicit boolean field handling (remove @Data for this field)
|
||||
private boolean active; // Change from isActive to active
|
||||
|
||||
// Explicit getters and setters for the boolean field
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
// For Jackson JSON deserialization
|
||||
public void setIsActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
private Boolean isActive;
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.domain.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class MilestoneDTO {
|
||||
|
||||
private Long id;
|
||||
|
||||
@NotBlank(message = "Title is required")
|
||||
private String title;
|
||||
|
||||
@NotBlank(message = "Description is required")
|
||||
private String description;
|
||||
|
||||
// @NotNull(message = "Display order is required")
|
||||
// private Integer displayOrder;
|
||||
|
||||
@NotNull(message = "Active status is required")
|
||||
private Boolean isActive;
|
||||
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
|
||||
private Date milestoneDate;
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.domain.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TestimonialDTO {
|
||||
|
||||
@NotBlank(message = "Name is required")
|
||||
@Size(max = 100, message = "Name must not exceed 100 characters")
|
||||
private String name;
|
||||
|
||||
@NotNull(message = "Age is required")
|
||||
@Min(value = 1, message = "Age must be at least 1")
|
||||
@Max(value = 120, message = "Age must not exceed 120")
|
||||
private Integer age;
|
||||
|
||||
@NotBlank(message = "Title is required")
|
||||
@Size(max = 200, message = "Title must not exceed 200 characters")
|
||||
private String title;
|
||||
|
||||
@NotBlank(message = "Story is required")
|
||||
private String story;
|
||||
|
||||
@NotBlank(message = "Outcome is required")
|
||||
private String outcome;
|
||||
|
||||
@NotBlank(message = "Impact is required")
|
||||
private String impact;
|
||||
|
||||
@NotBlank(message = "Category is required")
|
||||
@Size(max = 100, message = "Category must not exceed 100 characters")
|
||||
private String category;
|
||||
|
||||
private Boolean isActive;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.exception;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(EntityNotFoundException.class)
|
||||
public ResponseEntity<Map<String, String>> handleEntityNotFound(EntityNotFoundException ex) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("error", ex.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.exception.domain;
|
||||
|
||||
public class MilestoneNotFoundException extends RuntimeException {
|
||||
|
||||
public MilestoneNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MilestoneNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.exception.domain;
|
||||
|
||||
public class TestimonialNotFoundException extends RuntimeException {
|
||||
|
||||
public TestimonialNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TestimonialNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.repository;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.Milestone;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface MilestoneRepository extends JpaRepository<Milestone, Long> {
|
||||
|
||||
// List<Milestone> findAllByIsActiveTrueOrderByDisplayOrderAsc();
|
||||
|
||||
// List<Milestone> findAllByOrderByDisplayOrderAsc();
|
||||
List<Milestone> findAllByIsActiveTrue();
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.repository;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.Testimonial;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface TestimonialRepository extends JpaRepository<Testimonial, Long> {
|
||||
|
||||
// Get all active testimonials
|
||||
List<Testimonial> findAllByIsActiveTrue();
|
||||
|
||||
// Get testimonials by category
|
||||
List<Testimonial> findAllByIsActiveTrueAndCategory(String category);
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.Milestone;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.MilestoneDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MilestoneService {
|
||||
|
||||
List<Milestone> getAllMilestones();
|
||||
|
||||
List<Milestone> getActiveMilestones();
|
||||
|
||||
Milestone getMilestoneById(Long id);
|
||||
|
||||
Milestone createMilestone(MilestoneDTO milestoneDTO);
|
||||
|
||||
Milestone updateMilestone(Long id, MilestoneDTO milestoneDTO);
|
||||
|
||||
void deleteMilestone(Long id);
|
||||
|
||||
// void reorderMilestones(List<Long> orderedIds);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service;
|
||||
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.Testimonial;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.TestimonialDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface TestimonialService {
|
||||
|
||||
List<Testimonial> getAllTestimonials();
|
||||
|
||||
List<Testimonial> getActiveTestimonials();
|
||||
|
||||
Testimonial getTestimonialById(Long id);
|
||||
|
||||
Testimonial createTestimonial(TestimonialDTO testimonialDTO);
|
||||
|
||||
Testimonial updateTestimonial(Long id, TestimonialDTO testimonialDTO);
|
||||
|
||||
void deleteTestimonial(Long id);
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.Milestone;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.MilestoneDTO;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.MilestoneNotFoundException;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.repository.MilestoneRepository;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.MilestoneService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.YearMonth;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class MilestoneServiceImpl implements MilestoneService {
|
||||
|
||||
private final MilestoneRepository milestoneRepository;
|
||||
|
||||
@Override
|
||||
public List<Milestone> getAllMilestones() {
|
||||
log.info("Fetching all milestones");
|
||||
List<Milestone> milestones = milestoneRepository.findAll();
|
||||
return sortMilestonesByMonthYear(milestones);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Milestone> getActiveMilestones() {
|
||||
log.info("Fetching active milestones");
|
||||
List<Milestone> milestones = milestoneRepository.findAllByIsActiveTrue();
|
||||
return sortMilestonesByMonthYear(milestones);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Milestone getMilestoneById(Long id) {
|
||||
log.info("Fetching milestone with id: {}", id);
|
||||
return milestoneRepository.findById(id)
|
||||
.orElseThrow(() -> new MilestoneNotFoundException("Milestone not found with id: " + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Milestone createMilestone(MilestoneDTO milestoneDTO) {
|
||||
log.info("Creating new milestone: {}", milestoneDTO.getTitle());
|
||||
|
||||
Milestone milestone = Milestone.builder()
|
||||
.title(milestoneDTO.getTitle())
|
||||
.description(milestoneDTO.getDescription())
|
||||
.isActive(milestoneDTO.getIsActive() != null ? milestoneDTO.getIsActive() : true)
|
||||
.milestoneDate(milestoneDTO.getMilestoneDate())
|
||||
.build();
|
||||
|
||||
Milestone savedMilestone = milestoneRepository.save(milestone);
|
||||
log.info("Milestone created successfully with id: {}", savedMilestone.getId());
|
||||
|
||||
return savedMilestone;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Milestone updateMilestone(Long id, MilestoneDTO milestoneDTO) {
|
||||
log.info("Updating milestone with id: {}", id);
|
||||
|
||||
Milestone milestone = getMilestoneById(id);
|
||||
|
||||
milestone.setTitle(milestoneDTO.getTitle());
|
||||
milestone.setDescription(milestoneDTO.getDescription());
|
||||
milestone.setIsActive(milestoneDTO.getIsActive());
|
||||
milestone.setMilestoneDate(milestoneDTO.getMilestoneDate());
|
||||
|
||||
Milestone updatedMilestone = milestoneRepository.save(milestone);
|
||||
log.info("Milestone updated successfully");
|
||||
|
||||
return updatedMilestone;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteMilestone(Long id) {
|
||||
log.info("Deleting milestone with id: {}", id);
|
||||
|
||||
Milestone milestone = getMilestoneById(id);
|
||||
milestoneRepository.delete(milestone);
|
||||
|
||||
log.info("Milestone deleted successfully");
|
||||
}
|
||||
|
||||
// REMOVE reorderMilestones method
|
||||
|
||||
/**
|
||||
* Sort milestones by year and month extracted from title
|
||||
* Most recent first (descending order)
|
||||
*/
|
||||
private List<Milestone> sortMilestonesByMonthYear(List<Milestone> milestones) {
|
||||
return milestones.stream()
|
||||
.sorted(Comparator.comparing(this::extractYearMonth).reversed())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract YearMonth from milestone title
|
||||
* Supports formats like:
|
||||
* - "July 2025"
|
||||
* - "2025"
|
||||
* - "December 2023"
|
||||
*/
|
||||
private YearMonth extractYearMonth(Milestone milestone) {
|
||||
String title = milestone.getTitle();
|
||||
|
||||
try {
|
||||
// Try parsing "Month Year" format (e.g., "July 2025", "December 2023")
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM yyyy", Locale.ENGLISH);
|
||||
return YearMonth.parse(title, formatter);
|
||||
} catch (DateTimeParseException e1) {
|
||||
try {
|
||||
// Try parsing "Month Year" with short month (e.g., "Jul 2025")
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM yyyy", Locale.ENGLISH);
|
||||
return YearMonth.parse(title, formatter);
|
||||
} catch (DateTimeParseException e2) {
|
||||
try {
|
||||
// Try parsing just year (e.g., "2025", "2020")
|
||||
int year = Integer.parseInt(title.trim());
|
||||
return YearMonth.of(year, 1); // Default to January
|
||||
} catch (NumberFormatException e3) {
|
||||
// If all parsing fails, default to a very old date
|
||||
log.warn("Could not parse date from title: {}. Using default date.", title);
|
||||
return YearMonth.of(1900, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,95 @@
|
||||
package net.shyshkin.study.fullstack.supportportal.backend.service.impl;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.Testimonial;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.domain.dto.TestimonialDTO;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.TestimonialNotFoundException;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.repository.TestimonialRepository;
|
||||
import net.shyshkin.study.fullstack.supportportal.backend.service.TestimonialService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class TestimonialServiceImpl implements TestimonialService {
|
||||
|
||||
private final TestimonialRepository testimonialRepository;
|
||||
|
||||
@Override
|
||||
public List<Testimonial> getAllTestimonials() {
|
||||
log.info("Fetching all testimonials");
|
||||
return testimonialRepository.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Testimonial> getActiveTestimonials() {
|
||||
log.info("Fetching active testimonials");
|
||||
return testimonialRepository.findAllByIsActiveTrue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Testimonial getTestimonialById(Long id) {
|
||||
log.info("Fetching testimonial with id: {}", id);
|
||||
return testimonialRepository.findById(id)
|
||||
.orElseThrow(() -> new TestimonialNotFoundException("Testimonial not found with id: " + id));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Testimonial createTestimonial(TestimonialDTO testimonialDTO) {
|
||||
log.info("Creating new testimonial for: {}", testimonialDTO.getName());
|
||||
|
||||
Testimonial testimonial = Testimonial.builder()
|
||||
.name(testimonialDTO.getName())
|
||||
.age(testimonialDTO.getAge())
|
||||
.title(testimonialDTO.getTitle())
|
||||
.story(testimonialDTO.getStory())
|
||||
.outcome(testimonialDTO.getOutcome())
|
||||
.impact(testimonialDTO.getImpact())
|
||||
.category(testimonialDTO.getCategory())
|
||||
.isActive(testimonialDTO.getIsActive() != null ? testimonialDTO.getIsActive() : true)
|
||||
.build();
|
||||
|
||||
Testimonial savedTestimonial = testimonialRepository.save(testimonial);
|
||||
log.info("Testimonial created successfully with id: {}", savedTestimonial.getId());
|
||||
|
||||
return savedTestimonial;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Testimonial updateTestimonial(Long id, TestimonialDTO testimonialDTO) {
|
||||
log.info("Updating testimonial with id: {}", id);
|
||||
|
||||
Testimonial testimonial = getTestimonialById(id);
|
||||
|
||||
testimonial.setName(testimonialDTO.getName());
|
||||
testimonial.setAge(testimonialDTO.getAge());
|
||||
testimonial.setTitle(testimonialDTO.getTitle());
|
||||
testimonial.setStory(testimonialDTO.getStory());
|
||||
testimonial.setOutcome(testimonialDTO.getOutcome());
|
||||
testimonial.setImpact(testimonialDTO.getImpact());
|
||||
testimonial.setCategory(testimonialDTO.getCategory());
|
||||
testimonial.setIsActive(testimonialDTO.getIsActive());
|
||||
|
||||
Testimonial updatedTestimonial = testimonialRepository.save(testimonial);
|
||||
log.info("Testimonial updated successfully");
|
||||
|
||||
return updatedTestimonial;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteTestimonial(Long id) {
|
||||
log.info("Deleting testimonial with id: {}", id);
|
||||
|
||||
Testimonial testimonial = getTestimonialById(id);
|
||||
testimonialRepository.delete(testimonial);
|
||||
|
||||
log.info("Testimonial deleted successfully");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/support_portal?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
|
||||
username: root
|
||||
password: root
|
||||
|
||||
file:
|
||||
upload:
|
||||
directory: ${user.home}/support-portal-uploads
|
||||
|
||||
app:
|
||||
base-url: http://localhost:8080
|
||||
cors:
|
||||
allowed-origins: http://localhost:4200,http://localhost:3000
|
||||
@ -51,7 +51,7 @@ file:
|
||||
app:
|
||||
base-url: ${APP_BASE_URL:http://localhost:8080}
|
||||
# Fixed public URLs with correct wildcard patterns
|
||||
public-urls: /user/login,/user/register,/user/*/profile-image,/user/*/profile-image/**,/professors,/professors/**,/api/posts,/api/posts/*,/api/posts/posted,/api/posts/tag/*,/api/posts/tags/count,/api/files/**,/uploads/**,/professor/**,/api/events,/api/events/*,/api/public/**,/api/jobs/active,/api/job-applications,/api/courses/active,/api/courses/*,/api/course-applications,/api/upcoming-events/active
|
||||
public-urls: /user/login,/user/register,/user/*/profile-image,/user/*/profile-image/**,/professors,/professors/**,/api/posts,/api/posts/*,/api/posts/posted,/api/posts/tag/*,/api/posts/tags/count,/api/files/**,/uploads/**,/professor/**,/api/events,/api/events/*,/api/public/**,/api/jobs/active,/api/job-applications,/api/courses/active,/api/courses/*,/api/course-applications,/api/upcoming-events/active,/api/milestones,/api/milestones/**,/api/testimonials,/api/testimonials/**
|
||||
cors:
|
||||
allowed-origins: http://localhost:4200,http://localhost:3000,https://maincmc.rootxwire.com,https://dashboad.cmctrauma.com,https://www.dashboad.cmctrauma.com,https://cmctrauma.com,https://www.cmctrauma.com,https://cmcbackend.rootxwire.com,https://cmcadminfrontend.rootxwire.com
|
||||
jwt:
|
||||
|
||||
Reference in New Issue
Block a user