From 28c85be0f8c7dad515c46909ce4a0b9a6216bf1d Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 27 Aug 2025 13:10:51 +0200 Subject: [PATCH 01/30] test commit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b3db3e2..d4bf309 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,5 @@ out/ ### VS Code ### .vscode/ +### Project specific application.yml From 786d1a561fb406e6bae2d67e8af10b1c8dec5aa8 Mon Sep 17 00:00:00 2001 From: Magnus <100518458+magnusgit1@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:17:37 +0200 Subject: [PATCH 02/30] Update README.md test access --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd76df0..fa76151 100644 --- a/README.md +++ b/README.md @@ -24,4 +24,6 @@ Once you are fully up to speed and working on the project it is perfectly accept Once you have your teams set up, enjoy working on the code. -We look forward to seeing what you manage to produce from it! \ No newline at end of file +We look forward to seeing what you manage to produce from it! + +----Test access Magnus----- From 3578fd75d6fd734b2bfd70d0df131fe461582dca Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Wed, 3 Sep 2025 14:51:03 +0200 Subject: [PATCH 03/30] Added checks for password and email --- .../booleanuk/cohorts/controllers/AuthController.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java index 0c9ba64..87a57fa 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java @@ -69,6 +69,17 @@ public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signupRe if (userRepository.existsByEmail(signupRequest.getEmail())) { return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!")); } + + + String emailRegex = "^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(\\.\\w{2,3})+$"; + String passwordRegex = "^(?=.*[A-Z])(?=.*[0-9])(?=.*[#?!@$%^&-]).{8,}$"; + + if(signupRequest.getEmail().matches(emailRegex)) + return ResponseEntity.badRequest().body(new MessageResponse("Email is incorrectly formatted")); + + if(signupRequest.getPassword().matches(passwordRegex)) + return ResponseEntity.badRequest().body(new MessageResponse("Password is incorrectly formatted")); + // Create a new user add salt here if using one User user = new User(signupRequest.getEmail(), encoder.encode(signupRequest.getPassword())); if (signupRequest.getCohort() != null) { From 03fc42873ef53b4a1ee92f7fc8d2ac151fe0f247 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 3 Sep 2025 14:56:19 +0200 Subject: [PATCH 04/30] created profileController, added mobile field to Profile --- src/main/java/com/booleanuk/Main.java | 4 +- .../controllers/ProfileController.java | 63 +++++++++++++++++++ .../com/booleanuk/cohorts/models/Profile.java | 15 ++++- .../com/booleanuk/cohorts/models/User.java | 1 + .../cohorts/repository/ProfileRepository.java | 2 + .../cohorts/security/WebSecurityConfig.java | 1 + 6 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index d1fae35..f74e8cb 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -69,7 +69,7 @@ public void run(String... args) { } Profile studentProfile; if (!this.profileRepository.existsById(1)) { - studentProfile = this.profileRepository.save(new Profile(studentUser, "Joe", "Bloggs", "Hello world!", "student1")); + studentProfile = this.profileRepository.save(new Profile(studentUser, "Joe", "Bloggs", "Hello world!", "student1", "11111111")); } else { studentProfile = this.profileRepository.findById(1).orElse(null); } @@ -84,7 +84,7 @@ public void run(String... args) { } Profile teacherProfile; if (!this.profileRepository.existsById(2)) { - teacherProfile = this.profileRepository.save(new Profile(teacherUser, "Rick", "Sanchez", "Hello there!", "teacher1")); + teacherProfile = this.profileRepository.save(new Profile(teacherUser, "Rick", "Sanchez", "Hello there!", "teacher1", "22222222")); } else { teacherProfile = this.profileRepository.findById(2).orElse(null); } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java new file mode 100644 index 0000000..53dd64a --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -0,0 +1,63 @@ +package com.booleanuk.cohorts.controllers; + +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.repository.ProfileRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cglib.core.Local; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; + +import static java.lang.Integer.parseInt; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("profiles") +public class ProfileController { + @Autowired + private ProfileRepository profileRepository; + + record PostProfile( + int user, + String first_name, + String last_name, + String github_username, + String mobile, + String bio + ){} + + @PostMapping + public ResponseEntity createProfile(@RequestBody PostProfile profile) { + User user = profileRepository.findUserById(profile.user); + if (user == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + Profile newProfile = new Profile( + user, + profile.first_name, + profile.last_name, + "https://github.com/" + profile.github_username, + profile.mobile, + profile.bio + ); + + return new ResponseEntity<>(profileRepository.save(newProfile), HttpStatus.OK); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex) { + Map errors = new HashMap<>(); + ex.getBindingResult().getFieldErrors().forEach(error -> + errors.put(error.getField(), error.getDefaultMessage())); + return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); + } + +} diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 4305d80..ff1c064 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -2,6 +2,9 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import lombok.Data; import lombok.NoArgsConstructor; @@ -19,9 +22,15 @@ public class Profile { @JsonIgnoreProperties("users") private User user; + @NotNull(message = "First name is mandatory") + @NotEmpty(message = "First name cannot be empty") + @Pattern(regexp = "^[a-zA-Z\\s]+$", message = "First name can only contain letters and spaces") @Column private String firstName; + @NotNull(message = "Last name is mandatory") + @NotEmpty(message = "Last name cannot be empty") + @Pattern(regexp = "^[a-zA-Z\\s]+$", message = "Last name can only contain letters and spaces") @Column private String lastName; @@ -31,15 +40,19 @@ public class Profile { @Column private String githubUrl; + @Column + private String mobile; + public Profile(int id) { this.id = id; } - public Profile(User user, String firstName, String lastName, String bio, String githubUrl) { + public Profile(User user, String firstName, String lastName, String bio, String githubUrl, String mobile) { this.user = user; this.firstName = firstName; this.lastName = lastName; this.bio = bio; this.githubUrl = githubUrl; + this.mobile = mobile; } } diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 9b27170..62d68c8 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -30,6 +30,7 @@ public class User { // The AuthController uses a built-in class for Users that expects a Username, we don't use it elsewhere in the code. @Transient + @Size(min = 7, max = 50, message = "Username must be between 7 and 50 characters") private String username = this.email; @NotBlank diff --git a/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java b/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java index 4ded968..57477a7 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java @@ -1,7 +1,9 @@ package com.booleanuk.cohorts.repository; import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.models.User; import org.springframework.data.jpa.repository.JpaRepository; public interface ProfileRepository extends JpaRepository { + User findUserById(int id); } diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index ad90f26..d0d3092 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -58,6 +58,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .authorizeHttpRequests((requests) -> requests .requestMatchers("/login", "/login/**").permitAll() .requestMatchers("/signup", "/signup/**").permitAll() + .requestMatchers("/profiles", "/profiles/**").authenticated() .requestMatchers("/users", "/users/**").authenticated() .requestMatchers("/posts", "/posts/**").authenticated() .requestMatchers("/cohorts", "/cohorts/**").authenticated() From 7536b5ff8ec48a2aef12e1aa3b33f492b9474899 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Wed, 3 Sep 2025 15:12:27 +0200 Subject: [PATCH 05/30] Changed logic for if statements --- .../com/booleanuk/cohorts/controllers/AuthController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java index 87a57fa..3919e66 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java @@ -74,10 +74,10 @@ public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signupRe String emailRegex = "^\\w+([.-]?\\w+)*@\\w+([.-]?\\w+)*(\\.\\w{2,3})+$"; String passwordRegex = "^(?=.*[A-Z])(?=.*[0-9])(?=.*[#?!@$%^&-]).{8,}$"; - if(signupRequest.getEmail().matches(emailRegex)) + if(!signupRequest.getEmail().matches(emailRegex)) return ResponseEntity.badRequest().body(new MessageResponse("Email is incorrectly formatted")); - if(signupRequest.getPassword().matches(passwordRegex)) + if(!signupRequest.getPassword().matches(passwordRegex)) return ResponseEntity.badRequest().body(new MessageResponse("Password is incorrectly formatted")); // Create a new user add salt here if using one From 136e3c5239b37ff360dead2f3ea42455a3772ab3 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 3 Sep 2025 15:38:46 +0200 Subject: [PATCH 06/30] createProfile now correctly handles errors --- src/main/java/com/booleanuk/Main.java | 2 +- .../controllers/ProfileController.java | 40 ++++++++++++------- .../cohorts/repository/ProfileRepository.java | 1 - 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index f74e8cb..2e647fc 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -84,7 +84,7 @@ public void run(String... args) { } Profile teacherProfile; if (!this.profileRepository.existsById(2)) { - teacherProfile = this.profileRepository.save(new Profile(teacherUser, "Rick", "Sanchez", "Hello there!", "teacher1", "22222222")); + teacherProfile = this.profileRepository.save(new Profile(teacherUser, "Rick", "Sanchez", "Hello there!", "teacher1", "88888888")); } else { teacherProfile = this.profileRepository.findById(2).orElse(null); } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 53dd64a..205a3d4 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -4,8 +4,10 @@ import com.booleanuk.cohorts.models.Profile; import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cglib.core.Local; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -14,6 +16,7 @@ import java.time.LocalDate; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import static java.lang.Integer.parseInt; @@ -24,6 +27,9 @@ public class ProfileController { @Autowired private ProfileRepository profileRepository; + @Autowired + private UserRepository userRepository; + record PostProfile( int user, String first_name, @@ -34,30 +40,34 @@ record PostProfile( ){} @PostMapping - public ResponseEntity createProfile(@RequestBody PostProfile profile) { - User user = profileRepository.findUserById(profile.user); - if (user == null) { - return new ResponseEntity<>(HttpStatus.NOT_FOUND); + public ResponseEntity createProfile(@RequestBody PostProfile profile) { + + if(profile.first_name == null || profile.first_name == "" || profile.last_name == null || profile.last_name == ""){ + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + Optional optionalUser = userRepository.findById(profile.user); + if (optionalUser.isEmpty()) { + return new ResponseEntity<>( + "User for id "+ profile.user + " not found", HttpStatus.BAD_REQUEST); } + User user = optionalUser.get(); + Profile newProfile = new Profile( user, profile.first_name, profile.last_name, + profile.bio, "https://github.com/" + profile.github_username, - profile.mobile, - profile.bio + profile.mobile ); - return new ResponseEntity<>(profileRepository.save(newProfile), HttpStatus.OK); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex) { - Map errors = new HashMap<>(); - ex.getBindingResult().getFieldErrors().forEach(error -> - errors.put(error.getField(), error.getDefaultMessage())); - return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); + try { + return new ResponseEntity<>(profileRepository.save(newProfile), HttpStatus.OK); + } catch (DataIntegrityViolationException e) { + return new ResponseEntity<>("User has an existing profile", HttpStatus.BAD_REQUEST); + } } } diff --git a/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java b/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java index 57477a7..4064ea8 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java @@ -5,5 +5,4 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface ProfileRepository extends JpaRepository { - User findUserById(int id); } From dae7c7d32f3cfb0a77ecdfe2a2253243b28f6c75 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 3 Sep 2025 15:46:49 +0200 Subject: [PATCH 07/30] Added error message for name validation --- .../com/booleanuk/cohorts/controllers/ProfileController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 205a3d4..d82a3d6 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -43,13 +43,12 @@ record PostProfile( public ResponseEntity createProfile(@RequestBody PostProfile profile) { if(profile.first_name == null || profile.first_name == "" || profile.last_name == null || profile.last_name == ""){ - return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + return new ResponseEntity<>("First and last name can't be empty or NULL. First name: " + profile.first_name + " Last name: " + profile.last_name, HttpStatus.BAD_REQUEST); } Optional optionalUser = userRepository.findById(profile.user); if (optionalUser.isEmpty()) { - return new ResponseEntity<>( - "User for id "+ profile.user + " not found", HttpStatus.BAD_REQUEST); + return new ResponseEntity<>("User for id "+ profile.user + " not found", HttpStatus.BAD_REQUEST); } User user = optionalUser.get(); From a7b6174d14ca841d22086f7c6453f29bde08cd99 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Fri, 5 Sep 2025 12:40:06 +0200 Subject: [PATCH 08/30] Fixed login --- .../java/com/booleanuk/cohorts/controllers/AuthController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java index 0c9ba64..0b5c5a1 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java @@ -27,6 +27,7 @@ import java.util.Set; import java.util.stream.Collectors; +//fixed issue with login. @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping From 3b67eebabf7a7ee7b7b69d7d7681ce19022f958d Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Mon, 8 Sep 2025 13:40:26 +0200 Subject: [PATCH 09/30] Updated Profile class, ProfileController and test profiles in Main to include specialy and start and end dates --- src/main/java/com/booleanuk/Main.java | 23 ++++++++++++-- .../controllers/ProfileController.java | 31 +++++++++++++------ .../com/booleanuk/cohorts/models/Profile.java | 13 +++++++- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 2e647fc..23cf7bf 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -8,6 +8,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.crypto.password.PasswordEncoder; +import java.time.LocalDate; import java.util.HashSet; import java.util.Set; @@ -69,7 +70,16 @@ public void run(String... args) { } Profile studentProfile; if (!this.profileRepository.existsById(1)) { - studentProfile = this.profileRepository.save(new Profile(studentUser, "Joe", "Bloggs", "Hello world!", "student1", "11111111")); + studentProfile = this.profileRepository.save(new Profile(studentUser, + "Joe", + "Bloggs", + "Hello world!", + "student1", + "11111111", + "Backend Development", + LocalDate.of(2025, 9, 8 + ), LocalDate.of(2026, 9, 8) + )); } else { studentProfile = this.profileRepository.findById(1).orElse(null); } @@ -84,7 +94,16 @@ public void run(String... args) { } Profile teacherProfile; if (!this.profileRepository.existsById(2)) { - teacherProfile = this.profileRepository.save(new Profile(teacherUser, "Rick", "Sanchez", "Hello there!", "teacher1", "88888888")); + teacherProfile = this.profileRepository.save(new Profile(teacherUser, + "Rick", + "Sanchez", + "Hello there!", + "teacher1", + "88888888", + "Everything", + LocalDate.of(1962, 9, 8), + LocalDate.of(2062, 9, 8) + )); } else { teacherProfile = this.profileRepository.findById(2).orElse(null); } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index d82a3d6..18aad39 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDate; +import java.time.format.DateTimeParseException; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -36,7 +37,10 @@ record PostProfile( String last_name, String github_username, String mobile, - String bio + String bio, + String specialty, + String start_date, + String end_date ){} @PostMapping @@ -53,14 +57,23 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { User user = optionalUser.get(); - Profile newProfile = new Profile( - user, - profile.first_name, - profile.last_name, - profile.bio, - "https://github.com/" + profile.github_username, - profile.mobile - ); + Profile newProfile = null; + try { + newProfile = new Profile( + user, + profile.first_name, + profile.last_name, + profile.bio, + "https://github.com/" + profile.github_username, + profile.mobile, + profile.specialty, + LocalDate.parse(profile.start_date), + LocalDate.parse(profile.end_date) + ); + } catch (DateTimeParseException e) { + return new ResponseEntity<>("Wrong formatting for start_date or end_date. Plese use the following format: 2025-09-14", + HttpStatus.BAD_REQUEST); + } try { return new ResponseEntity<>(profileRepository.save(newProfile), HttpStatus.OK); diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index ff1c064..b3b6ee2 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -8,6 +8,8 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.time.LocalDate; + @NoArgsConstructor @Data @Entity @@ -43,11 +45,20 @@ public class Profile { @Column private String mobile; + @Column + private String specialism; + + @Column + private LocalDate startDate; + + @Column + private LocalDate endDate; + public Profile(int id) { this.id = id; } - public Profile(User user, String firstName, String lastName, String bio, String githubUrl, String mobile) { + public Profile(User user, String firstName, String lastName, String bio, String githubUrl, String mobile, String specialism, LocalDate startDate, LocalDate endDate) { this.user = user; this.firstName = firstName; this.lastName = lastName; From 4281e1b3138fe367d9117466a5e2f9eeba5044c9 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Mon, 8 Sep 2025 14:08:12 +0200 Subject: [PATCH 10/30] Fixed Profile class --- src/main/java/com/booleanuk/cohorts/models/Profile.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index b3b6ee2..9f0c4cd 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -65,5 +65,8 @@ public Profile(User user, String firstName, String lastName, String bio, String this.bio = bio; this.githubUrl = githubUrl; this.mobile = mobile; + this.specialism = specialism; + this.startDate = startDate; + this.endDate = endDate; } } From 37e04c9ffc9919d81c80d7696d4fd1d0766d7674 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Wed, 10 Sep 2025 15:58:13 +0200 Subject: [PATCH 11/30] added patchmapping to ProfileController --- .../controllers/ProfileController.java | 19 +++++++++++++++++++ .../com/booleanuk/cohorts/models/User.java | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 18aad39..952b4cc 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; +import java.lang.annotation.Target; import java.time.LocalDate; import java.time.format.DateTimeParseException; import java.util.HashMap; @@ -43,6 +44,24 @@ record PostProfile( String end_date ){} + record TargetUser( + int user_id + ){} + + @PatchMapping("{id}") + public ResponseEntity updateUserWithProfile(@PathVariable int id, @RequestBody TargetUser targetUser) { + Profile profile = profileRepository.findById(id).orElse(null); + if (profile == null){ + return new ResponseEntity<>("Could not add profile to user because the profile does not exist", HttpStatus.NOT_FOUND); + } + User user = userRepository.findById(targetUser.user_id).orElse(null); + if (user == null) { + return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); + } + user.setProfile(profile.); + return new ResponseEntity<>("Profile added to user with email: " + user.getEmail(), HttpStatus.OK); + } + @PostMapping public ResponseEntity createProfile(@RequestBody PostProfile profile) { diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 62d68c8..4112881 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -46,6 +46,11 @@ public class User { @JsonIgnoreProperties("users") private Cohort cohort; + @OneToOne + @JoinColumn(name = "profile_id") + @JsonIgnoreProperties("users") + private Profile profile; + public User(String email, String password) { this.email = email; this.password = password; From 1453e987cbfd4b0b308e9aa7d76b00ac5570b670 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Thu, 11 Sep 2025 11:06:51 +0200 Subject: [PATCH 12/30] Added a course model, and a link between Cohort and courses --- .../com/booleanuk/cohorts/models/Cohort.java | 21 ++++++++++ .../com/booleanuk/cohorts/models/Course.java | 42 +++++++++++++++++++ .../payload/response/CourseListData.java | 19 +++++++++ .../payload/response/CourseListResponse.java | 16 +++++++ .../payload/response/CourseResponse.java | 4 ++ 5 files changed, 102 insertions(+) create mode 100644 src/main/java/com/booleanuk/cohorts/models/Course.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/CourseListData.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/CourseListResponse.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/CourseResponse.java diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index 2972906..09800e1 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -1,9 +1,14 @@ package com.booleanuk.cohorts.models; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.List; +import java.util.Set; + @NoArgsConstructor @Data @Entity @@ -13,7 +18,23 @@ public class Cohort { @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; + + @ManyToMany + @JsonIgnoreProperties("cohorts") + @JoinTable(name = "cohort_course", + joinColumns = @JoinColumn(name = "cohort_id"), + inverseJoinColumns = @JoinColumn(name = "course_id") + ) + private List cohort_courses; + + public Cohort(int id) { this.id = id; } + + @Override + public String toString(){ + + return "Cohort Id: " + this.id; + } } diff --git a/src/main/java/com/booleanuk/cohorts/models/Course.java b/src/main/java/com/booleanuk/cohorts/models/Course.java new file mode 100644 index 0000000..3feea3b --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/models/Course.java @@ -0,0 +1,42 @@ +package com.booleanuk.cohorts.models; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +@NoArgsConstructor +@Data +@Entity +@Table(name = "courses") +public class Course { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private String name; + + @JsonIgnoreProperties("cohort_courses") + @ManyToMany(mappedBy = "cohort_courses") + private List cohorts; + + public Course(int id, String name) { + this.id = id; + this.name = name; + } + + @Override + public String toString(){ + + return "Course Name: " + this.name; + } + +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/CourseListData.java b/src/main/java/com/booleanuk/cohorts/payload/response/CourseListData.java new file mode 100644 index 0000000..2f46feb --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/CourseListData.java @@ -0,0 +1,19 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Course; +import com.booleanuk.cohorts.models.Post; +import lombok.Getter; + +import java.util.List; + +@Getter +public class CourseListData extends Data> { + + protected List courses; + + @Override + public void set(List courseList) { + this.courses = courseList; + } + +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/CourseListResponse.java b/src/main/java/com/booleanuk/cohorts/payload/response/CourseListResponse.java new file mode 100644 index 0000000..4486843 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/CourseListResponse.java @@ -0,0 +1,16 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Course; +import com.booleanuk.cohorts.models.Post; +import lombok.Getter; + +import java.util.List; + +@Getter +public class CourseListResponse extends Response { + public void set(List courses) { + Data> data = new CourseListData(); + data.set(courses); + super.set(data); + } +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/CourseResponse.java b/src/main/java/com/booleanuk/cohorts/payload/response/CourseResponse.java new file mode 100644 index 0000000..8c1dee8 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/CourseResponse.java @@ -0,0 +1,4 @@ +package com.booleanuk.cohorts.payload.response; + +public class CourseResponse { +} From c5da59dbed072501c97ce03a162e5b3c1ea56960 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Thu, 11 Sep 2025 13:30:59 +0200 Subject: [PATCH 13/30] fixed patch, added foreign keys to profile and user --- src/main/java/com/booleanuk/Main.java | 32 +++++---- .../controllers/ProfileController.java | 65 +++++++++++-------- .../cohorts/controllers/UserController.java | 31 +++++++++ .../com/booleanuk/cohorts/models/Profile.java | 24 ++++++- .../com/booleanuk/cohorts/models/User.java | 6 ++ .../cohorts/security/jwt/JwtUtils.java | 1 + 6 files changed, 117 insertions(+), 42 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 23cf7bf..9b9d49d 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -73,12 +73,16 @@ public void run(String... args) { studentProfile = this.profileRepository.save(new Profile(studentUser, "Joe", "Bloggs", + "usrname", + "mygit", + "95555", "Hello world!", - "student1", - "11111111", + studentRole, "Backend Development", - LocalDate.of(2025, 9, 8 - ), LocalDate.of(2026, 9, 8) + cohort, + LocalDate.of(2025, 9, 8), + LocalDate.of(2026, 9, 8), + "fnwjkdnfj32.,." )); } else { studentProfile = this.profileRepository.findById(1).orElse(null); @@ -95,14 +99,18 @@ public void run(String... args) { Profile teacherProfile; if (!this.profileRepository.existsById(2)) { teacherProfile = this.profileRepository.save(new Profile(teacherUser, - "Rick", - "Sanchez", - "Hello there!", - "teacher1", - "88888888", - "Everything", - LocalDate.of(1962, 9, 8), - LocalDate.of(2062, 9, 8) + "Joe", + "Bloggs", + "usrname", + "mygit", + "95555", + "Hello world!", + teacherRole, + "Backend Development", + cohort, + LocalDate.of(2025, 9, 8), + LocalDate.of(2026, 9, 8), + "fnwjkdnfj32.,." )); } else { teacherProfile = this.profileRepository.findById(2).orElse(null); diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 952b4cc..37ba226 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -1,9 +1,9 @@ package com.booleanuk.cohorts.controllers; -import com.booleanuk.cohorts.models.Cohort; -import com.booleanuk.cohorts.models.Profile; -import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.models.*; +import com.booleanuk.cohorts.repository.CohortRepository; import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.RoleRepository; import com.booleanuk.cohorts.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cglib.core.Local; @@ -29,6 +29,12 @@ public class ProfileController { @Autowired private ProfileRepository profileRepository; + @Autowired + private CohortRepository cohortRepository; + + @Autowired + private RoleRepository roleRepository; + @Autowired private UserRepository userRepository; @@ -36,32 +42,18 @@ record PostProfile( int user, String first_name, String last_name, + String username, String github_username, String mobile, String bio, - String specialty, + String role, + String specialism, + int cohort, String start_date, - String end_date - ){} - - record TargetUser( - int user_id + String end_date, + String photo ){} - @PatchMapping("{id}") - public ResponseEntity updateUserWithProfile(@PathVariable int id, @RequestBody TargetUser targetUser) { - Profile profile = profileRepository.findById(id).orElse(null); - if (profile == null){ - return new ResponseEntity<>("Could not add profile to user because the profile does not exist", HttpStatus.NOT_FOUND); - } - User user = userRepository.findById(targetUser.user_id).orElse(null); - if (user == null) { - return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); - } - user.setProfile(profile.); - return new ResponseEntity<>("Profile added to user with email: " + user.getEmail(), HttpStatus.OK); - } - @PostMapping public ResponseEntity createProfile(@RequestBody PostProfile profile) { @@ -76,18 +68,36 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { User user = optionalUser.get(); + Optional optionalRole = roleRepository.findByName(ERole.valueOf(profile.role)); + if (optionalRole.isEmpty()) { + return new ResponseEntity<>("Role for id "+ profile.role + " not found", HttpStatus.BAD_REQUEST); + } + + Role role = optionalRole.get(); + + Optional optionalCohort = cohortRepository.findById(profile.cohort); + if (optionalCohort.isEmpty()) { + return new ResponseEntity<>("Cohort for id "+ profile.cohort + " not found", HttpStatus.BAD_REQUEST); + } + + Cohort cohort = optionalCohort.get(); + Profile newProfile = null; try { newProfile = new Profile( user, profile.first_name, profile.last_name, - profile.bio, - "https://github.com/" + profile.github_username, + profile.username, profile.mobile, - profile.specialty, + "https://github.com/" + profile.github_username, + profile.bio, + role, + profile.specialism, + cohort, LocalDate.parse(profile.start_date), - LocalDate.parse(profile.end_date) + LocalDate.parse(profile.end_date), + profile.photo ); } catch (DateTimeParseException e) { return new ResponseEntity<>("Wrong formatting for start_date or end_date. Plese use the following format: 2025-09-14", @@ -100,5 +110,4 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { return new ResponseEntity<>("User has an existing profile", HttpStatus.BAD_REQUEST); } } - } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index 1261544..efb51fc 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -1,16 +1,21 @@ package com.booleanuk.cohorts.controllers; +import com.booleanuk.cohorts.models.Profile; import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.response.ErrorResponse; import com.booleanuk.cohorts.payload.response.Response; import com.booleanuk.cohorts.payload.response.UserListResponse; import com.booleanuk.cohorts.payload.response.UserResponse; +import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import static java.util.Arrays.stream; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("users") @@ -18,6 +23,9 @@ public class UserController { @Autowired private UserRepository userRepository; + @Autowired + private ProfileRepository profileRepository; + @GetMapping public ResponseEntity getAllUsers() { UserListResponse userListResponse = new UserListResponse(); @@ -38,6 +46,29 @@ public ResponseEntity getUserById(@PathVariable int id) { return ResponseEntity.ok(userResponse); } + @PatchMapping("{id}") + public ResponseEntity updateUserWithProfile(@PathVariable int id) { + int profileId = profileRepository.findAll().stream().filter(it -> it.getUser().getId() == id).toList().getFirst().getId(); + Profile profile = profileRepository.findById(profileId).orElse(null); + if (profile == null){ + return new ResponseEntity<>("Could not add profile to user because the profile does not exist", HttpStatus.NOT_FOUND); + } + User user = userRepository.findById(id).orElse(null); + + if (user == null) { + return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); + } + if (user.getProfile() != null) { + return new ResponseEntity<>("A profile is already registered on this user", HttpStatus.BAD_REQUEST); + } + user.setProfile(profile); + try { + return new ResponseEntity<>(userRepository.save(user), HttpStatus.OK); + } catch (DataIntegrityViolationException e) { + return new ResponseEntity<>("User has an existing profile", HttpStatus.BAD_REQUEST); + } + } + @PostMapping public void registerUser() { System.out.println("Register endpoint hit"); diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 9f0c4cd..6d548a9 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -7,6 +7,7 @@ import jakarta.validation.constraints.Pattern; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.engine.internal.Cascade; import java.time.LocalDate; @@ -36,6 +37,9 @@ public class Profile { @Column private String lastName; + @Column + private String username; + @Column private String bio; @@ -54,19 +58,35 @@ public class Profile { @Column private LocalDate endDate; + @ManyToOne + @JoinColumn(name = "cohort_id") + private Cohort cohort; + + @ManyToOne + @JoinColumn(name = "role_id") + private Role role; + + @Column + private String photo; + public Profile(int id) { this.id = id; } - public Profile(User user, String firstName, String lastName, String bio, String githubUrl, String mobile, String specialism, LocalDate startDate, LocalDate endDate) { + public Profile(User user, String firstName, String lastName, String username, String githubUrl, String mobile, + String bio, Role role, String specialism, Cohort cohort, LocalDate startDate, LocalDate endDate, String photo) { this.user = user; this.firstName = firstName; this.lastName = lastName; - this.bio = bio; + this.username = username; this.githubUrl = githubUrl; this.mobile = mobile; + this.bio = bio; + this.role = role; this.specialism = specialism; + this.cohort = cohort; this.startDate = startDate; this.endDate = endDate; + this.photo = photo; } } diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 4112881..28235d0 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -61,4 +61,10 @@ public User(String email, String password, Cohort cohort) { this.password = password; this.cohort = cohort; } + public User(String email, String password, Cohort cohort, Profile profile) { + this.email = email; + this.password = password; + this.cohort = cohort; + this.profile = profile; + } } diff --git a/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java b/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java index 33ef314..8f4ce40 100644 --- a/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java +++ b/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java @@ -31,6 +31,7 @@ public String generateJwtToken(Authentication authentication) { return Jwts.builder() .subject((userPrincipal.getUsername())) + .claim("userId", userPrincipal.getId()) .issuedAt(new Date()) .expiration(new Date((new Date()).getTime() + this.jwtExpirationMs)) .signWith(this.key()) From 500226dce79af137415e612fd30e3b96bf30bd0a Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Thu, 11 Sep 2025 13:35:03 +0200 Subject: [PATCH 14/30] Added new Comments table and added GET POST PUT AND DELETE to COMMENTs --- .../cohorts/controllers/PostController.java | 277 +++++++++++++++--- .../com/booleanuk/cohorts/models/Comment.java | 45 +++ .../com/booleanuk/cohorts/models/Post.java | 26 +- .../payload/request/CommentRequest.java | 33 +++ .../cohorts/payload/response/CommentData.java | 15 + .../payload/response/CommentResponse.java | 15 + .../cohorts/repository/CommentRepository.java | 8 + src/main/resources/application.yml.example | 27 -- 8 files changed, 384 insertions(+), 62 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/models/Comment.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/CommentRequest.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/CommentData.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/CommentResponse.java create mode 100644 src/main/java/com/booleanuk/cohorts/repository/CommentRepository.java delete mode 100644 src/main/resources/application.yml.example diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java index bfb42ef..0ebb376 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java @@ -1,23 +1,38 @@ package com.booleanuk.cohorts.controllers; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + import com.booleanuk.cohorts.models.Author; +import com.booleanuk.cohorts.models.Comment; import com.booleanuk.cohorts.models.Post; import com.booleanuk.cohorts.models.Profile; import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.CommentRequest; +import com.booleanuk.cohorts.payload.response.CommentResponse; import com.booleanuk.cohorts.payload.response.ErrorResponse; import com.booleanuk.cohorts.payload.response.PostListResponse; import com.booleanuk.cohorts.payload.response.PostResponse; import com.booleanuk.cohorts.payload.response.Response; +import com.booleanuk.cohorts.repository.CommentRepository; import com.booleanuk.cohorts.repository.PostRepository; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.ArrayList; -import java.util.List; +import com.booleanuk.cohorts.security.services.UserDetailsImpl; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @@ -29,51 +44,245 @@ public class PostController { private UserRepository userRepository; @Autowired private ProfileRepository profileRepository; + @Autowired + private CommentRepository commentRepository; + + private User getCurrentAuthenticatedUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.getPrincipal() instanceof UserDetailsImpl) { + UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); + return this.userRepository.findByEmail(userDetails.getEmail()).orElse(null); + } + return null; + } @GetMapping public ResponseEntity getAllPosts() { PostListResponse postListResponse = new PostListResponse(); - User user = this.userRepository.findById(1).orElse(null); + + List posts = this.postRepository.findAll(); + + // For each post, set up the author information + for (Post post : posts) { + User user = post.getUser(); + if (user != null) { + Profile profile = this.profileRepository.findById(user.getId()).orElse(null); + if (profile != null && user.getCohort() != null) { + Author author = new Author( + user.getId(), + user.getCohort().getId(), + profile.getFirstName(), + profile.getLastName(), + user.getEmail(), + profile.getBio(), + profile.getGithubUrl() + ); + post.setAuthor(author); + } + } + } + + postListResponse.set(posts); + return ResponseEntity.ok(postListResponse); + } + + @GetMapping("/{id}") + public ResponseEntity getPostById(@PathVariable int id) { + Post post = this.postRepository.findById(id).orElse(null); + + if (post == null) { + ErrorResponse error = new ErrorResponse(); + error.set("Post not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + + // Set up the author information + User user = post.getUser(); + if (user != null) { + Profile profile = this.profileRepository.findById(user.getId()).orElse(null); + if (profile != null && user.getCohort() != null) { + Author author = new Author( + user.getId(), + user.getCohort().getId(), + profile.getFirstName(), + profile.getLastName(), + user.getEmail(), + profile.getBio(), + profile.getGithubUrl() + ); + post.setAuthor(author); + } + } + + PostResponse postResponse = new PostResponse(); + postResponse.set(post); + return ResponseEntity.ok(postResponse); + } + + @PostMapping("/{postId}/comments") + public ResponseEntity addCommentToPost(@PathVariable int postId, @RequestBody CommentRequest commentRequest) { + Post post = this.postRepository.findById(postId).orElse(null); + if (post == null) { + ErrorResponse error = new ErrorResponse(); + error.set("Post not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + + User user = this.userRepository.findById(commentRequest.getUserId()).orElse(null); if (user == null) { ErrorResponse error = new ErrorResponse(); - error.set("not found"); + error.set("User not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } - Profile profile = this.profileRepository.findById(user.getId()).orElse(null); - if (profile == null) { + + Comment comment = new Comment(commentRequest.getBody(), user, post); + Comment savedComment = this.commentRepository.save(comment); + + CommentResponse commentResponse = new CommentResponse(); + commentResponse.set(savedComment); + return new ResponseEntity<>(commentResponse, HttpStatus.CREATED); + } + + @GetMapping("/{postId}/comments") + public ResponseEntity getCommentsForPost(@PathVariable int postId) { + Post post = this.postRepository.findById(postId).orElse(null); + if (post == null) { ErrorResponse error = new ErrorResponse(); - error.set("not found"); + error.set("Post not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } - Author author = new Author(user.getId(), user.getCohort().getId(), profile.getFirstName(), - profile.getLastName(), user.getEmail(), profile.getBio(), profile.getGithubUrl()); - List posts = new ArrayList<>(); - Post post1 = this.postRepository.findById(1).orElse(null); - if (post1 == null){ + + // Return the post with its comments (comments will be included via the @OneToMany relationship) + PostResponse postResponse = new PostResponse(); + postResponse.set(post); + return ResponseEntity.ok(postResponse); + } + + @GetMapping("/{postId}/comments/{commentId}") + public ResponseEntity getCommentById(@PathVariable int postId, @PathVariable int commentId) { + // Verify post exists + Post post = this.postRepository.findById(postId).orElse(null); + if (post == null) { ErrorResponse error = new ErrorResponse(); - error.set("not found"); + error.set("Post not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } - post1.setAuthor(author); - post1.setContent("Hello world!!"); - posts.add(post1); - Post post2 = this.postRepository.findById(2).orElse(null); - if (post2 == null){ + + // Get the comment + Comment comment = this.commentRepository.findById(commentId).orElse(null); + if (comment == null) { ErrorResponse error = new ErrorResponse(); - error.set("not found"); + error.set("Comment not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } - post2.setAuthor(author); - post2.setContent("Hello from the void!!"); - posts.add(post2); - postListResponse.set(posts); - return ResponseEntity.ok(postListResponse); + + // Verify the comment belongs to the specified post + if (comment.getPost().getId() != postId) { + ErrorResponse error = new ErrorResponse(); + error.set("Comment does not belong to the specified post"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + CommentResponse commentResponse = new CommentResponse(); + commentResponse.set(comment); + return ResponseEntity.ok(commentResponse); } - @GetMapping("/{id}") - public ResponseEntity getPostById(@PathVariable int id) { - ErrorResponse error = new ErrorResponse(); - error.set("not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + @PutMapping("/{postId}/comments/{commentId}") + public ResponseEntity updateComment(@PathVariable int postId, @PathVariable int commentId, @RequestBody CommentRequest commentRequest) { + // Get the current authenticated user + User currentUser = getCurrentAuthenticatedUser(); + if (currentUser == null) { + ErrorResponse error = new ErrorResponse(); + error.set("Authentication required"); + return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED); + } + + // Verify post exists + Post post = this.postRepository.findById(postId).orElse(null); + if (post == null) { + ErrorResponse error = new ErrorResponse(); + error.set("Post not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + + // Get the comment + Comment comment = this.commentRepository.findById(commentId).orElse(null); + if (comment == null) { + ErrorResponse error = new ErrorResponse(); + error.set("Comment not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + + // Verify the comment belongs to the specified post + if (comment.getPost().getId() != postId) { + ErrorResponse error = new ErrorResponse(); + error.set("Comment does not belong to the specified post"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + // Verify the user is the owner of the comment + if (comment.getUser().getId() != currentUser.getId()) { + ErrorResponse error = new ErrorResponse(); + error.set("You can only edit your own comments"); + return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); + } + + // Update the comment + comment.setBody(commentRequest.getBody()); + Comment updatedComment = this.commentRepository.save(comment); + + CommentResponse commentResponse = new CommentResponse(); + commentResponse.set(updatedComment); + return ResponseEntity.ok(commentResponse); + } + + @DeleteMapping("/{postId}/comments/{commentId}") + public ResponseEntity deleteComment(@PathVariable int postId, @PathVariable int commentId) { + // Get the current authenticated user + User currentUser = getCurrentAuthenticatedUser(); + if (currentUser == null) { + ErrorResponse error = new ErrorResponse(); + error.set("Authentication required"); + return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED); + } + + // Verify post exists + Post post = this.postRepository.findById(postId).orElse(null); + if (post == null) { + ErrorResponse error = new ErrorResponse(); + error.set("Post not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + + // Get the comment + Comment comment = this.commentRepository.findById(commentId).orElse(null); + if (comment == null) { + ErrorResponse error = new ErrorResponse(); + error.set("Comment not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + + // Verify the comment belongs to the specified post + if (comment.getPost().getId() != postId) { + ErrorResponse error = new ErrorResponse(); + error.set("Comment does not belong to the specified post"); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + // Verify the user is the owner of the comment + if (comment.getUser().getId() != currentUser.getId()) { + ErrorResponse error = new ErrorResponse(); + error.set("You can only delete your own comments"); + return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); + } + + // Delete the comment + this.commentRepository.delete(comment); + + // Return success message + ErrorResponse success = new ErrorResponse(); + success.set("Comment deleted successfully"); + return ResponseEntity.ok(success); } } diff --git a/src/main/java/com/booleanuk/cohorts/models/Comment.java b/src/main/java/com/booleanuk/cohorts/models/Comment.java new file mode 100644 index 0000000..6e6fbe5 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/models/Comment.java @@ -0,0 +1,45 @@ +package com.booleanuk.cohorts.models; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Data +@Entity +@Table(name = "comments") +public class Comment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column(nullable = false) + private String body; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + @JsonIgnoreProperties("comments") + private User user; + + @ManyToOne + @JoinColumn(name = "post_id", nullable = false) + @JsonIgnoreProperties("comments") + private Post post; + + public Comment(String body, User user, Post post) { + this.body = body; + this.user = user; + this.post = post; + } +} diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index 3d879ba..89c6252 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -1,7 +1,21 @@ package com.booleanuk.cohorts.models; +import java.util.List; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import jakarta.persistence.*; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -24,6 +38,10 @@ public class Post { @JsonIgnoreProperties("users") private User user; + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @JsonIgnoreProperties("post") + private List comments; + @Transient private Author author; @@ -41,5 +59,11 @@ public Post(Author author, String content) { this.content = content; } + public Post(String content, User user, List comments) { + this.content = content; + this.user = user; + this.comments = comments; + } + } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CommentRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CommentRequest.java new file mode 100644 index 0000000..d3bbab2 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CommentRequest.java @@ -0,0 +1,33 @@ +package com.booleanuk.cohorts.payload.request; + +import jakarta.validation.constraints.NotBlank; + +public class CommentRequest { + @NotBlank + private String body; + + private int userId; + + public CommentRequest() {} + + public CommentRequest(String body, int userId) { + this.body = body; + this.userId = userId; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/CommentData.java b/src/main/java/com/booleanuk/cohorts/payload/response/CommentData.java new file mode 100644 index 0000000..e76b4aa --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/CommentData.java @@ -0,0 +1,15 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Comment; + +import lombok.Getter; + +@Getter +public class CommentData extends Data { + protected Comment comment; + + @Override + public void set(Comment comment) { + this.comment = comment; + } +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/CommentResponse.java b/src/main/java/com/booleanuk/cohorts/payload/response/CommentResponse.java new file mode 100644 index 0000000..ca93871 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/CommentResponse.java @@ -0,0 +1,15 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Comment; + +import lombok.Getter; + +@Getter +public class CommentResponse extends Response { + + public void set(Comment comment) { + Data data = new CommentData(); + data.set(comment); + super.set(data); + } +} diff --git a/src/main/java/com/booleanuk/cohorts/repository/CommentRepository.java b/src/main/java/com/booleanuk/cohorts/repository/CommentRepository.java new file mode 100644 index 0000000..70d051b --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/repository/CommentRepository.java @@ -0,0 +1,8 @@ +package com.booleanuk.cohorts.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.booleanuk.cohorts.models.Comment; + +public interface CommentRepository extends JpaRepository { +} diff --git a/src/main/resources/application.yml.example b/src/main/resources/application.yml.example deleted file mode 100644 index 23db90f..0000000 --- a/src/main/resources/application.yml.example +++ /dev/null @@ -1,27 +0,0 @@ -server: - port: 4000 - error: - include-message: always - include-binding-errors: always - include-stacktrace: never - include-exception: false - -spring: - datasource: - url: jdbc:postgresql://:5432/ - username: - password: - max-active: 3 - max-idle: 3 - jpa: - hibernate: - ddl-auto: update - properties: - hibernate: - format_sql: true - show-sql: true - -booleanuk: - app: - jwtSecret: - jwtExpirationMs: 86400000 \ No newline at end of file From d38a21707c05548b8bce22243fb50e3748b2719c Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Thu, 11 Sep 2025 13:48:53 +0200 Subject: [PATCH 15/30] fixed foreign-keys in profile and added cohort as part of the user-patch --- .../com/booleanuk/cohorts/controllers/UserController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index efb51fc..0c621a9 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -1,5 +1,6 @@ package com.booleanuk.cohorts.controllers; +import com.booleanuk.cohorts.models.Cohort; import com.booleanuk.cohorts.models.Profile; import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.response.ErrorResponse; @@ -58,15 +59,21 @@ public ResponseEntity updateUserWithProfile(@PathVariable int id) { if (user == null) { return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); } + if (user.getProfile() != null) { return new ResponseEntity<>("A profile is already registered on this user", HttpStatus.BAD_REQUEST); } + + Cohort cohort = profile.getCohort(); + user.setProfile(profile); + user.setCohort(cohort); try { return new ResponseEntity<>(userRepository.save(user), HttpStatus.OK); } catch (DataIntegrityViolationException e) { return new ResponseEntity<>("User has an existing profile", HttpStatus.BAD_REQUEST); } + } @PostMapping From 0cbba952a59152f7695c2adc7221a8a55f216d23 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Thu, 11 Sep 2025 14:15:55 +0200 Subject: [PATCH 16/30] Added Like attribute and post and delete mapping for increase and decrease the likes --- .../cohorts/controllers/PostController.java | 258 ++++++++---------- .../com/booleanuk/cohorts/models/Post.java | 13 + .../cohorts/payload/request/PostRequest.java | 33 +++ 3 files changed, 158 insertions(+), 146 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java index 0ebb376..c47bbd0 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java @@ -23,6 +23,7 @@ import com.booleanuk.cohorts.models.Profile; import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.request.CommentRequest; +import com.booleanuk.cohorts.payload.request.PostRequest; import com.booleanuk.cohorts.payload.response.CommentResponse; import com.booleanuk.cohorts.payload.response.ErrorResponse; import com.booleanuk.cohorts.payload.response.PostListResponse; @@ -56,84 +57,83 @@ private User getCurrentAuthenticatedUser() { return null; } + private ResponseEntity unauthorizedResponse() { + ErrorResponse error = new ErrorResponse(); + error.set("Authentication required"); + return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED); + } + + private ResponseEntity notFoundResponse(String message) { + ErrorResponse error = new ErrorResponse(); + error.set(message); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + + private ResponseEntity badRequestResponse(String message) { + ErrorResponse error = new ErrorResponse(); + error.set(message); + return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); + } + + private ResponseEntity forbiddenResponse(String message) { + ErrorResponse error = new ErrorResponse(); + error.set(message); + return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); + } + + private void setAuthorInfo(Post post) { + User user = post.getUser(); + if (user != null && user.getCohort() != null) { + Profile profile = this.profileRepository.findById(user.getId()).orElse(null); + if (profile != null) { + Author author = new Author(user.getId(), user.getCohort().getId(), + profile.getFirstName(), profile.getLastName(), user.getEmail(), + profile.getBio(), profile.getGithubUrl()); + post.setAuthor(author); + } + } + } + @GetMapping public ResponseEntity getAllPosts() { - PostListResponse postListResponse = new PostListResponse(); - List posts = this.postRepository.findAll(); + posts.forEach(this::setAuthorInfo); - // For each post, set up the author information - for (Post post : posts) { - User user = post.getUser(); - if (user != null) { - Profile profile = this.profileRepository.findById(user.getId()).orElse(null); - if (profile != null && user.getCohort() != null) { - Author author = new Author( - user.getId(), - user.getCohort().getId(), - profile.getFirstName(), - profile.getLastName(), - user.getEmail(), - profile.getBio(), - profile.getGithubUrl() - ); - post.setAuthor(author); - } - } - } - + PostListResponse postListResponse = new PostListResponse(); postListResponse.set(posts); return ResponseEntity.ok(postListResponse); } + @PostMapping + public ResponseEntity createPost(@RequestBody PostRequest postRequest) { + User currentUser = getCurrentAuthenticatedUser(); + if (currentUser == null) return unauthorizedResponse(); + + Post post = new Post(postRequest.getContent(), currentUser, 0); + Post savedPost = this.postRepository.save(post); + setAuthorInfo(savedPost); + + PostResponse postResponse = new PostResponse(); + postResponse.set(savedPost); + return new ResponseEntity<>(postResponse, HttpStatus.CREATED); + } + @GetMapping("/{id}") public ResponseEntity getPostById(@PathVariable int id) { Post post = this.postRepository.findById(id).orElse(null); + if (post == null) return notFoundResponse("Post not found"); - if (post == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Post not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } - - // Set up the author information - User user = post.getUser(); - if (user != null) { - Profile profile = this.profileRepository.findById(user.getId()).orElse(null); - if (profile != null && user.getCohort() != null) { - Author author = new Author( - user.getId(), - user.getCohort().getId(), - profile.getFirstName(), - profile.getLastName(), - user.getEmail(), - profile.getBio(), - profile.getGithubUrl() - ); - post.setAuthor(author); - } - } - + setAuthorInfo(post); PostResponse postResponse = new PostResponse(); postResponse.set(post); return ResponseEntity.ok(postResponse); - } - - @PostMapping("/{postId}/comments") + } @PostMapping("/{postId}/comments") public ResponseEntity addCommentToPost(@PathVariable int postId, @RequestBody CommentRequest commentRequest) { Post post = this.postRepository.findById(postId).orElse(null); - if (post == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Post not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (post == null) return notFoundResponse("Post not found"); User user = this.userRepository.findById(commentRequest.getUserId()).orElse(null); - if (user == null) { - ErrorResponse error = new ErrorResponse(); - error.set("User not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (user == null) return notFoundResponse("User not found"); Comment comment = new Comment(commentRequest.getBody(), user, post); Comment savedComment = this.commentRepository.save(comment); @@ -146,13 +146,8 @@ public ResponseEntity addCommentToPost(@PathVariable int postId, @Requ @GetMapping("/{postId}/comments") public ResponseEntity getCommentsForPost(@PathVariable int postId) { Post post = this.postRepository.findById(postId).orElse(null); - if (post == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Post not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (post == null) return notFoundResponse("Post not found"); - // Return the post with its comments (comments will be included via the @OneToMany relationship) PostResponse postResponse = new PostResponse(); postResponse.set(post); return ResponseEntity.ok(postResponse); @@ -160,28 +155,14 @@ public ResponseEntity getCommentsForPost(@PathVariable int postId) { @GetMapping("/{postId}/comments/{commentId}") public ResponseEntity getCommentById(@PathVariable int postId, @PathVariable int commentId) { - // Verify post exists Post post = this.postRepository.findById(postId).orElse(null); - if (post == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Post not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (post == null) return notFoundResponse("Post not found"); - // Get the comment Comment comment = this.commentRepository.findById(commentId).orElse(null); - if (comment == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Comment not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (comment == null) return notFoundResponse("Comment not found"); - // Verify the comment belongs to the specified post - if (comment.getPost().getId() != postId) { - ErrorResponse error = new ErrorResponse(); - error.set("Comment does not belong to the specified post"); - return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); - } + if (comment.getPost().getId() != postId) + return badRequestResponse("Comment does not belong to the specified post"); CommentResponse commentResponse = new CommentResponse(); commentResponse.set(comment); @@ -190,45 +171,21 @@ public ResponseEntity getCommentById(@PathVariable int postId, @PathVa @PutMapping("/{postId}/comments/{commentId}") public ResponseEntity updateComment(@PathVariable int postId, @PathVariable int commentId, @RequestBody CommentRequest commentRequest) { - // Get the current authenticated user User currentUser = getCurrentAuthenticatedUser(); - if (currentUser == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Authentication required"); - return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED); - } + if (currentUser == null) return unauthorizedResponse(); - // Verify post exists Post post = this.postRepository.findById(postId).orElse(null); - if (post == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Post not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (post == null) return notFoundResponse("Post not found"); - // Get the comment Comment comment = this.commentRepository.findById(commentId).orElse(null); - if (comment == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Comment not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (comment == null) return notFoundResponse("Comment not found"); - // Verify the comment belongs to the specified post - if (comment.getPost().getId() != postId) { - ErrorResponse error = new ErrorResponse(); - error.set("Comment does not belong to the specified post"); - return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); - } + if (comment.getPost().getId() != postId) + return badRequestResponse("Comment does not belong to the specified post"); - // Verify the user is the owner of the comment - if (comment.getUser().getId() != currentUser.getId()) { - ErrorResponse error = new ErrorResponse(); - error.set("You can only edit your own comments"); - return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); - } + if (comment.getUser().getId() != currentUser.getId()) + return forbiddenResponse("You can only edit your own comments"); - // Update the comment comment.setBody(commentRequest.getBody()); Comment updatedComment = this.commentRepository.save(comment); @@ -239,50 +196,59 @@ public ResponseEntity updateComment(@PathVariable int postId, @PathVar @DeleteMapping("/{postId}/comments/{commentId}") public ResponseEntity deleteComment(@PathVariable int postId, @PathVariable int commentId) { - // Get the current authenticated user User currentUser = getCurrentAuthenticatedUser(); - if (currentUser == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Authentication required"); - return new ResponseEntity<>(error, HttpStatus.UNAUTHORIZED); - } + if (currentUser == null) return unauthorizedResponse(); - // Verify post exists Post post = this.postRepository.findById(postId).orElse(null); - if (post == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Post not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (post == null) return notFoundResponse("Post not found"); - // Get the comment Comment comment = this.commentRepository.findById(commentId).orElse(null); - if (comment == null) { - ErrorResponse error = new ErrorResponse(); - error.set("Comment not found"); - return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); - } + if (comment == null) return notFoundResponse("Comment not found"); - // Verify the comment belongs to the specified post - if (comment.getPost().getId() != postId) { - ErrorResponse error = new ErrorResponse(); - error.set("Comment does not belong to the specified post"); - return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST); - } + if (comment.getPost().getId() != postId) + return badRequestResponse("Comment does not belong to the specified post"); - // Verify the user is the owner of the comment - if (comment.getUser().getId() != currentUser.getId()) { - ErrorResponse error = new ErrorResponse(); - error.set("You can only delete your own comments"); - return new ResponseEntity<>(error, HttpStatus.FORBIDDEN); - } + if (comment.getUser().getId() != currentUser.getId()) + return forbiddenResponse("You can only delete your own comments"); - // Delete the comment this.commentRepository.delete(comment); - // Return success message ErrorResponse success = new ErrorResponse(); success.set("Comment deleted successfully"); return ResponseEntity.ok(success); } + + @PostMapping("/{postId}/like") + public ResponseEntity likePost(@PathVariable int postId) { + User currentUser = getCurrentAuthenticatedUser(); + if (currentUser == null) return unauthorizedResponse(); + + Post post = this.postRepository.findById(postId).orElse(null); + if (post == null) return notFoundResponse("Post not found"); + + post.setLikes(post.getLikes() + 1); + Post updatedPost = this.postRepository.save(post); + setAuthorInfo(updatedPost); + + PostResponse postResponse = new PostResponse(); + postResponse.set(updatedPost); + return ResponseEntity.ok(postResponse); + } + + @DeleteMapping("/{postId}/like") + public ResponseEntity unlikePost(@PathVariable int postId) { + User currentUser = getCurrentAuthenticatedUser(); + if (currentUser == null) return unauthorizedResponse(); + + Post post = this.postRepository.findById(postId).orElse(null); + if (post == null) return notFoundResponse("Post not found"); + + post.setLikes(Math.max(0, post.getLikes() - 1)); + Post updatedPost = this.postRepository.save(post); + setAuthorInfo(updatedPost); + + PostResponse postResponse = new PostResponse(); + postResponse.set(updatedPost); + return ResponseEntity.ok(postResponse); + } } diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index 89c6252..b2ccae0 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -33,6 +33,9 @@ public class Post { @Column(nullable = false) private String content; + @Column(nullable = false, columnDefinition = "int default 0") + private int likes = 0; + @ManyToOne @JoinColumn(name = "user_id", nullable = false) @JsonIgnoreProperties("users") @@ -47,22 +50,32 @@ public class Post { public Post(int id) { this.id = id; + this.likes = 0; } public Post(User user, String content) { this.user = user; this.content = content; + this.likes = 0; } public Post(Author author, String content) { this.author = author; this.content = content; + this.likes = 0; } public Post(String content, User user, List comments) { this.content = content; this.user = user; this.comments = comments; + this.likes = 0; + } + + public Post(String content, User user, int likes) { + this.content = content; + this.user = user; + this.likes = likes; } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java new file mode 100644 index 0000000..052d54f --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java @@ -0,0 +1,33 @@ +package com.booleanuk.cohorts.payload.request; + +import jakarta.validation.constraints.NotBlank; + +public class PostRequest { + @NotBlank + private String content; + + private int userId; + + public PostRequest() {} + + public PostRequest(String content, int userId) { + this.content = content; + this.userId = userId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } +} From c8201477d9d0ed6ce4286890af76cbeade60bea9 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Thu, 11 Sep 2025 15:38:11 +0200 Subject: [PATCH 17/30] Changed cohort and user model, + cohortController --- .../booleanuk/cohorts/controllers/CohortController.java | 7 ++----- src/main/java/com/booleanuk/cohorts/models/Cohort.java | 6 ++++-- src/main/java/com/booleanuk/cohorts/models/User.java | 6 +++++- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index e96619c..168ac9f 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -1,10 +1,7 @@ package com.booleanuk.cohorts.controllers; -import com.booleanuk.cohorts.models.Cohort; -import com.booleanuk.cohorts.payload.response.CohortListResponse; -import com.booleanuk.cohorts.payload.response.CohortResponse; -import com.booleanuk.cohorts.payload.response.ErrorResponse; -import com.booleanuk.cohorts.payload.response.Response; +import com.booleanuk.cohorts.models.*; +import com.booleanuk.cohorts.payload.response.*; import com.booleanuk.cohorts.repository.CohortRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index 09800e1..6b2f1eb 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -1,13 +1,11 @@ package com.booleanuk.cohorts.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; -import java.util.Set; @NoArgsConstructor @Data @@ -27,6 +25,10 @@ public class Cohort { ) private List cohort_courses; + @OneToMany(mappedBy = "cohort", fetch = FetchType.LAZY) + @JsonIgnoreProperties("users") + private List users; + public Cohort(int id) { this.id = id; diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 62d68c8..a252ade 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -43,9 +43,13 @@ public class User { @ManyToOne @JoinColumn(name = "cohort_id", nullable = true) - @JsonIgnoreProperties("users") + @JsonIgnoreProperties({"users","cohort_courses"}) private Cohort cohort; + @OneToOne(mappedBy = "user",fetch = FetchType.LAZY) + @JsonIgnoreProperties("user") + private Profile profile; + public User(String email, String password) { this.email = email; this.password = password; From a2f0e8ba6ab22fc506550c3e74d5622fa7f3b09f Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Fri, 12 Sep 2025 09:40:32 +0200 Subject: [PATCH 18/30] fixed name-issue in userId and fixed order of arguments in profile-controller --- .../booleanuk/cohorts/controllers/ProfileController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 37ba226..1a65d6d 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -39,7 +39,7 @@ public class ProfileController { private UserRepository userRepository; record PostProfile( - int user, + int userId, String first_name, String last_name, String username, @@ -61,9 +61,9 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { return new ResponseEntity<>("First and last name can't be empty or NULL. First name: " + profile.first_name + " Last name: " + profile.last_name, HttpStatus.BAD_REQUEST); } - Optional optionalUser = userRepository.findById(profile.user); + Optional optionalUser = userRepository.findById(profile.userId); if (optionalUser.isEmpty()) { - return new ResponseEntity<>("User for id "+ profile.user + " not found", HttpStatus.BAD_REQUEST); + return new ResponseEntity<>("User for id "+ profile.userId + " not found", HttpStatus.BAD_REQUEST); } User user = optionalUser.get(); @@ -89,8 +89,8 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { profile.first_name, profile.last_name, profile.username, - profile.mobile, "https://github.com/" + profile.github_username, + profile.mobile, profile.bio, role, profile.specialism, From 6c4f00f44df6ab09e0e3c928018503a586960452 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Fri, 12 Sep 2025 09:44:00 +0200 Subject: [PATCH 19/30] updated if statement --- .../com/booleanuk/cohorts/controllers/CohortController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 168ac9f..e1445ad 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -25,7 +25,7 @@ public ResponseEntity getAllCohorts() { @GetMapping("{id}") public ResponseEntity getCohortById(@PathVariable int id) { Cohort cohort = this.cohortRepository.findById(id).orElse(null); - if (cohort == null) { + if (cohort == null || cohort.getUsers().isEmpty()) { ErrorResponse error = new ErrorResponse(); error.set("not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); From 55acadc35e61ece1949f80b8438537590088c43c Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Fri, 12 Sep 2025 11:11:08 +0200 Subject: [PATCH 20/30] Updated JsonIgnoreProperties typo to avoid infinite loop --- src/main/java/com/booleanuk/cohorts/models/User.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index a4dbad6..67de4c7 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -49,7 +49,7 @@ public class User { @OneToOne @JoinColumn(name = "profile_id") - @JsonIgnoreProperties("users") + @JsonIgnoreProperties("user") private Profile profile; public User(String email, String password) { From 09e73196073d37dd1b5acb95a4df2d244bb7065f Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Fri, 12 Sep 2025 16:01:27 +0200 Subject: [PATCH 21/30] Fixed bugs --- src/main/java/com/booleanuk/cohorts/models/Author.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/models/Author.java b/src/main/java/com/booleanuk/cohorts/models/Author.java index 89aa218..dc66655 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Author.java +++ b/src/main/java/com/booleanuk/cohorts/models/Author.java @@ -12,18 +12,18 @@ public class Author { private int id; private int cohortId; - private String firstName; - private String lastName; + private String first_name; + private String last_name; private String email; private String bio; private String githubUrl; private String role; - public Author(int id, int cohortId, String firstName, String lastName, String email, String bio, String githubUrl) { + public Author(int id, int cohortId, String first_name, String last_name, String email, String bio, String githubUrl) { this.id = id; this.cohortId = cohortId; - this.firstName = firstName; - this.lastName = lastName; + this.first_name = first_name; + this.last_name = last_name; this.email = email; this.bio = bio; this.githubUrl = githubUrl; From 6b63b4f8dad5994133e208c03b8e64a22a148168 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Mon, 15 Sep 2025 16:02:39 +0200 Subject: [PATCH 22/30] Fixed json ignore properties --- .../booleanuk/cohorts/controllers/ProfileController.java | 5 +++++ src/main/java/com/booleanuk/cohorts/models/Profile.java | 7 +++++-- src/main/java/com/booleanuk/cohorts/models/User.java | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 1a65d6d..e1d9c83 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -17,6 +17,7 @@ import java.time.LocalDate; import java.time.format.DateTimeParseException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -54,6 +55,10 @@ record PostProfile( String photo ){} + @GetMapping + public List createProfile() { + return profileRepository.findAll(); + } @PostMapping public ResponseEntity createProfile(@RequestBody PostProfile profile) { diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 6d548a9..6ea0d25 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -1,5 +1,6 @@ package com.booleanuk.cohorts.models; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import jakarta.validation.constraints.NotEmpty; @@ -20,9 +21,9 @@ public class Profile { @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; - @OneToOne + @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "user_id", nullable = false) - @JsonIgnoreProperties("users") + @JsonIgnoreProperties("profile") private User user; @NotNull(message = "First name is mandatory") @@ -60,10 +61,12 @@ public class Profile { @ManyToOne @JoinColumn(name = "cohort_id") + @JsonIgnore private Cohort cohort; @ManyToOne @JoinColumn(name = "role_id") + @JsonIgnoreProperties("cohort") private Role role; @Column diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 67de4c7..107ae75 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -47,7 +47,7 @@ public class User { private Cohort cohort; - @OneToOne + @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "profile_id") @JsonIgnoreProperties("user") private Profile profile; From 3565f748e0628345472ae545f953fdca57e1d346 Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Tue, 16 Sep 2025 08:47:39 +0200 Subject: [PATCH 23/30] minor changes made in ProfileController to give correct data in DB --- .../com/booleanuk/cohorts/controllers/ProfileController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index e1d9c83..cc9510f 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -44,8 +44,8 @@ record PostProfile( String first_name, String last_name, String username, - String github_username, String mobile, + //String github_username, String bio, String role, String specialism, @@ -94,7 +94,7 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { profile.first_name, profile.last_name, profile.username, - "https://github.com/" + profile.github_username, + "https://github.com/" + profile.username, profile.mobile, profile.bio, role, From f7d67caf19f627795382c77e772081794de212b1 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Tue, 16 Sep 2025 09:12:24 +0200 Subject: [PATCH 24/30] fixed variables in record class --- .../com/booleanuk/cohorts/controllers/ProfileController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index cc9510f..78c63ae 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -45,7 +45,7 @@ record PostProfile( String last_name, String username, String mobile, - //String github_username, + String github_username, String bio, String role, String specialism, @@ -94,7 +94,7 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { profile.first_name, profile.last_name, profile.username, - "https://github.com/" + profile.username, + "https://github.com/" + profile.github_username, profile.mobile, profile.bio, role, From dfd1960494f04ecb8796f9d5cd5142170a93f1b7 Mon Sep 17 00:00:00 2001 From: Isabell Date: Tue, 16 Sep 2025 10:02:12 +0200 Subject: [PATCH 25/30] Added Endpoint for courses --- .../cohorts/controllers/CourseController.java | 53 +++++++++++++++++++ .../com/booleanuk/cohorts/models/Course.java | 5 +- .../com/booleanuk/cohorts/models/Post.java | 2 - .../payload/request/CourseRequest.java | 18 +++++++ .../cohorts/payload/response/CourseData.java | 15 ++++++ .../payload/response/CourseResponse.java | 14 ++++- .../cohorts/repository/CourseRepository.java | 7 +++ .../cohorts/security/WebSecurityConfig.java | 1 + 8 files changed, 107 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/controllers/CourseController.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/CourseData.java create mode 100644 src/main/java/com/booleanuk/cohorts/repository/CourseRepository.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java b/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java new file mode 100644 index 0000000..13a5f12 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java @@ -0,0 +1,53 @@ +package com.booleanuk.cohorts.controllers; + + +import com.booleanuk.cohorts.models.Course; +import com.booleanuk.cohorts.models.Post; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.CourseRequest; +import com.booleanuk.cohorts.payload.request.PostRequest; +import com.booleanuk.cohorts.payload.response.*; +import com.booleanuk.cohorts.repository.CourseRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("courses") +public class CourseController { + @Autowired + private CourseRepository courseRepository; + + @GetMapping + public ResponseEntity getAllCourse(){ + CourseListResponse courseListResponse = new CourseListResponse(); + courseListResponse.set(this.courseRepository.findAll()); + return ResponseEntity.ok(courseListResponse); + } + + @GetMapping("{id}") + public ResponseEntity getCourseById(@PathVariable int id){ + Course course = this.courseRepository.findById(id).orElse(null); + if (course == null) { + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); + } + CourseResponse courseResponse = new CourseResponse(); + courseResponse.set(course); + return ResponseEntity.ok(courseResponse); + } + + @PostMapping + public ResponseEntity createCourse(@RequestBody CourseRequest courseRequest){ + Course course = new Course(courseRequest.getName()); + Course saveCourse = this.courseRepository.save(course); + CourseResponse courseResponse = new CourseResponse(); + courseResponse.set(saveCourse); + return new ResponseEntity<>(courseResponse, HttpStatus.CREATED); + + } + +} diff --git a/src/main/java/com/booleanuk/cohorts/models/Course.java b/src/main/java/com/booleanuk/cohorts/models/Course.java index 3feea3b..ffc559a 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Course.java +++ b/src/main/java/com/booleanuk/cohorts/models/Course.java @@ -1,14 +1,11 @@ package com.booleanuk.cohorts.models; -import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; -import java.util.HashSet; import java.util.List; -import java.util.Set; @NoArgsConstructor @@ -28,7 +25,7 @@ public class Course { @ManyToMany(mappedBy = "cohort_courses") private List cohorts; - public Course(int id, String name) { + public Course(String name) { this.id = id; this.name = name; } diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index b2ccae0..d4be7b5 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -77,6 +77,4 @@ public Post(String content, User user, int likes) { this.user = user; this.likes = likes; } - - } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java new file mode 100644 index 0000000..d9f90c2 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java @@ -0,0 +1,18 @@ +package com.booleanuk.cohorts.payload.request; + +import jakarta.validation.constraints.NotBlank; + +public class CourseRequest { + @NotBlank + private String name; + + public CourseRequest() {} + + public CourseRequest(String name){ + this.name = name; + } + + public String getName() { return name; } + + +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/CourseData.java b/src/main/java/com/booleanuk/cohorts/payload/response/CourseData.java new file mode 100644 index 0000000..ff08d50 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/CourseData.java @@ -0,0 +1,15 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Course; +import lombok.Getter; + + +@Getter +public class CourseData extends Data { + protected Course course; + + @Override + public void set(Course course) { + this.course = course; + } +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/CourseResponse.java b/src/main/java/com/booleanuk/cohorts/payload/response/CourseResponse.java index 8c1dee8..d18f643 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/response/CourseResponse.java +++ b/src/main/java/com/booleanuk/cohorts/payload/response/CourseResponse.java @@ -1,4 +1,14 @@ package com.booleanuk.cohorts.payload.response; -public class CourseResponse { -} +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Course; +import lombok.Getter; + +@Getter +public class CourseResponse extends Response{ + public void set(Course course){ + Data data = new CourseData(); + data.set(course); + super.set(data); + } +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/cohorts/repository/CourseRepository.java b/src/main/java/com/booleanuk/cohorts/repository/CourseRepository.java new file mode 100644 index 0000000..6a0321e --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/repository/CourseRepository.java @@ -0,0 +1,7 @@ +package com.booleanuk.cohorts.repository; + +import com.booleanuk.cohorts.models.Course; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CourseRepository extends JpaRepository { +} diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index d0d3092..8043723 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -62,6 +62,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/users", "/users/**").authenticated() .requestMatchers("/posts", "/posts/**").authenticated() .requestMatchers("/cohorts", "/cohorts/**").authenticated() + .requestMatchers("/courses", "/courses/**").authenticated() .requestMatchers("/logs", "/logs/**").authenticated() .requestMatchers("/").authenticated() ); From 950f9b6905152c0e3fcb8edc956efab9e9aee3d5 Mon Sep 17 00:00:00 2001 From: Isabell Date: Tue, 16 Sep 2025 11:45:25 +0200 Subject: [PATCH 26/30] Added Endpoint for Student profile --- .../controllers/StudentController.java | 63 +++++++++++++++++++ .../payload/request/StudentRequest.java | 35 +++++++++++ .../cohorts/security/WebSecurityConfig.java | 1 + 3 files changed, 99 insertions(+) create mode 100644 src/main/java/com/booleanuk/cohorts/controllers/StudentController.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java new file mode 100644 index 0000000..6911ad7 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -0,0 +1,63 @@ + +package com.booleanuk.cohorts.controllers; + +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.StudentRequest; +import com.booleanuk.cohorts.payload.response.Response; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("student") +public class StudentController { + @Autowired + private UserRepository userRepository; + + @Autowired + private ProfileRepository profileRepository; + + @PatchMapping("{id}") + public ResponseEntity updateStudent(@PathVariable int id, @RequestBody StudentRequest studentRequest) { + + User user = userRepository.findById(id).orElse(null); + if (user == null) { + return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); + } + + Profile profile = profileRepository.findById(id).orElse(null); + if (profile == null) { + return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); + } + + profile.setPhoto(studentRequest.getPhoto()); + profile.setFirstName(studentRequest.getFirst_name()); + profile.setLastName(studentRequest.getLast_name()); + profile.setUsername(studentRequest.getUsername()); + profile.setGithubUrl(studentRequest.getGithub_username()); + + user.setEmail(studentRequest.getEmail()); + profile.setMobile(studentRequest.getMobile()); + user.setPassword(studentRequest.getPassword()); + profile.setBio(studentRequest.getBio()); + + profileRepository.save(profile); + + user.setProfile(profile); + user.setCohort(profile.getCohort()); + + try { + return new ResponseEntity<>(userRepository.save(user), HttpStatus.OK); + } catch (DataIntegrityViolationException e) { + return new ResponseEntity<>("User has an existing profile", HttpStatus.BAD_REQUEST); + } + } + +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java new file mode 100644 index 0000000..bfa76a1 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java @@ -0,0 +1,35 @@ +package com.booleanuk.cohorts.payload.request; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class StudentRequest { + + private String photo; + private String first_name; + private String last_name; + private String username; + private String github_username; + private String email; + private String mobile; + private String password; + private String bio; + + public StudentRequest(){} + + public String getPhoto() { return photo; } + public String getFirst_name() { return first_name; } + public String getLast_name() { return last_name; } + public String getUsername() { return username; } + public String getGithub_username() { return github_username; } + + public String getEmail() { return email; } + public String getMobile() { return mobile; } + public String getPassword() { return password; } + public String getBio() { return bio; } + + + +} diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index d0d3092..ac0793f 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -59,6 +59,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/login", "/login/**").permitAll() .requestMatchers("/signup", "/signup/**").permitAll() .requestMatchers("/profiles", "/profiles/**").authenticated() + .requestMatchers("/student", "/student/**").authenticated() .requestMatchers("/users", "/users/**").authenticated() .requestMatchers("/posts", "/posts/**").authenticated() .requestMatchers("/cohorts", "/cohorts/**").authenticated() From 5d9384c15c8e98036a02adf9591fb1421b3dae15 Mon Sep 17 00:00:00 2001 From: Isabell Date: Tue, 16 Sep 2025 11:56:05 +0200 Subject: [PATCH 27/30] Added encoder for password --- .../booleanuk/cohorts/controllers/StudentController.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index 6911ad7..a9c742b 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -1,17 +1,17 @@ package com.booleanuk.cohorts.controllers; -import com.booleanuk.cohorts.models.Cohort; + import com.booleanuk.cohorts.models.Profile; import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.request.StudentRequest; -import com.booleanuk.cohorts.payload.response.Response; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; @CrossOrigin(origins = "*", maxAge = 3600) @@ -24,6 +24,9 @@ public class StudentController { @Autowired private ProfileRepository profileRepository; + @Autowired + PasswordEncoder encoder; + @PatchMapping("{id}") public ResponseEntity updateStudent(@PathVariable int id, @RequestBody StudentRequest studentRequest) { @@ -45,7 +48,7 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen user.setEmail(studentRequest.getEmail()); profile.setMobile(studentRequest.getMobile()); - user.setPassword(studentRequest.getPassword()); + user.setPassword(encoder.encode(studentRequest.getPassword())); profile.setBio(studentRequest.getBio()); profileRepository.save(profile); From f9b2db80fe6005284677d9254a083acfca0dd668 Mon Sep 17 00:00:00 2001 From: Isabell Date: Tue, 16 Sep 2025 12:54:50 +0200 Subject: [PATCH 28/30] Added check for student-role --- .../com/booleanuk/cohorts/controllers/StudentController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index a9c742b..a20aac2 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -40,6 +40,10 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); } + if (profile.getRole().equals("ROLE_TEACHER")) { + return new ResponseEntity<>("Only users with the STUDENT role can be viewed.", HttpStatus.BAD_REQUEST); + } + profile.setPhoto(studentRequest.getPhoto()); profile.setFirstName(studentRequest.getFirst_name()); profile.setLastName(studentRequest.getLast_name()); From b7f61fdb82b4cfd74a865a2bcdee7d337c7a3fd2 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Tue, 16 Sep 2025 13:20:37 +0200 Subject: [PATCH 29/30] Fixed hard coded postst and posts --- src/main/java/com/booleanuk/Main.java | 69 ------------------- .../cohorts/controllers/PostController.java | 33 ++++++++- .../com/booleanuk/cohorts/models/Post.java | 15 ++++ 3 files changed, 47 insertions(+), 70 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 9b9d49d..a3c4461 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -52,75 +52,6 @@ public void run(String... args) { if (!this.roleRepository.existsByName(ERole.ROLE_ADMIN)) { this.roleRepository.save(new Role(ERole.ROLE_ADMIN)); } - // Create a cohort. - Cohort cohort; - if (!this.cohortRepository.existsById(1)) { - cohort = this.cohortRepository.save(new Cohort()); - } else { - cohort = this.cohortRepository.findById(1).orElse(null); - } - // Create some users - User studentUser; - if (!this.userRepository.existsById(1)) { - studentUser = new User("student@test.com", this.encoder.encode("Testpassword1!"), cohort); - studentUser.setRoles(studentRoles); - studentUser = this.userRepository.save(studentUser); - } else { - studentUser = this.userRepository.findById(1).orElse(null); - } - Profile studentProfile; - if (!this.profileRepository.existsById(1)) { - studentProfile = this.profileRepository.save(new Profile(studentUser, - "Joe", - "Bloggs", - "usrname", - "mygit", - "95555", - "Hello world!", - studentRole, - "Backend Development", - cohort, - LocalDate.of(2025, 9, 8), - LocalDate.of(2026, 9, 8), - "fnwjkdnfj32.,." - )); - } else { - studentProfile = this.profileRepository.findById(1).orElse(null); - } - User teacherUser; - if (!this.userRepository.existsById(2)) { - teacherUser = new User("dave@email.com", this.encoder.encode("password")); - teacherUser.setRoles(teacherRoles); - teacherUser = this.userRepository.save(teacherUser); - } else { - teacherUser = this.userRepository.findById(2).orElse(null); - } - Profile teacherProfile; - if (!this.profileRepository.existsById(2)) { - teacherProfile = this.profileRepository.save(new Profile(teacherUser, - "Joe", - "Bloggs", - "usrname", - "mygit", - "95555", - "Hello world!", - teacherRole, - "Backend Development", - cohort, - LocalDate.of(2025, 9, 8), - LocalDate.of(2026, 9, 8), - "fnwjkdnfj32.,." - )); - } else { - teacherProfile = this.profileRepository.findById(2).orElse(null); - } - - if (!this.postRepository.existsById(1)) { - this.postRepository.save(new Post(studentUser, "My first post!")); - } - if (!this.postRepository.existsById(2)) { - this.postRepository.save(new Post(teacherUser, "Hello, students!")); - } } } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java index c47bbd0..3995363 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java @@ -127,7 +127,9 @@ public ResponseEntity getPostById(@PathVariable int id) { PostResponse postResponse = new PostResponse(); postResponse.set(post); return ResponseEntity.ok(postResponse); - } @PostMapping("/{postId}/comments") + } + + @PostMapping("/{postId}/comments") public ResponseEntity addCommentToPost(@PathVariable int postId, @RequestBody CommentRequest commentRequest) { Post post = this.postRepository.findById(postId).orElse(null); if (post == null) return notFoundResponse("Post not found"); @@ -251,4 +253,33 @@ public ResponseEntity unlikePost(@PathVariable int postId) { postResponse.set(updatedPost); return ResponseEntity.ok(postResponse); } + + @PutMapping("/{postId}") + public ResponseEntity updatePost(@PathVariable int postId, @RequestBody PostRequest postRequest) { + User currentUser = getCurrentAuthenticatedUser(); + if (currentUser == null) return unauthorizedResponse(); + + Post post = this.postRepository.findById(postId).orElse(null); + if (post == null) return notFoundResponse("Post not found"); + + // Only the owner can update their post + if (post.getUser() == null || post.getUser().getId() != currentUser.getId()) { + return forbiddenResponse("You can only edit your own posts"); + } + + // Update content only; likes unchanged + if (postRequest.getContent() == null || postRequest.getContent().trim().isEmpty()) { + return badRequestResponse("Content cannot be empty"); + } + post.setContent(postRequest.getContent().trim()); + // Explicitly set timeUpdated only on PUT update of post content + post.setTimeUpdated(java.time.OffsetDateTime.now()); + + Post updatedPost = this.postRepository.save(post); + setAuthorInfo(updatedPost); + + PostResponse postResponse = new PostResponse(); + postResponse.set(updatedPost); + return ResponseEntity.ok(postResponse); + } } diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index b2ccae0..d1d24b2 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -1,5 +1,6 @@ package com.booleanuk.cohorts.models; +import java.time.OffsetDateTime; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -14,6 +15,7 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; +import jakarta.persistence.PrePersist; import jakarta.persistence.Table; import jakarta.persistence.Transient; import lombok.AllArgsConstructor; @@ -36,6 +38,12 @@ public class Post { @Column(nullable = false, columnDefinition = "int default 0") private int likes = 0; + @Column(name = "time_created", nullable = false, columnDefinition = "TIMESTAMP WITH TIME ZONE") + private OffsetDateTime timeCreated; + + @Column(name = "time_updated", nullable = false, columnDefinition = "TIMESTAMP WITH TIME ZONE") + private OffsetDateTime timeUpdated; + @ManyToOne @JoinColumn(name = "user_id", nullable = false) @JsonIgnoreProperties("users") @@ -78,5 +86,12 @@ public Post(String content, User user, int likes) { this.likes = likes; } + @PrePersist + protected void onCreate() { + OffsetDateTime now = OffsetDateTime.now(); + this.timeCreated = now; + this.timeUpdated = now; + } + } From ca8c475d5a6435d66adcdd5f0a93aa1fb5669b7a Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Tue, 16 Sep 2025 14:50:07 +0200 Subject: [PATCH 30/30] Made the posts to work --- .../cohorts/controllers/PostController.java | 3 ++- .../cohorts/controllers/PostRequest.java | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/booleanuk/cohorts/controllers/PostRequest.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java index 3995363..0f25e4b 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java @@ -84,7 +84,8 @@ private ResponseEntity forbiddenResponse(String message) { private void setAuthorInfo(Post post) { User user = post.getUser(); if (user != null && user.getCohort() != null) { - Profile profile = this.profileRepository.findById(user.getId()).orElse(null); + Profile profile = user.getProfile(); + if (profile != null) { Author author = new Author(user.getId(), user.getCohort().getId(), profile.getFirstName(), profile.getLastName(), user.getEmail(), diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostRequest.java b/src/main/java/com/booleanuk/cohorts/controllers/PostRequest.java new file mode 100644 index 0000000..f2072bb --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostRequest.java @@ -0,0 +1,22 @@ +package com.booleanuk.cohorts.payload.request; + +public class PostRequest { + private Integer userId; + private String content; + + public PostRequest() {} + + public Integer getUserId() { + return userId; + } + public void setUserId(Integer userId) { + this.userId = userId; + } + + public String getContent() { + return content; + } + public void setContent(String content) { + this.content = content; + } +}