From 28c85be0f8c7dad515c46909ce4a0b9a6216bf1d Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 27 Aug 2025 13:10:51 +0200 Subject: [PATCH 01/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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/78] 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 1091f0428ecf874bf294a5c19f9f1ae9cf23cbed Mon Sep 17 00:00:00 2001 From: Emanuels <47167383+emazau@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:52:45 +0200 Subject: [PATCH 11/78] Class diagram created a proposal of a class diagram --- image.png | Bin 0 -> 104594 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 image.png diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..b9c411ba6190d063c58b87f8906bc6ed6106dee3 GIT binary patch literal 104594 zcmeEuc|4Tu+xN7WlI}`GXcJL{5E@gdM7Hd*RYHg<`#PhIyO3;2mXUqm_hm*2+4p@5 zF?M0hFk{AhUZ~vL@427nec%6{KHZ-_<{mTGb)M&S9LIM#j+aUb()4sJbTAlU@3J+#=s9-jW5ezhQZRkcQ4=F27cc4=!TXR z47Rrv`fpPOGVVSMwh}FK^|GqH-Xzt@TBrSP9~DutyZJ4$<&*&P_N;q@$B&nnxEiF% z=xDzjRh41cbey$Hovm(P8t2GhN4gH%1aZ>rMKjfrtfwhdVurTq_Z~fG3p$DiT&Q=i^KcoV| zYOeMrN&zd@SYIj1C3E1w0aFu`ba#)%Y5wsLp05j$`ltItM)BW*|LIrAVoM`e5nTNt zZTbC^lS$*_ z?6C8pbWg)abFA=nz5Bc!!j9;!C+}0TiV*Xq~i9;EpLYHdotCX+r9p^;JSW( zEp#Dw{C0ghcb<6Xb$#nVmK#_fth1Jc?PS{eo&Uof%gDwyzp5BoUqzK|MJNLAaB%bX z2_}!S`*NLVz0KF29#vv?TdJ>S(-7$o5$Pbg@21MGuh1VqbfRZwu*{2L+Ki}O;vF!b=_@8@A}E>5cr!7YNE6B3Y>+fM zBy|l_cx36$C-zT=lwKadtqkff?{=B>(9aUxq=1+`fcQ|4rp*%{`D7nKzd9Fd_MQ4+ z=f=Zu%@Q$@^p-a(jB4p@)pdos*N5SCV-!2NEtxpot9|~^*2|S!nU!9< z6eSHWcHijdAkHh<4w3>HYz}%cl6(D5$SqskBF-vL=VdI7Skq$>IxG2`{0%sU_`vNP zysOaul_K@q+UZ(byI0>(ZCYnF9>6KXiz8Vt$d6y^!(hit%6g$KeRy4`C)rY9b~o(O z)`Y~wsbjEhSW54THW57Bu3m{bIc@A$UfcmE2nK8KspiK;ynoMeCck)jj@R}ecp@@y_@)^xv|1sy?9e_BX24;VfJNKIaeJMb*U;bRo3@u8>+U$&M}#fkSKP; zw1wTfCeHVA__xDOJtM)|iH8?*^*pH8zrDxNSiPx|(*>JgO7TX<##8HO{`Zo>!3el& z8$ZW5x6>ApL=8QZT58eqE4@0J)?)t2pjnI7ZoADqzf)0lP7<&?neT#wGu@SrL6-(A z5hhjfrj7?_=(i?zB6cJ6-)dEM^>ig2#VLyj9oibCjS4WcRWr$`?h{G4Z{Xd;qAZ=S z7Ry9#4JlSsdMDX=R`Zr|>2$t@mpW+YkWNXr#$nq3m8S{6W zo@w{&621_ith3AO-Q{ei8F7R_r^v$$605TG!1M9XZwPTd0?o47QLXcjjK(wKDnga> z^?Ud+yrjH^f=J$!5B1+-ZZwOF{?Z%B!grq?B^ni}E8tBdy%=f@gn zm(9*cy?#nzpucgVvt78iJ3OpyY;vAmSZ z2;kjW+R*V#>`!s@U&V=lvDJVutDh;&!O>LM)&C)wMHU@TeKB^}#g=eWeD34(;$7O8 z_HCsrJ-4#d%8nN4r|O#5+4L~6stJy!ol>y!doDDNG;f$qF$qN|V|2~C!mYfGPCc>0 zaLl}KTiT+Sm_M7hS20wBG(9=qMj<_LEYe;8JKbZiaJv2TP*BuU>g_Q9Gc~unjb4i+ z-o3~vw9k&HNxN9V&cCP==B@qxqIH%Q{T6qj`qS-H8vH#wPD&9^WX%nmctIO(=hU4R z;J8)r-6lsbwK3%qJh@pMeKPre!!de^FvX&;kK5+R+Ika@XM&rB7ik?i=Gv74D8lTq z;zFvC@c9--ubWK-LK5u}!J)4^mz%0yVI&*dq2mkb6h_Y#?Ns0Dvg5|qytT3 z@E|0VSJWF@9Oy(;)K3IV<|{<{<)^9A+CNX}x9XKW(D;}doZ9%HoeuXFdy1I)vCxwz zXeGcIb5Z2!Roh{+5%r6gJKFpy-ac7%@&_L(6%U%fz~r9B7Ji>su8!s3dGMP?;okTY zqHP_KS>^t9$@k1VBkey@g(lgDLBN~Oqxj0+N3c_C-VEy6ox#l5M@ZA>QzkilPt~2) z&8yB@SenjzrFR@ZU#W=-)ey--kXS_GjIXc+*SHv`(#S|oe%x}^oRiZ>3I&9h`iPkgPW&3R^&Y@^lD&RHC@Y7tSa}d#js$`)zMz zh?=D#N3L#VUDj(1yPD$}r}yq5b(1ECs^olmE#xQlZX}L!@`P~baJ?-4oEiIG6Kkk$ zAYCEp7JIkaYb1prBDHWym&z?a$d{A@9bmBwB@)aS*B-IeaHZ6H*X4b z>X$~DTKl^`9E)yk?nt8TCcet})M`@A^xiUcb6L5c)<7^qul{0qSkDqtKhZ^o^OPgu z5|O#`h+U%Y^D|EDeG%-o*Lm^k4n0JP1F|IBIB9nqY1{n;w354JiiB@CrV!pSyNcGB zr&e>4J(1ScbCOZV=#4n62trcI#+I$k{Aa4=Zs2j=nM^(gPwQ(@2>xXiXXt}c=b(TG6^4Y&ssgY z8mW!L30t9*4LoBg<$_E(;(li|9Ym8`I_5sJh=dWJOkS&fS`)u_e<`uO^O(fpuDgn- zWR!`61_qtYOy>!*@$s$o*R;|1@8?%go&3kM4jdk~O1wCYjoSaZ^5cA|`M60>PDo@#aQIBwHQW#Yr;k z+ftFiL=#epk65|!rpeUleD15Fp0XX(gVf>-H;?_|95YvN?@0`X8jOK`=r~e{awk5r z?{$O+J-dd{i{%@}x8Yv!E+Iq23?sY7w1>poWtW{Gr3gUzx3 zSz694)XO9~Ui0wqnEy+XeD`k#Njg69{x5T*8H|3!$J^B=PRQrL$ zO7p2!-|mXvWxhEw!Rx?3B<(l+q>CE%Jrk9ZdTo}==20^Y6o;?kUEvju$0+uv?Q}Yw ztwva!(H)^w!#=-&!r^|Y-TnBti29_UeesqYDElG@o;-bukAM zR{yQj3SrU7uz+9Bxo6nB^eH~0X{fWpTvB_n;Qlv#hKZ$vD{N}ti!_{io|m3LF{*U+<~1anYdzw0L#)R2i>K$=VvB%;8qzFP{eZ@D0})7a*0Ibt~rCmRfaT z6Op>lmaI69)s>C=(32jUyO;#56A_OzXVbHc_%0?7y-F;W5l1;*7xFWBm1|VLtYcsG zJX}iZnp0Z8ceRdlgF6*f*mZ4JbcVO6kV)v5ZgFp$C6E1G^|41tL$bX3dwzrEWE2#L zJMCm#t(bD&1y!amGvo?MOT0xFcQDJ7^3mTGs=daa^jXhdL)ZMi{~GT0hxepN%=m*SUCh@$cp`fd|Ohk zaQsD)AWFS>eS4+)$m3104$og@a6`HE2Yt3;6Hay;`R#_MEHpGu*%>KG$#QF1)9Ukx zSy492gxl(^Z#!`r_{;D{73)n&85#J)wYnWVcC(TE?B+RqqlenVEQLP99<>VMk{fOj zuzn7h>ZBT%WF~qq;rGGyGWYyEL5{rm(ihsqOJ4vfjI4+-A16KMH%-oo67R$y%;Nyax(JGjmpv zB@dxi4w=(FD*-*1Io%I5Y2Fs{9Za5GR;$sd8umoREV|EoPA25^?NDF-*k!zO7@U{e zOeIwce5CVy`U8@IXR+_pgRhqt+En}a411_;qZjWJ{$$DzYgeVXHvVooBN5ch=gFvZ zriOIf?Zr&BZwtyexamOt%A-ULow29QU$Z8Bu=cbW*S*pHrdrseIsz1`<*)~P3V~F0 zn@@Jsxu~~S91-$G@;WiXZW|M)Tvljxk%AZ8ly_b{AotTyN(riW)1Abi;A)+?g8^LU z%9)+c8Jn2M_^PU@86tTGgZ-jz2J*$n#FVr14Hn|!HsZJ+pdKo?PL2VH+}u~}f5hzUIgl01j8N9jfm`CI&;OMjX)%=ZF{Iw)*avc z%^eka;LV6@@1WvO>G>LQuv*mNj^|@+)T@7?|BuVd&YidymgvjdF2CWZcF|c3Y!Beo zprer-m-t26EBK2Ez7}}{Zp$vZDLVPbWgL4a)b-xTAw&tKBIFN6 zs!9;*1^UUB#B}e#L^$msmQvUiLcQo3&{1(Sf2e9?wFtnAUys^8=mpiH&Q7f#(c*YoUfu%+z>>f&l?87=ftT)^ znwfd<-Tvs+Et}ibb(^wpGkD9@wU26TZ6yL~skXMZl3B$%7=z(IoQb^4%(JJ|_c7&= z4ah4F^R+>VlNEHwVK5O%T4c^TV9r7ITdaRsVq)S?$O&DQtNf||ITBiDOs*NEYs!`T zQ_T)>@8A05)47@-8}|8LdIbIheOvttRJ5~IK@|!dK0f35+A$tV=i9e$v&_3i`nm^d zQOJJ+5gOnAhDBk-!C+UvO7t(ihJU`6l9xPIp5N?RRpKO(o84nO)n&+W1*Y^^&w2J5 zSnT8Hc$e+6>ub{6-uzDf}(g zP8~QWYQ)U5XU{rNMcij-*7Rg>P#_HCH^gi=+pFIqrdBc`{p$oWR#KBPZ>Kak602ux zO?EFBl<@-w?CYQfv?n4b_@>qKH|#m|qUuEjnY9<8pTNGkt>N^vv^xx&xX*Q|IW|Vt zoQ-eXO*Qqh8B2-^imat8xGrQ@(2zQEG=9<;{7&B9zi<^>!!J=>eFUzTdvYH;Ef7L+ zR+=g-6lUK$Dne0!eq(wgqxZ;VSbbTtIoho+%R0iqp%wWA;773a;BT$8@nkJ@U3BMG zDVn@I)_qU9cbxBK3KW;VzrD*tr1prpdWEK}%H6m}8H(fPN^^3zwu1M!!7WH7#)qwSkE>%vvMf4Stcly4M*8f`vq9@J!Sr%QWG7cha(@XQxOt z#ER(}BD$NJd~?jZ)h#`{ok_7~4T|fG){%-lj~QA-UHoP}1UX z@2QLE_5>D={ z8lJ>|@I}2IqFuoadZDASk1n098>0Odt<7pZeJipbS4+*yv83vVG=cRCqI80=8d577 zn>cB#efzfZnM1r{mf|29aD@0*;A~&#dcTNl**XW2C5AF)_ySE@+XBL$#?UnxSUOJU! z{7B={iG^#2@050n=8Zd100pLUy03c^tVGS=7m?lm#WRfk25+B@`}oPR7^i@7|MF8> zhuvfwQ!cAGJ06&tZnM7--t8a6EL48~RGF>ag!9KS`K}$FpU$Z=yK(v%T{6^0AyYL* zoSu?wVI|jZX=|G?e7LgqjIBm>U6rvux8-nqvn}IDiG7|LSnK}VEj&QoXO_k zza5Lpu@s{?Jb1QX^#PJDQZg_uMgG+Q$$;FF6CKS7o{{8l%(+X`itXW;^&Bns`Y{tE zi~Jz;*R&3OpPqX0#9_K*W89JckQ}Ae2Xfz&b{uVixSp;>v9n&peLPq1cxtsX;sW_} zmF4BV{>x}|bf@ZKd(!H&FNX+TyW1wUBmd&!%kHK78uW}5B6Odj)~U2_7s_>=e=I?& z|DP0{*qxO0RmK|%ydsAoHsJS?PoJK`p}-1bME<3AhF<=&NK@=Y;@SB$qPs3s;%51z zHduG?Odiff-okGtiy^W3UwJ4~y@l*!?!+^(O>+;bl>xIL2Z3~ zpl4_9xjl?-cPsN;Jj9LL;xQ*vLnd z;tz~RPgge-{+#aPXOIY2|2!02(|KOJ*tY(bJ|IDXxBOO9UvI1+K5{1me`5F3qlmYZ z3%T;XpvF|T_Hp|}>Eyr>nr;>hxPhY5Cf{=S)yXlNRBZ>n_b3ZOBxY3O5Op-xtYs#+ ze>tX)5FN99n{UOEiDgFC`m+4Bbm|S z0JTc6d{lSFk!fb?J8=j>8zT4MC5;)FAR=^Q0XmFo>x`pXj%x6ndqIEZPfYW_ky^N9 z>fxlG1dUh{R6v-gwWogP5D{aIUn@PXA}SyE1XkxKNh5;S4u;8r1&^|OoHJ7*{S^zf zCiK*A>3;U^lVL)&yCiRXYGQ2?ru0FTws zpros7&akOOEm*88DVIt)?F5S4JG~p!I2eqz;ekfD+mll9r5Tnfd9Z;c2^;HQodDvd z6GDAwV_ZOhTsSWw@hglSVqyYn4)_yiZ)If#9=NSgpb5WML(H}G;e~bF2N_|$Z!Zu? zN}#g=6-n)V57CJ%Gr?Kzn_!F&M;>IdI5vZ8zO!ZE|Bl&jtdG~>JLGYCPiaJYD><~7 zH->89J!U<3n(abAR3uE^(95sLUK;t5SDyNw!zfAAQDlM-cIrZ^+5H^l&u9P_HVZc5^Fb<39j@rIb2f3 za^N_Y;vxPwJpAgl_?|j- zs->yv9s|>A4h2Vwf9K}TmF4eKqM+<3M-r;{WOWD6SHV+2iWVmLRs0Cn$tnm+9)F;f zmNyD0pHQ%FJ~a+x5ZS(N^I>*7XbZ|!mPkZPF{Xah+>f={a2Xo+ZEe+UY-|!o{v2}4 z%0QtG?LSXnPfy6-hJf*L(;rd7+&uZmTYl@nx)iR*y3}8Z4yNi^Fg?r2Ke-(icM;_5 zjb}$wQ!~vS`mXT7+Pb=5TJm46;WxnLO@0gkDMdzEO)a1}{&%E;FP}a(iA}<`qx9Np zO#Y*~{u2eBgs&{m7c7kgPp$1mEIpB2c2D5NUCr}3KIHs{)iCaNq{1aXq}mabt7{Ud z$SoW=B>_db-Oe2Ce_Z=j@qYE=|AN8S$n{h-b>hb(gmfNa1#;x?CCr&AyeNL%f~fcJ zS^jg{1qrcg`03F%)Ov5PmNlprf%$e}x_VfrEfQ0^b{4Zq#d7*i`6ZQYej%u7FW!9N)urRC^)vh2}fJN@`%*)drzW1xzg;ri^ z7BCn;1aatve|G|oZjRJ_^4`0yqm;G8ldQdE+vV_ZvzETR>p?D@^&LZILf!o=mw~$P zdT&FLfR((Dwy`+=;Wq;SF{ix1ca_*w{74d)2#`)R^xS{iTxs5AMZNa2vhlpObif{8 zmfBRhX$`?g$t7nsgIEuyB_MP-_#+%Leh zXFlqW)D21pZ)ihJw4dUlv`*%E)4n}ha@m$ltujA6Mc2~v-XWw-zevi9BrnS)o>7lm zbO=NP4-$+QlAPCaY1(SZQ=*ozF3KQp<2fkT*enh=#T#d^*6%Tm6N>M--EBSdcq&Lx z(CF&R18n0}Bi9RATAD?U8+g_{x*|mpI*<)$=DDHyd9KzrimMKn4D*z!lDG1EI;j`2 z3`R5kDVK#Jo)5~(0}0t;LZRL|{_fYb82K1QZKS(D=1X1D{=Hn3C<=>*!pynz;Wc!F z1J9d^*@AWr@1_f>kI+d&nFSBhhW7c~7^#EuI!=n<9$*?Q_3 zz`hEDcn%fF=MV_woGh@ys38x&m5(->W5w-j2>`sI$k5&M9bOuLCj)w2=^SO+*QvkB9msUkyf zO3PizKRGtYg>39@Vi@nnouziMibp@W>9}m{8RbDHC>F~PaRjxmI1L;sHlJ%3Ug=(O zeu1i9Qd}l5$}lYy=3;fF>`>SAmrML5^X4N>sZC4^H1e<$vjJfej%+CHtgFKA1s*H$ zC5vH9HQ+f3cn<8XQ1SSiE3@tk@piO4x9R0qw83ZDf+O{U6xy8rD0O9iOV<)BA}RGE z>F$)W(ZWm}NtO%G5h$6Z;%==xkI7ThNYQC5Q!G|NVw`V)pM@zb_a*tV8nIXra=zLX z94oR&V5Lv5ZebD>mgJYHI`1)Q#43GSoruu_q{*p0Qcii#k#)=lBY6DOhy76QH--Y4 z5R{w_LrLS80A=8Ln1dtNU3S&|)p;isGlAjwQB2mT5m{HYCS%DU*`OFvQc(P(j+6f9 zPNaAW7!~kaSU>1^K9DAJ<9gfKK(;X*_M3a~GkSMEpUmjCq`u`a`+V}CXng#j)AIrU zF(<3X9DeX=m9y1DcbVVESG@NWm-Z9@`jm3VU5EZ=m&cJY?s6z3(7Ng+NuTh#MYu`5 zBlW306AvNsa+%x}QMY>GH7w+OP+efXCxV5{M)8@Tl>M`gmincp##4ZOJvSkJF<=XJI+W%e-3@ojP+I6V^n`vBH`nKrjK^Lr^ZA$C7Q<_E;!kC?+)^a z<*fh4w^PIZW2Q*il+{9l-mHkpC=FFB+a$!0V0=8KbO&AC)YpAT6J^J*W0Ti9t_Rf6 z67-9NQ>>?T3i*$y%*X^tGH5${vv462cGPiOeaxY_pEPUgE}K~bLHUl*2KE7V{7ujq z^Dm0`+NjIDrcIYQNm}^|@mxC(Ze?Sc2yQEx3YsN8F^Mi07sM-@P5O{yg!2?lIhb#0 zBcA~wp)S-tYTz>dzA5F>$Wv2@_Sm6tT3|BoU2t@YlgLVkBmVj(l{1XtoTO_(5y0LvY;DQEtjIh>7Yde<3qA z{eFq+Gk-1C_#HKp>%7D0ow1Wf6DP!xp;gBsfw~(V>+q&a_#GQyzV+{J7O`4cMVyyY zbf3j6pZq>&a_AN1TY2fZ7JIvCy3U{q?!p<2i}R8y<4|uE;@PH&{65Jym3y+7;0Oac z5L5gVI)bY^%HM5llv;#?PF+$>gv9^nvNybD~Q>x*B!$fn;lDXzDe_kIZ@p4D0c z7E{+RHUC>c{;Hb(w}AZL0`hMm!GE)We3RRSgxITxs@d7uKezw@{fSKIStc?Cx8ekG z53oF=4dg;qN13lEnDJG4mtAyGLV>?zA4RVs!bsg(D5e z20lRKuVUN>Pl%)vEI-%_aw$ZGF}u-~rrqzsUJC`;v=4G|2$pjn@_=*+BH-F#C2pdk zqQ78=4q(y0FdHy{{{w5^vr3jib+Tq<{|dE#!U{v_Nr-*^sJCrM^&qiUlxbD0+ZG(x ze>S)09tRN!I!`?c)l!b`Db2Xa@IQeqKP8b>b~RZAhbDN_eEP_3x#Ku$?n=(RIzvF$ z|E)9pCw5&0_J_tRUg?9I_{y^TLMwDPZCZEK7n$PzTPoVRwD2c0^(yxYXk4HeA?8jX zB2rNciM%5lVCFxlJSL$OfY%dJQY>0^J}gWI4h1)_U*)=xdWjt}8D5Hhzd=g{SN>Bb zTVt?(yelC&IeXoAdvx=v+XQwL5Frr95PrO+wY78IWS&;(X}ir-Qrv(62?LHz`h#1a zJWaXfG{j;^47h0Eom%@4ZMIbxhJY{}ti*8*39SNMZlAXNm(Q2+_xBMvAsaHP)Gib>+nBO_A71kN}XRXoN^9^7F&R;Jh+5hWJin9@GM%VE;e zOc_~Nte34vJ&4^q7Bzx+ocIfuq$2oSM#~k(NJ3&`5t39DWb>l*$ij+V-ZkV+L=2Q-a zZz&eIFbXUS|9Hu(X1-O(r5)E#r}7En7N=GnPmvhsg3P{uFQo9O@EBsM!H{q99t0^=k3-sIMe>2BSV$_sK+GhHHB!K8>yQ z>#nh{g%*C+!M>p;3l~G+g;OtT%9?$REt#J47JbzU9wg>FX8L7Ikw|9hzF`hZ@B@$8 zB;N#G;ENULizAdYcY36G@dUZl*qiB$_2k>?Ux?}R<8finu@8QaZA|<#_(+u>NTmS? zqUzhJHLm)y%W(pac{FD$07!6iR-- z=9RS&_aVIgE4Jq^P{g7@VlhCB50Lzk=gWEHXdEs0J=g!2r_h1=z)RT&28 z4x|N90!O-pA5KHhmc;c-Q_DIA<(pCS=1RpLQ{*bOsn+_Jo+s8TM%k0$lP3MX0jaQ*(+UgV zIPd`RdpdVo$pos4V6YSczGa|stv-XNS(+UY(ZFhMZ3|+11nHJ0%OOj?ETxCJayFDqws_3ZTzYI;$jGXp#}?p zh=98yH1t=^3bj-0gMAf;1X(FGceE30i*r9t&FW3doMYQ9&P)sjXqRm?1KI6Qo*qq8 zFE&zd-hMe;^Nf00f~~{FolhCn8JY0D#{Mpod+AMXxZCzcK!s#RA~KU?GNn&@;*@3f zqGEYOAiMltba&m!dD1SZjO+#y??_wC(E0ua{?>-zx2t93$5;2A&@k7thHL80PH+q< z62^a0yZ=>Rm5d{832!rBE84v-u>EQGi)X!%Er@0J6(uIV`$mgZ>+)1A-6#peP)=Y) zh)2&_jCE+U3KMU{J?Z9zH%E6EGw?XiiNNRk-tFBZ9FMfrA@y0S>!wqi#^k0abp@u{ z(>`$_o15@^O}YzlDcH)#Y>ScV!U}}b;J0LqTNx#ypAEQLWUgz$F7L@*#R*O^H`BKm zZ1ZkTEXQM7ag@gs3(!kDx&%p$T5PWt63p+(%{a!$dy%qDE*xnv2b`UmN@|yaJ4>F6 zdPQGXoE|)epn*v0MLUz`hQ9bve3~6W#W<#jHtDt$mC2-5*PkZM>aUC-T3@rF)d!RLv!HID6>76L za?Wz3zLPS!V*?fMt;aKsIYV3H{chLUEEljl%}13tRohkP$#1EXY1EQ`Ewh88QZ8wn z=-x^ik%7-j^R84)cGia}tKgs1xL5~=Ioil^U}a<#G8`hF_gjUp(o1U{pR<^GfCS5eIm5l;N`$_?{2c;w0@p>l#t!ZZ+=QW7YUq{gSqsPX}x{G^&5{MWHh6<)zDsv^z4f=f>F(Qd_e29W*-6fKT(;c9si0M~(@s3La?mFO zILZL2pXt2;wz8_Dw}RyT~iY-bT&^!rs%5jR&$)qx@nsm{6>scr?t-9PnEBaMWcZ9M6P}}b-a?KbJ^K@@BrNLtEV8-!$R~8aqCB|%_56%1gu2;oJ8T= z=~row*E8g*DdfxUrzPy4#7#U!d4Q=6^EnB$Z#?Go@6iY`6F<2Cz<9WnflD(>jm28^ zD0O)scUucDTqOFVi!!C;_bx})gZF152R=qHeiobB(kvS%r3W%f$Mx0LTTUmIO)^!@UbvrGSbvf z!J2!Nr51q$cJ-7+Kx7B^|2a!LxlEEvm<&BDF=2N{2#=$an=jt*z0Ka{;3sAy#TR@l zb$WJK1@38w=X=qX^6_CGbLOY5lXFxLt0|WA_ywAv2Rcyk#9cb}Onc{i;}L=F7WJ{` zAJCn8N1`8@F(@ybXRpPBd_C6|wkTXEt*L(>D?a3GU<^R|R zP>V#VARU{7A-`3b&&9%&3$hw#Qw^G9VZ5& zmjb(hQ-QfW?W?$Ql zKH0(E(9rOPMp~iulau?&XW7K9Z%9c=i9h;Ul0az|p3L39foT8Zy8o;Lf7PPfQ3PqU zDm^{DY_#%{3fy^AilYf2AM39rnc!o-m3cJHTOm|D+73tQy_O%KQ;h=S4gP`WT%&HS zaG+BJGaMmW((wYoE~Cj9+3*61`7*?{^b!- zVG)rSewVSEj>Jx5VL`zN#BwR3<>dh`LLrV)NM5W!q*Smw$w_;&t09r8#o7R#*!#kduhQ6jbbxXOd6-opmBYaKGL{8H4z={LLrXDqRFu~)n{{8Phm?> zPZF8z2)vi0PB@a&0@NymCfT85fhvA0IXSstp-p>X+774f&cM;;b3&Rh8tFZ*f+73U zzB14*flopw;K+%%CLj^79j9+xh zoQFnih~PvLcbjV*0?}@l-6D5 z@fq6m3;{=LUugnnRe+#@tu_{NHP=g0lENHD4!ev9VH!(HTv2#Clr(NZ8y5}D>u~($ zBi+)`@jwH@{OW4}Zw(AQ6eRUnc|$C|simdz3R!=}j9Qht{Hma!z#1QFH%{zEwkPNI zCD;MqD!6796%{`)4TbOwvG|P4wek%E_>;~;?N%33Z)|U&9dD3aS1|aBw%Ro-?K?2^&nJwL^6D9HSTn&Uz%udWUB%-9kVz#xi5$7=1q2x$%%)g60}Xj5;q1pCh743w+}#pTb6qb@T}nBp!k4aR2jgSI zn!sUob90-N@3TDcg#k`@v-b8s2rVg}7{Eg}4!s*Z&yv(ueQH3lgBo;i3rJcmZr~uq z2mF3;RTNv_fEPWM)o01jGYM^g<5+ayWG!z2lxv`i1zjntCvGC;ua3X8vgJX3S_Fz{ zj8>lkw?8Fs2w(jA4C)m^O|_$H`+$I$c5H);15Cq9wf)QTAR1Fs79 zL}3`fY3oZ+XqX|y*=$^7pSkRnt_8FnBq13 zeD{X!6@Emq6dC`~F7P*5^_gmt4p{-yIx1>EO~2 z5_yvN80G!N`EbYXz2jdiRm9DZ-GJkMfAM_Mxiet|P>X8JlriaNxhtKn0mRk$yUbms zoX%FD?;Tjl7y`aSe)nIhz$(h)3Ge4Y=9dT$TP-iK*Wbd+ZF62}0^CsD!vPd-g@Qw( z7TwVuphftQA(Q}0&I=0%ftZ~CpeQ68@;1rrF{XKv+TMFORA@ zD>`D2ZFZR*4jDWp9MGTDgS>FJ#aA%CPKWRA^^Bl%A%ZT*Ixi;4wk(ZLYZU^MQz3AvN-hsDOh8;mIQlyS?eZ;2mPnS1K7coGq1};XvA-?!A{ck9 z7k;?P(=HJ-UcD!IpB6S;GwnT_SPFs0KggeeQoc`JmLt3Ex05Cj(iPgVnfb?T?vw0@ zDK+t2o&u1&@FhF^kqnfF4^bJjCIX{s zX3$YhhfjruinBB;y$dmInn~drvm-e%t?grVa<3T~J}2WrJa#<};&F+MHDUIH8WJG2 zXqS>No_0=(;;4F(vv-)|_~~A!nPRA(vOvDB+g;IObvz7iBSjPm@CEWIjwi?JzL=R@ zV^v>BP$b*UR=1<-=co;4ZD?`GH3{l z`{i>lu_0Ewi&yml$XXf(cOtZ?D2`!h&1h5PtC~p;_^n6dZb`k5vrTS1+lFthjfX}h z9DtA4htb{3;#F$s(sxX7KOX6LCz=tr&0ClpbnLWU*UKx$?A%(j6P#G-+2#Bv#x|2r zRHmumYj14OlYs*Jb+t78u(NiY@KUC%a5|08qAlfq?0IXP!4*3QXXnPp3Ruy(zdo?b z>jK|vb@a*Tt2F<8&t&%oiZP{~%sv@K$)vE}G;s{qRW6=(`4)a^#;hh}d_YLX%UagJ zuMW=L*97LqJbUkbyW7rgVi_{YZYjIxHLj*_y8<4xN00|SC36NvS{ZNEzy&YO7nY$n z!@kBUkWgn^mL^}c(hV!f-@+l42HL098g24P^eOi5-92sT4;c~Zjfln-CuN)nU+hJ+ z-{repy31@-zN4#E@=k{C1Q^S6J`d@@VADG!cq+HocZ${R!>82V!lD&M34qBHKbH0K zFoC@HD71L4DzPg+^SZDwA}&<88jS@%+Y-ompg*_g1$r)bWI1(v>L_@YpWPqn@iq2k z$6YxRT~qeE?`wS|G{F$I7@x>XMjEKTxr0LWf!S5%_eIt^??bvz$p9zm>G%7%i!jvL zoBYL5tQL94(E5Qu(!JpeMi2$yqsQ>q5x_!fs9TQ$JoK4@)C?xOLi1Fc^!J;MF|5r# z45uas7Hq&s8m|f)`|+XzE4K}2K+sfdaWPi4Q|*3x6FBssv$Jt9;J&un zYJYyNyN6EwjJFq!dJ|!XgR%oTb!7oJQsKHlH~>b{k!y@4hnB;*mlqvf&<_bS`s9F! zU*fvSGtzFkyIZRNgd_h$w_SoO>IHH{3>v=G$AR?qjJm{$mL&CaiH>|m5|3YEV*#d% zdc}+pzBWJRI)$7=3d>88XV9x&@r9iHN9R)%+xZ=a<|vHz1$aT|{U5)i;*+QPqv z-vwayZwDX+WH2>;+?*`2Uc*DO|4Y}54mPhInz`0>{XQ_4C_H$uLKpkABT)6;U|v|A z1t#c7wSIuhFg2=`_+Vt&m!UUE338E|oG>B~T&)*NWQ}c)sAZ%}j|aaMxPBs3YWz#6 z?cf!hxMOq+aCDp=yE<*U5Ggy&QT>`#p5zr@fo)jjdLCN-(nlVJLc@m0c1> z7!9{u^kHxI+ZnZgns&Mx|2C^`ctr8$=j>R{Nrh zKk(21J;rrn^vjL|iC_vIn70y9?@q+msriekm3G|EZ%tpF8%MAg7c!CXQOfvwq-dDb z9Al5Ngr;B}qYtZ;>q(e+9gzAhtWIG+e)2W1ozXDCKL7BzExiI;+idwmj7{-5$Q}mN zy!(fbKB8bB#X^EOI_|9f!^oIj;S@?3I@rnHeZ@wn-;ZLvu10I}X@s?P7-v!`NpD-}*- zyG+=lCQVqCl_8I?QUx$Ml@|(;x_gs-Bn>hB;lyusvxSA)CtM81zewUiLm7S_Qh>i z{H$@IR?0|!>E%!a{#H?{9McH{cAxD{O992zLosm@t^qEj!v5C8RFpvC#G3=SS0B6$D+qHz=Ty9 zP*C$a=u40n-}LpOGFez;>$i!*;faVNE+{GeJWKf`l6PLa(~n5{bmg5R^dpQakzcHhC$5&{pr0P0brbhCE8)v*~wL9fmK zLb+Al1p17)wGQt{V!{m7lk8uM$I>m*qV?lJ=Nuj>c*Mo|h7-C-M$mV2!17WL8q>3t z7bTQv>usmS>pZ2F_k#DSHdIZjyI7PVHDnEn;j-PlFEVkBZ}MyIUpeuDQh=5N13H_| zx@V&Y^W3It4h_v?VKdlGQtku;5`}Yrupaa8& zW_MMHmG7>XMuEsqarbAgOWLjggaO^irO^i0^Y@?*o3XyV3;J)OfRGxSE>Qj;%IXFi zXz@us7WQVZ;^Cm#{$_lP@+;(k3=rR8Nv|Q+9d>m*vHu-!p6(g4)%*jMqS51lZwX@2 z+RBdiF@j9N&gYMXGOJpQi}u+_k399E_*SK&v2WVm?UyywSjj=WQ+5X78~bK=(%d`n zS?who*csSyt8dRQn~fs>-&^!e22tceQ=HAnicbAs%GVDjYK9T`r2Y z=YSsVyA~EgR6Mn9)n5QhJ5)H+Fz^-(0nFk524;u8X^D#>$3&d&Zj#YuGPAspxfyyq z?aiUsYLfx>f7|5}uWpfU+iQz5Q6&yL+ZmN{Bg|-;@ z=H@q!uZ>RmOLI)$Di}_i{z?GU>R0h!CQDZv%h|zehYi$=00KX^=O#1(Sj+%Yq=3zK zwM8DdtB_dm5}#ht0}fwUN;&p&(16Io`4QT)=+yz9TRP)=SBEJ9gW;q|^!dJIM3<-T+}Cc+ zhC_Vw`3F}>tj%;FMaww*FMi!$E&uG$=$AF3>hC*z!4`m249x*3S+IqsB`QY}^Kx%- zJ_Az`zo6O}xpxZYt~Tt&gimD8zy&p^7e#aaGA{rkIUfRj7r3_}N2(hVF31?6o&*_;tnTHUZwOFv88M34?RNSy&iRO#S(k{I!mmQC5c zp!(!pc8(=5rGN?(&1x;{ZT=v@~?}Ux^^AU04 zkh>DVk^zuJ%329fqT^Tp{iWB-<#?mnh3|7INEeEq_t&kIXcdf6A-ZT}Q1-G}$V}+m z_67E|x`jji3%smjTDw~kb1IdL5*~dL^P4!V2qycE zNyd|OE6yiQew(EUM90Wta!*EgD}!NN?O(CW4Q{1BhB7a|m4}>1VAgisp9=W@;4xF# zMgVgP=zuY_PL3$z70-X9q277=O%A*zCO*|BQ6Z=mm%gRxk-bvvy<*egBkjeaGBmr)pwm%vID?KtoYn)%96mTAPCdMBQs38ayN|=(o-7O_0_u}2R zyotSubhZwgtA8&OMyLqdvli8t z6&tHVSpfOr>xM`?==+-=&Y$oMK4(zv|JD-%#4h4|*0H%GJJ9A=5MbK~c(f5sn-S7s zjv}@CKoVo!kuha=Qhuu@2N}+lLKyZmi5$r=7gNy|JFy7A*WR+s63QlP!UhY`shaF( z3yL5f{iZ#(x@vVu1P%)gtT>8pT0BUbJ~dke41AXBON)^|P|3{H6$-4l_ct<{e`^1O zL|iH*&RHmQaXTX_FX}TrvfqEv9i~_^7`mt-IJW%AeTAfTl`n1Yn1HlHVq;8SUsp<7 zbm--lNWp0BfD$faa-pcQM?>?I;jZf=yp|z~?&FpnMIMqRi&#Tut_P$m@PrllnUd8Y zeJ+oHsYDk}x1D$BX19}ws|oXu?WG{ zBUP_m!?KE#60sl{b+4-drj4(n!r3l}6nmNBtho$Ygbun2#b3y9$)`YjL`(WJ>?gv28nqBX zrW$>{)~?{7)fwnjL(PPI1jj*x_fBNxm36l_Q1?U!uwW$cdo#y*YAWVg2J?2^!dLo2 zrLjv_633IfPEo#{*vZMfQx( z$FQSAD&X&IF7HS43hfhsu12ofDF9M_SFJ%8&vPFGLbLi(;WNLv0Q(HYaC?6-8&Q(~ zR=|ZwWm=m|ON8Ku(m2i%>Kmty){HvKF zVS#0q%9=&9I=Ew zH24lUmO+RxeNmUAvp4=(k1~$|UB=0tL>pX8lciZoH`N-iBs*8Ag*5#(2VwO2U9^-f zJ-VHHr#r2-q;)1<>{zD{q}HC`!nbz*qo!;WSWB~|qz4o10smP<**d{BIlIW1# z@1K&;^Ly_|Lr5*7anxaUY-#?Iyj=@s3ra(J zSJ{MY5Say0}v z?z=mG)9Zdg^HTg*x6Qv4z5LlrVkdwEJ!%T;>+291rp=8Mg&3McfjBr1Ovr$AG7T$s zSGm`XN7e$Z1UU8!%|k!N;L@)qFfY6@Z9^7?^k%==!W+Ox|JP|LhojMN-MjC+N>R52ThzHEI4e6o8?qT7NQ z$$ot)#r}OMXJyLa?Ynty^M20z-I#gnWfFAds6sd1Q?CnP+sqqj3Y2$R;xhtKuzNz} zyG4R`*5T;x_%!Utdy9Q0wzuf)=7sibtKeG%Fw0r0`7N6u_3QgPr~&WXb={^Q8}voY zdvokRmw@dgjVA7|23x>aV}hTIx2>1CbAHQpTU%-xa|lE7CxZreKHC?{~jn7Nxuyqeg=qx4CeO~_I`Xjh2^Y@qc$J|5&~vn zF6&K{gbcOAE;gON+CAD37FvE-`z_3tTbt$2Cx*+xw_N)mSe7|8Id8V z%vSb(M)TLv^aid(sNf(oKdh&*6WIy3*HU~$k36aWY^b}7*l4yDOpif#mYvm0vi||= zpAdQK+>?|-2e`@)jr%I~Vr;eJeXq?xp0(Vi9W4{Hi*ju@$_Y&?aA#r81Gfd4#DLbS-8^ z{h~&Y{|b2PpyZ4KsvHx`>k0G2?|aamwlOo%m63r0xK#`c(i1;x@q?A1sa~B^wNP{o zL^BjPdMfytAHLwL*Fx-weTcp!a2BK;jPL(kioZ7creaqgsu%CfNeVR{hjhaTl~n5J z6RsVw2Q7Ex_veM?8ML>3%MlqC^WJWA-fuCWa>lh=|BJab1*k!wWi4*(p=TdmHU?xF z(D=|rxi>+%puG=w&(KEZX?Fqc7YNOB<-WHKB*FWkUN18D*MBXpOi=XCJu7B6KssCl zK4A!G+2$X#9D@#FV2!T>Xj#)Yu^5Cv$j5R$iWxJpZaE;0W!>$qR13UoP9y zLgGQa#Ize<{Fos@s>iGbnku_m|0!R8M=)xgz`1BKA9T)FpgnQ^IZYGMjJ2whd%W~X zpiduKIx6+JjrC*q2{&G+P2~%3ypAg!`M7`Nx)82qsARQ3t1QJ+Gqfh}oz~6Pm z>=uEJ*p0pGe=YIKuy&|y^gHCy=)(Xi-XMP*P8>~CCU>c7H9HCdM^;N!8p+p59p^tE z#JtVxlq>dfAA{^qED+zG-{)qHTOI*%H%zq-cf+(6=P1FoC^i4AW-bmPdh62^&AUqd&p+V8+T-=mr zCV|QbTLq9rrr}5QcRA9EIPc)?-bOt%^?vpWOoPbTVV+vi7%dmUrVLkHp)pKY^fX+* zTemUe<(5}l+-;|FeTWfgMD_;2Z z*6A`!nT+h=Ny1WEDBg@<2fZ78i)#XxrQ&h7l9%poRWD0J5=gq&bIyHZ?CA-s0^=$2 z5Oa@*ZBAb6XGV20&zo7eL_lp^w64G^7w?NwP2$3cIung6FQOuGeP)2eE7HM1a+i|ah7(QiKI zd>KLodY%RijoRHIiRku!zj-8thtac(J|Is<-{rR07Ba(woa6=6FD-ZVV1m!?I&zMV znQ7L!jb7isSy6gnsa~++t{o>Lb;;Ji%e(>331Zd8^kK%m{RL2fx^;E_hFvxKej(~Z zU}z<}Aal_roFbXu!W}t;=yON)$$SGr90q|8I(>qM0Gw|+9|TIbVe55Tw@hUK!5}0J zA{fNn0Ap7|3NdrGQu@d>R~P&JJ9Y0-7dRsh`XE4>7_sMXD$nFB%Q?Rs(#OwHC$hJ#G{2LZe}^*~-JqwHWpiSd-dtgrvkFF) zk^NfWqxS1qR1racc4tp79dCELy6ed)FE6iY`7bUPC~v#S+8-kw7;zYD@dVjSB_TJT z>3-|wYmfoAEK9(mqr>W-ESbC(zVB+I-k_K83N)U7YJ+zx+9t7u#6%lZ@EG8Rb|fZ7 zEN1uQaSoUojx^578M}k#MPLw2f$J47(zRBxE^WPEGy9FaS%!nzqQ@sM)zQi-`|4x3 zF=$vJQFx(w!o8>Ua^8B9V2ijQc!K%wfBKuk(J%V6mzP$tj57q6NDg`tIE!|oN)za4 z6Y>*ZDv*{c&p(%Psc-7nGe5(W^NDAr1s2|@>EA+pangze3u(960aA3(?`fRS9govj z6ljGyU=CfgdiY_gv_Gq%T%^T)4;4(lMXI(*HWJ3}1gR1v>!ESMe7;OBY-1J|)(-=F zz0D`uKTZk^NY$WgVSr7d99iESNl4``H)2SkxWXN7`%U<`g0kVuLHCbRFx+7Si~A9~ z^VcNo7T%Y2$b(90^LokG+(7wi_BIRcdzclV$u);42Xt9!IQyP_17a0|sB;e)7kXg4 zR}fsu7W^(&-1;i`1vLe=;u9xrH*=C~ziJ+nwr6tRzopN#wk4yoNA2lFL~;Gzhc6C* zL}8<1y&dN_y{zyYD)(edW-V3df;;(e-^!|>k>J0T90ZL|z;a%A7Ap|B`iV7<3t>Q~ zF&aPd^}RX6MWN&gOW^`3wVh!qGnZsp1#K{5;L&#^tdtRHx*V{8k}&97+5W6zx2CPE zzr~qYz9{(GMiwOReXW=ecLFG*fbew`qL4?{`O!nr;GWJ}iBj#bxP*mLs>EX%vA8{`Q(EDD0A zGXZdyQnK8{yWhSklbIv0yQRNF&L344^gh92Gc9Af{LxDDI&e|VQ(8KhNVxrG=3BWk zhJu)!1>dDNa^B8gW~D3255T{iEW6qRCD3LE`q42qq*c>%-lU$uitA`7`~6sjq1{!k zWNk+T{fOY!>AdpWydkJ!copVy(q04V9u2)Q^6D03N~aX$tKW0R-gX{ zHJG*SGL$**&MGP)7|C7Wg7Y~i_K#Q!Z|@5iS>`IOR&7o1AFJ8JeG&%)jE5PzJ@$~A zqWuqF7iN+Cy)P7AWyPQ%>c6Ar)u!tJzMu>k zo7h)8W~)71jZE~b1sxx?b#(!d{MR||6nYoaXAo8z#%Q|>z{FcM1-pHAXtrHZ)>^l& z?3MfUIud6bj6QCeI@ik>ZdJHO%0wvo$vnKV9wskjgKbUwsjp4upv9`kdH}Uerb653 zRy3>^^PI?SvK~ct*NPL=5enRV7lsUIw@M=ELYvb zeeqhe=9I;?D+s|mdm3%t&#;(I8tFB^8(cce^?I0|>)D$Mb4WR>UuISvM8W;Szv$~% zCbEEuSD@BuH=g)J1pei@*Mi%gIcLucTcZl*9h2qN9`dFSqM7TLqA@Q6{b0$sfk^=pCcjv$RdO`|jT>zN+$p3d!3~be-Qt zMW!VqENfzI5?Jkb_kQU-NKd;cAn-!D6pjNQZUnPgq{X)QXl*T^rhSkAz76}ja3i?A z-myRnS!{8Ew9^VmjKqhmMBkIw4ce5^SJtVM-_WC_00@QC{#&5xrbBG9ke#^LmkOl9 zXgc?r-vb>IiU34_o^sci+Z{|hX>zag$q>&V5)9S@jE{s1D%9>wvmb#ic7l4sB!HY^ zp5gVVF({Ua&X;jO%b6IEyHFL_-O|nI3Ja~>)@^bgHZ1ODeGQE~G*C;)UR+lyIoWci zf`EnfvOsv5JXBhFqnW04X`RsH}s*C7XD?N}x_M4)6#2(XIMqhD-U5fX*T`q_Q z$3IA{Wg*Q-PKn8G(F+-C7xMP1gXEi$Uf`qrOZyaLhq(P`K)Sf~oG(Z6bGhUz6Dd* zg>g^?BydKQsH;crpFM){zz<9xVO9^2M)w_Hu-QA9y5@y`938x{^>(p2>Kqjid$1o@ zT&;x#T}{Xxq+ry1t_dO4?@^W!&LmiJ^W!=O*3A^x*m8xqQ-jc^vU^rpFuHYH17$RCFj-`<>fsC6~3tL9C`#|{G zfQ8Q#HH1X~ac{tgP4oyD)sXw-F7F=Hta$A$(o+<(C%M)~JLE6VuDo6zQn5Ah4vQBc zxmFU#>0njS?DTGZ6ELXeI*u7$o{!sEp=>laQu&xOzFUmWL>8P4wlmYhmMrp^(!pe= zG>sb(6pp+$a--}|8#y&200W>koj)2#>#Zd@Bnw#<&S_K@`H`(n0>)E)*nx1;e=Xh* z2q2(s<`YZq&#A%y(u_WwX8T=upzdHA@nLmrxDEm&201{)VN6rL(_v5s%hv|A2Psh3 zQ_UKrt+QK^&Eehbl|Z>czzvVnJX<}L= zAF_?fuWHF!0L2J>^ktGs+AwKGN(+TaSjfxp?k&{_XL)pdV!(9t8wkoa&S6r*yZTSh zHv_h3)EO5k*&qVJ<-YZ{+ikY=o5JSqUO`BB_)dpo2a28H*_#-ow@F4{k&$*-!({Dq zK`;5y#HKgDYF0Qh`M;?V_hMOcxj-Gf7-z1?xn(!H}xc5@197mSR{pI{555_pa;wGli zm3Z|6u!vxS$@YA%0$`%Yc0eyAABo{{20}U?;myalfC`^)jarX;EGYa>tt||LDZ^9T z&8Zcfe50IiDanC!uxsw6o-wtJBH-UgKH4PZ9d!gyf>)Z4WDkL9Pc~8OwE-~J6ew+8 z=1c|rhnX*}PGPXA9YbgTnUxBm-h><=GGNo5KGVCXOO*Ld06h)(BPY7*c0oYSAk=1N zn-0VQc8gwdnA$3T*~7~X=4G>n$%^1j)mc-{=1X+~ky~Fkv%>$btTEH#s#Z`s86WPv zC51xNxl0oXA>_(XxaW5ofxprmFw3YA%BpkUJW#)qRF?x(Ad2*TP+qg9OHV2 zZw#5cOkDWBt_2fR5Hip|NLkF5Hdd5(yXrhpvErKI6-dKf=D&>Eg9;e*r}i``lclch zM$w~|_eeJuz3mt7m{*|78oA4F-8BMYN^g2Mdq7o#jpCxQ9>x{oKC$&6xH5A4La79^ zn@QH_Yqtaa-5W7IpY8b$&9a-0b)-MI5LG!5Fjc3D#zX3J1yp}pU%LU@=+so@f-Qbfy zvF@h^!LE&&@@Ztm@a7Yw!Il%-)(%iK;CI>I-F#gL#!ogYE^iNByuEA(iE-KS-Wx#T zddHroL$0+|SPuEzU*>v=+F@F;(A(x@quqBJdq39qc?&)RiGQI7fZRC^bWdFL0*i5bXyuf}`%~jL}HbXXHzk1$G|@9ppkw{32tAkQljprewCf?^2wAfe z7o|WC%I%k5Ok8YM6GaaR!BK)#BdB$Xt~^G1n7*4^Llyh4*5y3JC%9=FnBg9)nrLy$1T-47)PTwxUlc%|%#NhUA|S!QF7d;jwK zd~?UlG*&5S^Qo)U2_T#A9siKKpP46%8YBu=x6)q;&pp(CXlf^4>UcqpHpmaKAOQ(3 zPfwgQF`(lJshsGQ^4T~)`_9h;s$P)PpFlg4R4QCB{*`k#7=^t&7O!YHu?L74fM|^C z7#SU_`BZ^Epn}^b)%%$iJtt?feZaK=M-6Xz!x9kOe%MUQbLT4gVt`^MTwYvoF_IbpS>urzc0#5&K!X|FK;@dL|lJDll8`M^~c< z+zd3DM2Ofyg%qHpaV)H%BNFzsDd|DI?5W!?7rF(?`9}pFa1=5Zm?=J``=T$m-VTN| zy=}4m_Ha)Tx2TIh*Yv~qE8Lg1Lm{!XCGa&Q3uqq*9R8@mWTWoZ-0VnB*U3f*_(~28>WL|$b7LRTj|dOb+20? zaH{DQSQ^0WR^j}XW6|co;|Ps>3#%h_xx7yHTF~Lv&?qCeH=P|2rf}cORg0+Yegj*a z?psk0Ll%&Cr-TdydVdVshrl#csNm*;Lb1S>E2U8rf7@am%&crdfL;pI?#s3Ws2vkY3Zqe%hMUOA)pFC~3` zufKyF0^mm9746YgJ>U*>W(sN-C(?mT4+II_&xBy6WSjR>x(Z(lq5AmiS7EJY#BzE< zF;HDTu`cl=D9ejlg@iY*#NFC+0?3iZY4(>c&5Pxdy!u~YUA1qG*Y9*$tAxNJfjm%^ zE-Aa~+`Ef{FqROA;igk}v?)uw%FUmCnGdkoc#WRfWZ6_rzMb&Ao1fLN`DoWcyTSF| zSB*uD8v};nw(b6^S+GpWOoxzg8OZbX&E8-kUHishU$(P7^;3oax3<>aqOspJ><=fA zp(X4q&wz4ppAYXEk@2E);)6-hf4D|uK?cpNj~eOTOe|iMTq23&O`i-Hh^+H!T?q2O znm{3N$F`#=x0Vh|2oxb0krQCl_W9XYy~%O`dGr((UHyg<34oeD^kt1F#92K=zOnP8 zq?JpvzSnU3-tY+)(r$LD0NEL>C@xO3K`hWD=V1!?K zisC|^5CXPp9L;LSPtMHD_x51Co?~J^xA#P;5G24hqT8ptKw+{)602{{Ksm0L@<#f% z=M;b{>>p?!dcOr~${oj{C9VvsHAQGbz5(vw<>JZw^I13H-@ zOo{H^p%((=BHp&VF4@_THyuulUZf}EmAlNrHmd2Y<~1@xGEb@?x%mnY` z*aPwBPueU{)he&6Z~$cGaJ@wh?b zYYjwN|Ci&5K?}{LQ@NkiGjpH)I(_e_X8^+=)e(iAc8hce%^Y2Ji*>6i*C2Fy>hE+J z4@ce|>YhU3*qzls2`fVa;y=a-p#|xXcLi@hL-XMONr{f2of#Vb1x9M1rxXHdmx_SV z@0~V}4&{NYcF{n7(!sPrv!2O+nc4o;#MktYf(q0UNDdiB0fhjX@EPhqRFG@~!Yb}j zN8KMi_a`=7X8FeMvl)y*VKbOeHV?#5N?^C3VrFG3H1ih7Wm;Nsqes&Ig$Te8y&cUu zH&&)hCF*}y0q{hj6(^ymi4k$2X#PM)r|NL#68C=|;)edo4-LOSzR*{a9sdkuDfE6&y3UIsuQ=huJD_W04%gq{ZYS3*^0*>3LZ z-0tzX`}+gP{W-2oq};AskZk12ghNQQp{#Pyg@GB|U~Ccml-QA^fm%|&fn2GZzK;@W ztxo&gx}K+LF-%5Dh{p|AS{zKX)4=oTq_YbjQM5mLTCqef{ndqJlmR>=$}oxpmH6a* z`*DRkyHs@vfzr+~UDdCVXETtytn8J|H6Eoeo(Tz;YtJ_0Z-?P7yJFzPPSixXP`VMz z3!ykhQOgcZC4P!SPwNmVGbw_&kM6)yogTn471t|wG*V_BBZ}^|9D~~=gk$#3N^|0- zw}qNe^cD%iEuwnX7JG99{Y)tG@50DdZ;OjhJq8NYJmTV}<9^z^)Xk%Q;y!`a5@DEDS znE3ekX1Jhb-P*_GIG@{d@F~f((5F_`3GG>7RMH8qJ`X2nEID{5zM1*rw=2}tYY-!h zjG7u3wUH!LLsb6t#917iqZn@cTkaLKl1=8Jvm)Z+;zM=hxF;5Lc|8jBlqv+e= z2clY+ynRdt7cJ@3yw|K9?R0fEB40n{o&}TRKclwBa4W`EeAMIhV}O9Jq^p<=UB>{$ z=T~F}DbLZLbJ_UMk5n;-iGN(obm1zSl_BQzLM{ICG0|hzyE9}w*2lF`3o6qr7~??K z>slZg54Cq+pDM8$lAerQw(@Od)kvZ{1$Z>#Oh83u8n5S+8Bh(c7xBZZWs>dkWrYJb zgrQO4MjoC>z>S=o%5vY#iJ1TcH&s@%%`>$ta4-s|Uxy+S?#J-;*6md8Tzd#h$4$?o z2LHI!?J2sx(p#4cFvMN+)FO)N7MPS7V7s&AX zX3MVcw78wvavhg++*paJ&APhl>aa2z0J@0!!WXq&l(hq*+zG))Pl12s2!g+;QmSg- zOb{mSY~E-Tnb!En4@B2Rdp$nTtP40oFN=^Nq(XyC&o5C0V)-C%pQpxOz#BjY|ryk$s45SHZfQxuiXt zOwGXWbu*qYKiky1*OAzU8 zapB%(irUjmI$&qs%LfyCy?ye7?_b7^{?uV}H@Ch7+Iq=}0HSK5O2tR-dFr?bb`-yy z>=eE!x4Al;2YcKWH@ChpQVLp5akwXW1^9!)$jIo0nD~&S`h^ft;kW7r7?r}tshHrG zP^W6j%F6yYW&G{^{a3;7jGn*Do5$jBRZ4`ndg4Xo>k#M{8!;H0o4@{kj>W|@)dFRC zC71GVveI|5P$PpoZG7@W?bVAF9=Zf>n)y*DC4JBiJP%GXz8Qx7?m2vXC%~G&ivxWD zAHN4mw0XoipB!AEDws4bhQ4HRP760TEu6an?Y5dk*6kllOV45?r=^8B@FdS1sq9Hp z$p&MKc^2OdM)&=Ly~5CP+t!CvP7as;&ao^?I71S6ysFwTn1HS38}NDg-kiy}zE0QL zj*+TTURe8k!-rnAbQ@Uj7?@BzkYNZT?BRbDzjNaYum$zUfSR2(^nAe5%#1kAMuDg4 zN$aWFhhG6SAgVy0#q@`#CN5gDe`!;H-w@dF^=((xiF1U>aRTWgush8PP89u>JMUin zVuyeGnV;AFnJYDj25l`5t|y=PfAHKN)G;3{5%_-x;pYP@$-%)bUT}?(WOq zEn)q!fPjD>cMsIaer`k>y@zWH+3vgD7YZt#y=AI#!w09GkH6E2FQ_WI46BN$*B;TjWW z4?jksGvXQ;^GS8MjObwEZ;DJm6m4DmB@kBCzwYDs|8g0@T;BGco-2Rec);SP zTb{;J?4am!AGw ziUH831HA`;0cRZqW6WPXC8YA#1VMmqZ(pw-Y~9WgbO4U|F+!;tc)7IfM-Rc_MzV+QUhRZ4hTVmYKx1G*2c z)^e%C?zp1te|mchMOXw1^2MsZh8AZKK#eXoHdd#r*H8yG&7qSe{F|Hjx2N^zPJHxv z$5k+9l6o-UzcVO@yY;)%J~(02sZxIkNDmAOP;B|>Xg{Ex`@v$4?(UZUSj^wf=z-@( zuMB5aN{ZCaGfF~A`eQMHI_NKs8U60u8l&P&EFPo#T3RPDfc9QP37O(L#`r={T0hX` zBeTC_)&~Cv(}S_?O5s+a{d+)!4NuF;^3H@q=X&$QNrVWi{It`9WALY<6#wBs+hwkR zIbDV8t$ZpiIqGdY*MS*7Jfy*xqmGljS$9#waE`fJ02XvBRGDF!S| z`k?cP{{v5n&W}vL01?7d9J1H9;poj*|`as((NCz10ro>*S}Xl!t9K!D8uK6@Y*~_ByRa>XK6woo6MGM^z;Z z;qKWiZ9R|nscW*F^56t$IZm9|X7q-3WpDj|w<-UJ(He-rvdYUTiZh$Q_8j_5(v6Ax zdzkLcs2KBRr6d(vo2$7XI{)X5b`U2~^w!}+k?-M4-Tz+)WWcJ&w0RTk`5j3$!Zp1^ z$=`g;1I)m%wXAdfF<~+SdI)35Zi}`OHHCjoO=!Lw)}|c_|Yb#cXV5l z^}?d$oGr=ZS~;oJr4y*oDgr(jlp^~Wxq#mCAI@4ap2>Cn`i?uh*VoEZ4?pK5kn8Fg zJ(jaIA;}d!mxJ+j$@`sgc=mnPf$dYS4pG(DJyw05wchGC(oLv4a1tGf)jy-ZSWd3M z9K)RscZ@kVjwA1*9qgOsFeI^#QWM>5vOTxu%H}ODBJ6YVGM<>J9)k7Lc9*c|DId|J zd(VjjgBmWP*CDs(Fh)2qj>5XS@nmZ1y2g*QCgZPXlm-pyYF&Qy`fwg3#no$>YUcg7h$vQt(I zkr@mHrj)yzm@)!^%BF+Q%JCn3phh?HOXvx)$e=!l zdQ(-r(SGgZt#Abqf5}2s(hmEC;;U0+{wF9^G%uX1oMs+VXzb$kTj9i+`4ZgV6H1hC z$KOVMk>!hEOjcq2dxDj-wxV4eyF16C=6JBDyw&h;74IJ5C1lESxbCkR<4SR3Bf*oU5j%c$!!W@ia2@{}PIIgf=g zw@s3X8rR{7FbfUTA!D}_ZlsGFwBEbE$G@TejT58a22b+q*;dVubjLe%Mx|J|cQ|F1 zP_LW}Mc7ZuZyXz-*S-8<?KTXoA;D5`F9?uHE^QTkTqLrE*l zCobf;qLObuxlV3hPu#3stv>Q`?fG?rLR(p}K#&MKFYY4b1NIu3!0boqB+X=N)0Cof^l z&IGj)^GS*w)jX}^Mt4Tu?$z{Bs^DzytDbldCHQ-(rm4t!2R~7rfM0PHNUtVIqf|>N zK|ESjg>2TbiI0JLk?t47vKmEB?7_auS6jNCl`veRAsB?7eQHZfsO;wNP)S7f^=SF& zi6F9I5-|woBHtPki!B+c~fWlEeU1Wfa`07I?+dXu&GigoJ}J;H%N8 z`kAJoV;*IwN*V)~f#U}EMljUyU~HbdcNjQipRPophy_4qGQs2b8<<7_ zH;Sk7-!N{^Rjz)JCl9PIx9I^&bBGpnyzYHsB==zL?XVe^ZPIaZCg0bixvN>Q*~i_! zUHrtws~ew0>FYA0;x5TeHE%BRl+Ay!+A!SG-Onk$pLW-wxS(6KDO>6kl?G8^Zo#C{ ze!P6v9SAz==&kwbf_|hpe+58X$Gq3BeQ_#T9Xv6T?%_r2B+%kc3l>0$a`^lrZ;0sW!~ zr|t5zaPMmiPeuPFt~ai8P%Al%M?rEX>SIT~>H=mUF9HFIf55>2R)j_bD~R9uLK#=p zXMT*gZ{G^5sJuB$;<_urzG^(I*?cBUZEM=n{C61)(AD}4d;WhLlK5X-(?3Kkz;S;E zhz~*@^<+WjF$bmfJ2zo>jQ%lC?wx&r%E(dsQ?PD1WL5y`O6Uz4?odbWP!C~acn>ut zceRSFPXLXvSN|Mixe8J$e`7v*00V;%>(868sBES6dv$_n{hg)8zgqMkb-kayj?u`z z@O*}t6ORnEJ%Bxs9a% z4^S*M%*j)xB)|g^-g>A!s#)du{(OTmPcaV|c=DInR&W3Hmyap*CfV^sFP-;~CG`D(Tf%rZU(7W1PXCX^p){4Ohd`3xpNd1KcMhrrn3hNI z$MBvg@SI&fVkvlZOyq&0{Dg|iqm`r7<$~jpf!#+|;7`4sFkZCHPHbhB`oDzmejRML z@;@xxqu3qkm69tq4H$!TU)p0vw40xPvif(M%<}I*>)zf3dOdvcI*N4{cRcd?hYK%X z&9ZohIABxVP!j&?h(V{)JVJH|uP8wygigk^6?{?F)(+a)+4)#lnlb|b&Bf8Ol0$T0 zHZ4vNLw|L%P40-{4w=)nI;EC%rMofG;C{AKlUH;pk#Ak4L0*IJ(Wm7i3zca+@!-5) zwi-hjAo|Qd;7TC5pIzU+&T?r8;>K6>;6C(kM>y8j)6%U~7Sw41hC7`~42V7CbW!KFts zNZ~^ZZ;CE#<<_dvFQe>FE+>P#6lUhe%-`2sj^pG#Q4Li7KouZp zcX=Y(mlT3)Gcx|;>8q!)s6cULYPt+fkGa28fUtvl-M=UyfcKRlxB#8yIhd`+y9a}3 zm|8FNvJgHaWHk^fAPq7)71VGSa=Ce6Fe-#P8-Yd!fc|^3*c*o{``_1AM`DccAmHsB z8HEFsbnd8}7}VJn=e`o(AUhinUtv@-7Nid|x9kjO3-)66s&uGzJM5#jCf!k4`%3=! z8A;GzIH4J`ePgGQeWVCv_JF(leeI%vbc?O!kwD!_jbXF>GLcDAY!6t$NexFTw)v>|8q|+`ZUQSQzV+d<%Mu7~M6Bi>>qv#2^|3(P@`2(thHpyHc0A4if$WEsw zzv<^0`lx#a^RSetgVi&;>BhUeacUWrfFqeM{`t0h$e1;G#UwuVJo0#FS^l`~xhTxX zGEBaN{xs=`2i3y_B#hI6ptu~#RlZqg=o?iBvVgnCy&n?KOFx()Yww~E8?6_QtHjX{ z-BRdT*W$!g+I18DhrAOI1#v)u`PWj{HHXs&HQA#UO`UcC6ni ztRumEp0l{Mcw__;r12uvIJB$`l! zPX+iP2Cl^YqiR^1uDxjo0z-#y2M?=5MbBQdr+*U zj%$hI4hXBK&kgV5G*V-byJ-MU0tZm+esEl%*A}Oq7&#bMU!7CqXu5FKpxMb~y@>w_ zc|rq?TA=<5;b#749d2q`GJ)U{>p6ac%0>aDs7;}X!6A#1JwWrwC4ts_Rdt8P$hFv4&y6yzkudl0Hij0_vcG+pxi!}X~%w%cR012R} zzb=rIqHFb@mgS*1LIO{M9L55Ad)JCy7#Pfa4Oo3+`amSk$}^ARRhz`3WE{(LqX@FC zc2(j*8l-Q0PcoZq*E@JI8B@;P3d+Ok90Vi}A?Z7u?>Y%739JCeTLaGO-=q9w-#G-o z9v`UY4<5z#jC%bvZBst?nCor1k#5oWR<9?Xs*`fsZy9=Xy3iCMAEVDBR(0fPHh?m* za%`uK|Ft!Y@qcubI>(^$zdlX(vlxo1vWo5_r!Ytw_h9oDi6#cs%o5Y;o-Jkg*x4SO zD^no)QPyznY>twP%Mm!K+YpP#Ms1c(D@6Eg^A_RWbN)5W)4JkW0)4&%lDmOkv55PV zfo5i5b^fw%T^`vT*ceK-7gob(%6g2#mF@S629n2cuTG&!3Q!UO@FSzLMFV<9#QU#c^6L#=WEFSHJN50fkQtOOo~n|95o@SIvW{f&hk;Jd_Fh8ll}Pr*9Ty)fHenaaAs_ zDXR6^y`)^$X)${f!Z}%8TI(|zGpl^$G(Kp2-zS-5eLauU@wqg*RK#t5-JjyJw1S{& zae4}&vKC%u0?k$L#=M)X#`)~$dAxe-3yVH$M;`q&nBMV;v5vN-|7>8Q8a{*r1mkZI{d;xVd zB)JdkGK+3SZm7Cf;=Y4S^F#T!E{P#?-5j2E&65K|9+b#*s#@{f6ae<~B)y)fH6t$o z{jb}vE`vy6og~2IJXFp$x~XroYeMo#H|N$#XOE6t)*3PO3t?Z3hRxq&oz`oq%fmVH z{7D#{soz6u4xhI?pNQzi9vSgQVa9h z_>-pE#&-*)tz1afKq{b4kJ=g&M-M=);A63n2?4FA2Ldhjro1RBPq6Yp<{~%$m*ULGd$3DW zo_dfLAck&?5q7A3rMZiT_i0$-De=OoM^9Rz>TVo@`zH-_jusH{-tb~7_XFM{RHB9R z+G;ZUHASF-X7f}h(Nm@0zWC|0Iw_cCh4X89cI2}}i6;@(79vpFr$7y^mRM8waymli zmeo{ds3GhQ^|ZrD-dJN_8Y7o3VgY`y*z;hH{UrYUjo0&+kCvaJ#{K47$CxifnBRlZ zeC60z0n1p9mkxy-orN}zgp>Gi95U#TQUzp9+dCgiEHB^<~+>WNo>7K0|}6|aj5m?^;~D(wI41*OT&>nyt6 zBb-m_DqM?Raoggbrn+?I^n+x~24f6j$@jXas%&a8CBh!#Z}jtqu^u5~JmsTtK_lD? zENp5`$yw7kiSXhVPhbBQhHI7ilJnWu|6Lef4rY_i6*H||%eni52L^)kX!q&EpdH|K zX^w*0yw8OtA(p1Lrq)Te7*Uk&{!uTLlaKWp9=RaNkk5TKdr5K02t< zf#2s71E$sTK|zbj#P;dl{R-UuZSC+e=(FnBZ?J`a9*@4~a#+B+y;Q_XiA;HZJ#Hl< zhuj9QWiu;dhgJ|51=qY`oK?4}v30EQs|Rk!%!PwM@a4l;=j%$PRtpLe~} zP^*fNpFR6BpCh3or(bCi!?#i2k%G*by=8CfT4iZBr2Tpp0B(n>=K%@kqR4$C&8 zJ{nAP-i^umDzX%971mm_r_pajKa-*tcy6BJwapCtJyh+XcHW2?r599=xQ9NF3izK{8n|#S?ilJ}x3i{%SOV{oHs4^#LbiYk zm1q4~;avA+WKHSAsLd5nmU5Yl8{b@i5tz1P1H6dRuB#G=9OZvmkvQKSMd(ZDLYU~O zgmqt|jO0iFRpj(J#hr8mX=?+RB7%43kjN+hlR$d=_H96+lS1pbzxLCI2tr<<%McWwV&qrx*mVCnzcQr6mi3AwuQ&3EjrjCwIL~*JNom+ z`VG#Bx{jxe!WP7+ES!nEHo5DImPs~`xgpaPQF$W4ln8zPeT-~O{ix4x>AshL+SZ44 zc7w>lE{W|FP97*A8>!*4giCN^EM62}*1*fQ#|}@7owYp`rg0(kwVq8Q5oMAwNpAV| zFZn#~O?(jq8;QXeKMcy!d{gv}jJKUY}Xx5}f!c9`8?-bo5mDSb9!MI&?*3dJxy36pL z6ry-Hrg1yn`F9```1e`BAA?HA7=T!VD5X|oxHcQJu)PRTy|>;VLjMK`&j_>f%;Hk+PP|XWoDSHCL@MjMj+`S(_mCKylJ@WdL}pIBa`x#; zX2{G@p?nR~32LOz*m`g$e`p~7$6D457%r@&P?w;^C;E1!JFh|D8T&vydt%@nJn@+* z>Ka3$v&yh-E?Za^c5S}3;@1m!`@NmG$ZE4aV==Mn8gODsfg(5!328#91-owNgKl*8j^>|D0C$zy=iHpGq zl$i&$I&rL8d~+eM_!I2h#9~3ZRk2t;WX;q%%Q4Rl@!7Y6B-QY-NNAjDj83(=uTgBZ zs&kupTjnwZ`8i?CNjN9mZ9Y#^8FEy#`BF!yF+PQ-KRvc4xiI;QDywVy9C zpj4GfN09r$9Jf446hIVgH}AK(K%UJ6&Jum5HWtp?Syt07tNxvPo;m7^%6voZcw_Ur3D13iHabpAcE4;wb4VmYxE>^!hjKj!S;I%yzctmkNfxW zFEIv-_xp98=kYv_GtRikpf{T;f;Z6DhKe+-kfR5c^{I<%=Cr{84tZ!guZ5B>W54%- zIBes(dPdl9_1s45X!a2lWAcV~JEdfwL#hk$pgki0%Z<56U+Cm@0HE z!N8-|1V?zu$2EAvL#d5o_UKTjm|&ZXNc4_gn>&8*SUMqhDDq$n&jpV#$)G=nuH9dt zpHIBZ)!dXgv7SH==Ys70M*Hk#v5Kt(y^DKUG@DNUV{NQy5JwX{O8CLKKk&oqia+7N zXS5b(CPME+o-w^8bxa5znNB3|)Da=)KYgW}>uQ3l%{5~XP(t$z>ZjQw{O;HEpder^ zf|Br6pzs>ZU|rnz19M@4V4gVMyOiK)9WeHzhzs->qhGhy_pJgPv}ORz#|{mzu0j&% zsmT5GmDcgJLXX{ke|+V%qv#O}Pl69^g_-j0PZo4`+;70> z8&>4>1A}-%Ja`=i!LUWIIf(92uGeCG|JM0tcgpqh=mp$?lp~6j-z2Co~ewL8?oCR z?ss&gh}Qpd5=##sJvsnBj6U8=0=VPIKU-jCE7?A1pYkb0cR1onglx9TxZ^&4HskQP zbwThA6g*AT;GK<@;zZNq$UXSv%u`&vWnTJutvodT?MU}MROAQsnS`aSM;_ZL%w$R0 zqV2K?Bo?jO5m~LBG~q`{y+d|`8Y;7Rj1xMRGe!biC-g~k*lH62yfnL;?2x_YpqmQ`zB1#|IOlg3cFhg|rG{x~~4`Ni6y9A-XLrwE)k zp!eUV79<;kdhmqg?^ccQaBJnd@*tU7f7pT(ov)HVr6ERKPZ*r>Eo0gSn@I?#`y485 zsT5WJY^VKK@d++PvQ9sMJcRRKX;blDmjVJ^+ArE>^2PnO6CSBQLwlE$=;P+woNZ6d zU5S}6jCjrx@R*_7)z?HL;?;K*E#GZaO6A|@gz*8G$AFnkXCcvf|Mg~(CxF^-!EVM4?yjIil?Az1k-pC7tKrmS}uV7 zt6{*Ap+{2L|1KU6=UkwH{*wAXTWRiYq=h5%0Tm1kgIR6Xlw<;>@iY&yc2C*Y+hX}58Czb&CR}LCh~$~^JQvZ`4@-M8$q+`oJetDIEB;^f8t^T0ueDh^ zvDn`*%K0dpaH}uIAvvJnR90zK&VXgRvd7@R%$!MD5o5p1vZ|p@(#+@pQ|qVV&mP6S zNC&x*NF=g&J{63H)Ia~MF?IXwl5pp_mdgJh9SB~0Fq`P}G?{+%BnzMsN1!fQ`x15y z&rv#%phfC}|E(RnF zB4?lhzpVf{F5nD)hTNheQXg)g4jebDQ?;Fc@M(oZaQa_m%!1`u$!Xw>JNzY%DIYZ@ zc_PG0nh?-y@`zmCe-VEoKsSoReZS(weSc6_o~YC72jup3JbCz$CiJKqKdd_Zx0{D- zZQ!2J+_WDifh6)jdV|0grT($2ficLnrP5zO_YF4K4}qT#uTq@IP`1IVTr_rO*0pny z%jBu43^p#SmUFp&Y9&4(No47(j$Lo&-rnJ?r#So$S`&s zEjQGF5kd=;5Jn7c^g7F_^o;~(A=PTND#^G7ymdeWVmeD$GWb-?vgH(n{<%zQ6UA0eUuWX#m2FYZ1SV=bV` zc%OA^P$oY0c(8&0LUSzxr@-e6U?!Mfc6(ocY$PsT6bN$ZgAGGANJjOZ_nw}a+Jhgc zUMGp8Q(H;%n0RVl@ZPDiF(S1bog(=iffC{g)+4gX2ama+6V!XSnOn zj@>y|>%nhX<&zQ2Agb;W`yuCmcfN-1z!*@@*AvzJQ@7-#&cb@+k#W&aiBjr$q@2=rH(u}8c_yMUEl40l+uG)FT5<%Z)I z=->6fqP%~t_y3^NIaVU+|K0NcUEtbVp&~`u&lTtfh z^nO`&o`cz!&`MsbC@Kfm@R5%XE8RS6;sx1Tzj&42z_(kCpY)`fF$(Pb@moiij!`SN z7T7YJw3}T~6) ziIIr8%6iT7`!&)lYtIh`*4xt0TyEb}dvN%`{<}BBb_2ES1nP?tFh-qnJd}TiWh6fX z;4u%!WX7;AgZY;gwaI8)0DdK?socSZjR;%W2eWD0tRq9k#$6YdkANjl0a06*C|Xs9 zox7ysW|kw(5>;mEk|mmUQ`_q4V@g8u(@9s);V7O)=Nmb%9l_>7VZ17j;F zSg364?&_LjLkXeAl#E;j%SwdH6n1EdeYuJgshGIBEIU?U9R=6)fA9z2ri*lC&HlxJ z_@9F(qji7SAw2vSIhhPYMu&wfeK3HK!G?jN8o*^vK5}T&M*P7@wO!l#Mkl zAcEZU=w<^lMTlxIc>p5xO5Ib29Ez1I{-$ajK>$R~j}Q93UB2nR^qRaZXe5`?57laA zv>wNgZQsps9eVT?%iS{tqPGL~Sz#j-L&FF3%Kl0;6B)gYrETv1*CIa--txyFE%xt) zkJ6un&;tajwU?PB@_;#+(fYpV7fwB(FAq`wxxs-X05XLPm#JQ0VEzYjw)F3IW96q@$b?^A4eZq*jZr6vNcE#*oa#re{XRfxKQ!$T-g=|4zu`ZQGZ zN<5SjV`x(M;(GE_tIV9~yP|;QS=D5SLTQuz5E4}CkC!t3+N#q7GkTyu{z1qRri)Gu zU~=rTW4cDlnC!m0X! zFZvV9RmkUpiq+HmySI7h$^ONk_@|T}H#e(#vw___B!4)#G0kw*$w0a4o{jI1PQ+j;xw5Qsqzo&&q4S1) z;4bWO$u)Dd%kSHO`m*yUhg_@h+{JR0_?LK*4fQjnx{lXR0g2d-m1KQMy=QFNvfM-g zDqRKJ@6-v#0Dgf80-b-%D<3P?aK;ZZ~Cp5O3O43 z8>DzSl#-eZHf=3pGy$CEnJH?nV1_?- z*UIAe-kK5(GqRY=hTQ_a<;VRzQs}|Q{KFb6`<sFDere#rXQJK!1@dt$bZig|vC5{z zUn!m{Xr(h{(MHgai?AMx@@DnmyK$M(sZ`S16+U=4*OTdk{0;=g9emqiTD?A>sQDHrm%cQ;_H-ZQo7e=jzES232K+I{~PlplTqGsmQzohPBdX| z-iAhws80hBd*%MMn4tHxe{F`-lX@jwCr~wd3EXD@%-;; z$G5AyCC83(SAc8&Htqf`ZrR6l1MdNrAa#3`^?P)b1HZn52Khuqyd)x@&!8@oH}*I-})bG&hKu#)Rq>;pl% zFxNsSMroH${!DQZNj>A2FYG}B>_7rfKi!=w1NwF}hw2+d4P*EC;`ZgOk=W36>c(-G zWaiGr=eC8+Kk0l^Wo%5Z$$n1EZ2;uWZea*m5_aP9*9tGv6(RQ^8RIP|U1s6FFL;evk$_{S;@$X z(mhQaEFP0)mpxsIfJ$Q7K2N#%MM|dU)OexU)JUdqp*v>Ry0D7rMcrapd4bCHO{uLb?07KBCFVXHX&f?7HcB%{2UU2VL(ojkfW-l=v7cw3DUR+!wtso29}qOOd#gFIYJb*d>uZH<}%!|y`Y z8M;qV(xbo^2Y>P+C9B%fIOs7$_Rr0f^)>p!xruUzitS_(TvWXi3SYE!jw_5{Q(*Wz zOEI5S8zmEC)NlQ(Ubh zG{JAcjstCR)25*7A#&WTeZF@+dfBzW!fDD2>2w$kVykkn%)#Q(tyYI`=Qlp5E7tDH-E**Dd5&4I z)K*3>#L2F{0xi1pm%O~@XU>(APwyFYIq^-{Zb(;iru@PnRGspB1GMGMB)-CGYK%VI zYA$%}wp8J^^~Gs*tNsP#WOC3coPRD6N6(lc6FU8^u6!ntuoMKlUcrf6gb;@$M!emT zx?eQ6{I{QJ7r11RzD}_xfeVu0cSZ}?U%IwrWkw5C)t(2mxRp^C_n-(a)}V&0!a}n9;{u%{QLlD_N=CIjvSP!XaM!(SnI6WTlY{x$C`>cSbJ# zT@}vZ>)G!m&4lgi9u>9{Sh6aN*nNv;F^hM_>n%qD&^K7u)D=D84unJ!G$* z!gVW#`$=KEFhTj0wjXqMma}L?BS~e${8Ts;qU&pFroz|z-re!# zS;4Wz>t{HjR0k6cF%4O!Qo z4hO)Cy^a3`(5qJ&+aQC4YkGcWL4DI*(?j`^Y_uCJFPWaq#UjiqAG zGP8*Qvjd57)wXo#*!_{|vk z=ckkd`Poc*WT}t|@Fxe7`16z45QG*EcVYj4h@Y9=NW`?l_f%voq!PNkMy|QZb@9qMTT)CT5?4(s8|yh=I=s z9qdI{Ez!6x7IC(um6|omtEn>uVHm1#+K|7b^ z#`cc|V&o}&2q;xH^cGg7AU#L|yaf`wXyCls+XBUju6&WiH&{0TL_MYCL6^AD*suRE zL{>8hfcY|@d?Gq?7fX)yTaYKRi%*Xy1n$y-{45GaK3(V_RO_sIY=89ZR z%uPgN42wE#)jtUEJjply={Gv685m<{F&9&?2%O8$*ANBQQFwy8Za~G-6NjSB8Wu!3 ztf-|Nc2Xw@c8!$S@in(-e%B_81U6`a@iFO-4tW`i{qsU3B4!C#oI^H(G+{db__u@}8J+HX5?eK|vQszu_2egI{ zliCx{79mhE074$yfO9wajm4J;rL;+HR7sHxM`XL#^GC?ZoB}CVX1o&$*+BEKEpPv9 zFzIFL?Yv!MnFs+PD(2t9pFdt(BFHSb$90enp|}4`fH1rR&Livi%EgmiX3DT8E*<5v z`lGwu#@iJl>YjFS%_AEyJ5VAazBD24t5*1s;AAVCkJrwoGDK#^;qVabG4q~fWZly@IJ6gYw~z=}4)@LJQ$fL8kAV@Q zKi1B9aYCg$3cw2j8&+m|oH;!g_?+7^*vOAJEx#KCKeKIY0s)ea9}#W$=4k!+jouRB zHp#l~M`H3Kt#0WCHJBM+>EU$QsZ2S657s%AV;(aJ>VR6|Gc_-1I_gHwD{;cGVP{w=ULv+Kr&E5V)12aS;ENpEBXiqhOEYv+V;;k zKCs!L{fmiil{O60@{iNKr_`hGV4T-;>dMl8 z@h(;wD>!AHG)w@-k95d!2JNr2p~)nI)V$AQt`r9)S#$M6;v$92r(ZZs&vC=`-ebgD zXRvp>b~qBf1^AJ8wJ!>ZDCa9y$_IMJGc7)9l#KCburlLSR)riT=4~S-hn`BK{*j+J zlJh{wh`m4-fMh=yOG~i>cQyl=7GadGH8s-RH5gIn3#le>OspZ&f5 zCeOQuOv;ayWzaLSpJ79(Ad>0{xGK+>WJ~`bGq+ymq_flelBG+>MUayVbJ;Y92v58IuL%h z9!J*Vi9#=U7gB6X6m+XM$vzezb1zi-KtaDbb<|EjfRJIU??Qu?YJ{OgxcBa{JPA?E7sUD;dK5I%!h|(f&Y-<+yX!&dQ>uN?nF*J& zgMK258X_+!`UJYFiWY7TyG-i){Uz-|2eXBBagrF`ldwnjY?W75qDaZeYa5+HC{h^L z!8USz%nCXeM8pv5n|5!;&Jl&5$K{;F?FAYI;JMWNF*H5?on1c%81_cQUerEwWzq&L zv^fsZhJUkV`e<^xnvUF}E@NNn1e~cUsT^a(V{6i!WGYyX@yEUyFykD9=HLy?0ug4c;sf5WDUJ=eF*O z94Ajsf;AaTGhIlQ(Ct_`zZ*QHYMS2H!+sl#2jqy(&Q<~0 zE89pHq3D7N5c?_l%_(uOq9@XfjG0qdMhe_U{i%DIP}*m;#PKjel)FqqAw-{IDGn-E zCM7^Z*TgkxH}ttAd$Sww@liquOUsZPrBT{QE1H$X^&LoF?ToaW!gH7-JJpo`UtTo) zLKlkeL1fmJJB66c62E+K`)kR8hAb$K%WDB}cB=Hq)jDC!ex&BlzAY)?r|v|dqlhU+ zC$CBnPG=!sY2;j;4Q5z}mz$9rZLpEEI2^uqXEW^tkGApK8%_LTi*C^J&567J`Da;%cs30!IwMlK=8PggR*N6C_Uho#irW@hST z9&-|PCo{+6_{+7vcDMUwuO;v9)xUuIwN85H9rw5-tZi(qtT^f(39H<%`o|SSueiRI zUv}Ol!DHN;&-eD2OLv0F{2fI zAfI;{ad|^(+Z5&5|KZl4^HrEBVaM}5w6s33a{AZ@3N^a-y}M_#iPWZS0ZiHycS`F` zQ++yiiO-plY%X@$4cILh>2I#3>$wMo z?d&R4ZL?>+&907+^3iI%H! zE}W16!|5-VGoEoKlAG`OrY5hUmK&KqNX8tJUDZ;cYn{WY=$hePMt*(WyR=>l$JgTY zh`-F{!1a#M4BR*?w>@U%vr{{BBj@h7c4p?Mcy+gh-L7EKyv&f8Qf&3B2VAW)<(eX+ z5(tbfjUvqnyT*)o1&6N@UZ?6$TIb~NB0|?Hr5}=4%3j((S-$yM1W;MJ?#agyyzgF{ zt7RK>PGRcWm%jSUz;7MZC!o@Fm`g<~BL|M6_ewlv0{t|(`^BGg-Vgn}MW%UqHlr$1 z?ugn_%r^=BoLwRWd$^<2*dX-byBfScFcSC12E;;FY>mMN*&>*C-Mqr z4H6tS|uSSSIkwCA^JIj_$o>pQV#hZGTYm zFd**yZ)9dt*|1S6TbbnP_~^`mDk(euKA2!z+&1@Jy$}AIMZF<;0ya}-DU79rRFk&GhYC|YA}DS49YJ@ z*rTax$3t!)lo8ug{$8;{pPzB~^ASaq`9d}@4@6~T^cpVIp9htrgkfx??PIk#_AlR> zM?XuR=DDeu0l4}u#`6IN7f)8ur$z14tfnsrX<`f3j_(DpJ%}l9RWI2OX!P0*{4(WTdxTT9Zaj~ZB z^Rs3e18+YQ;9_@e!3mohNDer2)RG0$Q3?dIaNQou$Ib?O28hR%In}g)wo6^_i?WVY za*D}u#fSIDI62_d4|`OS-^xNFE1nymyo3?z9Vx<0i(Drjp;aeJaH#=9;nJBib>^J@ zDw0gLj-r@zuV#w6WGV808sF9C`-}Hkmqf=5`#J=dU#)JE-A@GbyPB9|A`|6;gnQmP zVD#uUsx;=M7Me9d@nryCxPpt5y}|z5+j-1A2G`W&&EQlDH_Z`s^f!$$8>)QkIP&^{T_AxA$ir84K`0f9Lg>Tmm0p7VqcX0q6_nvmfx~8TUb( zo!U4zuS1`ZGxpR>i9EOXbC}bfQ0B_4Uf_b>UF}kxvHaGW1yb;cU9yvT|H%|mREpi9 zQ)mzbjFA4-5A6X9Kac%0PBvBuM%ot)GHDWv^EC!Yi<9x-GBLjB=1|i6Ca2u;ij?6< zNI>bIRO9~7JN_+u#QxOE^R8XJH4dYK2(stvmatz9$Fgoz)&{g!=r(14v9z(fr#tgnKj|a-n>rIGy(ZotO|g$L{Iz%7m0XNw|D`<}f3Yml#YRV6!J; zUp^xeX13oZ?q8r*u7KH?ZAII_F0Z?*t#viRTbGJJ^yB1)qfRfgk5)$PAI6ewb|KNM zZOVJ*1}Q}*?=orr`e*$Eu*v?x%>OOz3{aWQOEbndgSTJ$tUpu#2R)3sXoHg`*)h)l2If}3cDgzS&n~a z>VFy0Gc%*-x#m^CSa224t^4vzk!G0%*ReKIepNG7!IS@tms~f+9&-hsG;{kRahVSn zLizVXeh>ZP^I2Ibazz{52P#Y9lCZvn)`k9G&b2ENq)c&fN5Kn zOse9L%cW4Iz8d3@-P9mzE4f4UT2L>l0tJ;OKL6G*8M#EB5u;JRH}ABt`QV6Pe^p|nXegPq;(Yk$QdsNYbrpn(l7qY z|IL_P(Xs12N6xj^BZVU1VoGdB4r3}Fv@66sAuCGr=Q50l8E?LOyM4@g0seI1q!(dE z%NF{(83e=lD=S?$uZRGOPXSmiVr?w;b%?rki9B4+XLs*D|C|fasAcQiGrf+ydH;~a zt%6%e%rzjf<%k`B=eIr@%6Y1g_n$N$p!`ZRfDJcmo}%_c3Mva=@uq# z^(&I=^fb9nmw;9PXVu417j}yqy3Z6qy%wOT`f0sCAx@U*#Ym!x<>nPanI^a@7S;1T zea%F31eDGfi8?QD<>kNKVFlS1NU?K)7af7h#JrHoj6(sCBTKeA8fr8>Coc4tIvd@Cb^zob7#ys5ij|sZq z2;-&qB*jzyQegE;soq~eL}N=qh}vD+n%xqYVdn2is8Npf}dw*fsY z1;JuK8fbPx@XADH68mZ15ohBcJE?R$R?~ZG%6WghZfsdMqV7zWE9FVQdR}aTE@w}< zu3^{z)bX2?AyzhnK$9tLH9_rracPmo#PAYPK~wE11luKw0~?#JMZAn$1051-q8wP{ z>bJL%Pp|bSgPi34cSFw`^*nxLZijQ68YzgrQEwM%JXFG4>PCyaHYk?_br3;bGg)H` zqIt^{4ookGT;Nl+NTYljP<3^FR7-pnRU*f{K(dW|PRRZPhuvH=_fL*emhx@V9X^5= zT%~&sefx8OMlYzE9rh_Xjc+&N(-Xz=E*0RUnzm~=o&cv8<{IZ{t}kh9xWRG@%G8GQ zI74nm7E@ziG7-h9ItjW>vg%xy}a{hui77SZe^Tp zO|~2-d94Cn-(P6w+isBxCixnfmTE}8Mz(Yn62o`gF*@ztgPc(66k$RDw!89& zPNw+ZdtnaEu7>pWc4{^`klDbq-kl}iPjb}Xgv4=*ZPZ{&3n~J*sZ2B<$;CmQwpTr# z=2E8Kio${l)E3I;~YKYQ@MIdIM$Nc$_U%6CcYn~$0<|Lei++L$i-o=4cI zYC&)9JKhoo-qr;^5u*}+@|3<>^gv_`Bl6RS4%F{+G+uRIeYQn)Phe_89vn=&#&K3O zUb!CEvZ~DA^^V4U)B`7C$0Kk_wOn8qBxZg6i-E)MN79$h#~On_<-R?Qi7F~ZE%{Fv zc6B9XHZKXJQ71?Y%iSL=cF?oC2EBV!4xj&p?~73IPy$L{&ZAt-=?*0NYOHGpXyH20 zEKTQnGINsN2o(ltO-@*m=+f$;(ed$@kb^-;D};c8%nyCh#?{Z@<(E#VDkD2mTuhr& zTwn`(E;rtDtSZyT$c8DC*xUYf8BRNuQzyDejAxU2#zFHp#?+f!Z@{N^gtHm;eBB41 zxnb|yc22KR3tgB8!WxRuj@}ZNYGP!+>B5^G@c+-Cya4gpa0U=Km_;yh25BTNf3r9W z!?9aaliT#fHTuUTTVpfHtVu`%A1+=VI9+k2$JbBK5TcC7N@0DjK&)ZEoXR9z^#$rR~H z)@GH8ct(AUBU1AM6n@B<*S-LUlGb+WkCo=2P4M^;h0xoZyCVBmUq-xB5gw}o@+5l? z%R+tV!lGG&2}6}>KAwXHvGXhCSFd*#crs*3MeYmTd)43FK!(hqCr1(7`M_V`aGyes zUM@z6xFO_{3nC9TPi}n&Z=qyLV&{VEk9NlW$=(BYPM=wF|Mh4ys^H#AoXp8eVS2)SI)BE&DEtw!McpgX*?sa&*{sx?+<=Y}^6ewL@EQ z%+-R4{VUU_vXdo&Lu7sR(uX70vDO|9%)@5zqx!ho6!N*h#)Rb2nxFi$Fr?>VG}}D|(%Nx-8O9YVx`5I+ zZzJ#iohLOxJ5v4o(9k(f|Fv7BwOSDcB{Sv^(3Sy6(+EMdi?4kE2 z73$CHR)yaEoGfWM9cwY^8(%3j@YBoYY(nIDpx~2y6pwYVAl)16nxjkT_cYNFyuo*S zEn4~30m!%bJ551<>-?D}daGGlGLkX14E`1{wCA`qCsm&5X-11BTMqSqkKz;q1&LGY)mvV)rqlc_3eR_YT=gL z)BYy{w00tPlN0)%*))Z&pUY5Nmug)~Q}s4FCoLa-Fd~S@y2??b#QQVKZ{91KvPP)H zT!Gf`@OF}}HxlcJu!73BF~XF~s4npNs->3-wU4~~QFbAy<##kIXnK|HCXQ=v9pzb7 z_cR`5VHj$~dhYk|UMRjijscNCWUe2}<})rA>`5{SYAka3a$6b=HCNN9yM@DlUR)jD zdJz65r|jE@UJ$N8j+Kzde!`rxZF^~hpI(Y)sbo&})K4k75}_3u5CRVAa4L|N(3)^U ztDVOs1xeYudtYO2*UqZR!Kx%AGV9$#r$r%zJ`1dtH}V|w)e_AnCo>h17@)=0jcm(A z+A4K{oszT{MffqrrNlx%`?}4*2cP%looell!1R

@$6IC$H8l8F{8XhxF4h4)Dyg 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 Date: Wed, 10 Sep 2025 15:58:13 +0200 Subject: [PATCH 12/78] 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 13/78] 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 14/78] 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 15/78] 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 16/78] 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 17/78] 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 18/78] 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 19/78] 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 20/78] 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 21/78] 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 22/78] 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 c85230d1039a8767f22aaad089dad06baf07122e Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Mon, 15 Sep 2025 15:59:43 +0200 Subject: [PATCH 23/78] added get cohorts to cohortsController --- .gitignore | 2 ++ .../cohorts/controllers/CohortController.java | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/.gitignore b/.gitignore index d4bf309..72a5930 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ out/ ### Project specific application.yml +build +.DS_Store \ No newline at end of file diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index e1445ad..7b8ce0a 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -3,11 +3,16 @@ import com.booleanuk.cohorts.models.*; import com.booleanuk.cohorts.payload.response.*; import com.booleanuk.cohorts.repository.CohortRepository; +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; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("cohorts") @@ -15,6 +20,12 @@ public class CohortController { @Autowired private CohortRepository cohortRepository; + @Autowired + private UserRepository userRepository; + + @Autowired + private ProfileRepository profileRepository; + @GetMapping public ResponseEntity getAllCohorts() { CohortListResponse cohortListResponse = new CohortListResponse(); @@ -34,4 +45,18 @@ public ResponseEntity getCohortById(@PathVariable int id) { cohortResponse.set(cohort); return ResponseEntity.ok(cohortResponse); } + + @GetMapping("/users/{id}") + public ResponseEntity getCohorstByUserId(@PathVariable int id) { + User user = userRepository.findById(id).orElse(null); + if (user == null) return new ResponseEntity<>("User for id " + Integer.valueOf(id) + "not found", HttpStatus.NOT_FOUND); + Profile teacherProfile = profileRepository.findById(user.getProfile().getId()).orElse(null); + if (teacherProfile == null) return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); + + CohortResponse cohortResponse = new CohortResponse(); + Cohort cohort = teacherProfile.getCohort(); + cohortResponse.set(cohort); + + return new ResponseEntity(cohortResponse, HttpStatus.OK); + } } From 6b63b4f8dad5994133e208c03b8e64a22a148168 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Mon, 15 Sep 2025 16:02:39 +0200 Subject: [PATCH 24/78] 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 25/78] 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 26/78] 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 27/78] 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 f5809a8be0c98da97a69fc2d0efda4d9ad3382b8 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Tue, 16 Sep 2025 11:02:26 +0200 Subject: [PATCH 28/78] small update --- .../booleanuk/cohorts/controllers/CohortController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 7b8ce0a..df09d10 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -46,12 +46,12 @@ public ResponseEntity getCohortById(@PathVariable int id) { return ResponseEntity.ok(cohortResponse); } - @GetMapping("/users/{id}") + @GetMapping("/user/{id}") public ResponseEntity getCohorstByUserId(@PathVariable int id) { User user = userRepository.findById(id).orElse(null); - if (user == null) return new ResponseEntity<>("User for id " + Integer.valueOf(id) + "not found", HttpStatus.NOT_FOUND); - Profile teacherProfile = profileRepository.findById(user.getProfile().getId()).orElse(null); - if (teacherProfile == null) return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); + if (user == null) return new ResponseEntity<>("User for id " + Integer.valueOf(id) + " not found", HttpStatus.NOT_FOUND); + Profile teacherProfile = profileRepository.findById(user.getId()).orElse(null); + if (teacherProfile == null) return new ResponseEntity<>("Profile for user " + user.getEmail() +" not found", HttpStatus.NOT_FOUND); CohortResponse cohortResponse = new CohortResponse(); Cohort cohort = teacherProfile.getCohort(); From c65247e9b504ff1781807c6dda201883c170c797 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Mon, 15 Sep 2025 15:59:43 +0200 Subject: [PATCH 29/78] added get cohorts to cohortsController --- .gitignore | 2 ++ .../cohorts/controllers/CohortController.java | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/.gitignore b/.gitignore index d4bf309..72a5930 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ out/ ### Project specific application.yml +build +.DS_Store \ No newline at end of file diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index e1445ad..7b8ce0a 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -3,11 +3,16 @@ import com.booleanuk.cohorts.models.*; import com.booleanuk.cohorts.payload.response.*; import com.booleanuk.cohorts.repository.CohortRepository; +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; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("cohorts") @@ -15,6 +20,12 @@ public class CohortController { @Autowired private CohortRepository cohortRepository; + @Autowired + private UserRepository userRepository; + + @Autowired + private ProfileRepository profileRepository; + @GetMapping public ResponseEntity getAllCohorts() { CohortListResponse cohortListResponse = new CohortListResponse(); @@ -34,4 +45,18 @@ public ResponseEntity getCohortById(@PathVariable int id) { cohortResponse.set(cohort); return ResponseEntity.ok(cohortResponse); } + + @GetMapping("/users/{id}") + public ResponseEntity getCohorstByUserId(@PathVariable int id) { + User user = userRepository.findById(id).orElse(null); + if (user == null) return new ResponseEntity<>("User for id " + Integer.valueOf(id) + "not found", HttpStatus.NOT_FOUND); + Profile teacherProfile = profileRepository.findById(user.getProfile().getId()).orElse(null); + if (teacherProfile == null) return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); + + CohortResponse cohortResponse = new CohortResponse(); + Cohort cohort = teacherProfile.getCohort(); + cohortResponse.set(cohort); + + return new ResponseEntity(cohortResponse, HttpStatus.OK); + } } From 63a20f2b6522756387bcc93768d922cba9534b38 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Tue, 16 Sep 2025 11:02:26 +0200 Subject: [PATCH 30/78] small update --- .../booleanuk/cohorts/controllers/CohortController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 7b8ce0a..df09d10 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -46,12 +46,12 @@ public ResponseEntity getCohortById(@PathVariable int id) { return ResponseEntity.ok(cohortResponse); } - @GetMapping("/users/{id}") + @GetMapping("/user/{id}") public ResponseEntity getCohorstByUserId(@PathVariable int id) { User user = userRepository.findById(id).orElse(null); - if (user == null) return new ResponseEntity<>("User for id " + Integer.valueOf(id) + "not found", HttpStatus.NOT_FOUND); - Profile teacherProfile = profileRepository.findById(user.getProfile().getId()).orElse(null); - if (teacherProfile == null) return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); + if (user == null) return new ResponseEntity<>("User for id " + Integer.valueOf(id) + " not found", HttpStatus.NOT_FOUND); + Profile teacherProfile = profileRepository.findById(user.getId()).orElse(null); + if (teacherProfile == null) return new ResponseEntity<>("Profile for user " + user.getEmail() +" not found", HttpStatus.NOT_FOUND); CohortResponse cohortResponse = new CohortResponse(); Cohort cohort = teacherProfile.getCohort(); From 950f9b6905152c0e3fcb8edc956efab9e9aee3d5 Mon Sep 17 00:00:00 2001 From: Isabell Date: Tue, 16 Sep 2025 11:45:25 +0200 Subject: [PATCH 31/78] 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 32/78] 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 33/78] 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 c4a8d2ddb1d8e1caeea6f5dc3f0ed93946d01717 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Tue, 16 Sep 2025 13:07:51 +0200 Subject: [PATCH 34/78] added delete in userController, fixed mappings in models to ensure cascade deletion --- .../controllers/ProfileController.java | 6 ++- .../cohorts/controllers/UserController.java | 38 +++++++------------ .../com/booleanuk/cohorts/models/Profile.java | 7 +++- .../com/booleanuk/cohorts/models/User.java | 5 ++- 4 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 78c63ae..91b44e9 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -109,8 +109,12 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { HttpStatus.BAD_REQUEST); } + newProfile.setUser(user); + user.setProfile(newProfile); + user.setCohort(cohort); + try { - return new ResponseEntity<>(profileRepository.save(newProfile), HttpStatus.OK); + 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/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index 0c621a9..8a7f5d3 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -47,33 +47,23 @@ 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); + @DeleteMapping("{id}") + public ResponseEntity deleteUser(@PathVariable int id) { + User user = this.userRepository.findById(id).orElse(null); + if (user == null){ + ErrorResponse error = new ErrorResponse(); + error.set("not found"); + return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } - - Cohort cohort = profile.getCohort(); - - user.setProfile(profile); - user.setCohort(cohort); + user.getRoles().clear(); + UserResponse userResponse = new UserResponse(); + userResponse.set(user); try { - return new ResponseEntity<>(userRepository.save(user), HttpStatus.OK); - } catch (DataIntegrityViolationException e) { - return new ResponseEntity<>("User has an existing profile", HttpStatus.BAD_REQUEST); + userRepository.delete(user); + return ResponseEntity.ok(userResponse); + } catch (Exception e){ + return new ResponseEntity<>("Could not delete user", HttpStatus.BAD_REQUEST); } - } @PostMapping diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 6ea0d25..e5454f2 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 jakarta.validation.constraints.Pattern; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import org.hibernate.engine.internal.Cascade; import java.time.LocalDate; @@ -21,8 +23,9 @@ public class Profile { @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "user_id", nullable = false) + @OneToOne + @JoinColumn(name = "user_id", nullable = false, unique = true) + @OnDelete(action = OnDeleteAction.CASCADE) @JsonIgnoreProperties("profile") private User user; diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 107ae75..5292995 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -7,6 +7,8 @@ import jakarta.validation.constraints.Size; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; import java.util.HashSet; import java.util.Set; @@ -47,8 +49,7 @@ public class User { private Cohort cohort; - @OneToOne(cascade = CascadeType.ALL) - @JoinColumn(name = "profile_id") + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnoreProperties("user") private Profile profile; From b7f61fdb82b4cfd74a865a2bcdee7d337c7a3fd2 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Tue, 16 Sep 2025 13:20:37 +0200 Subject: [PATCH 35/78] 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 c2bd63afa60848cd35a8c8797495fd50da1ed500 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Tue, 16 Sep 2025 13:50:12 +0200 Subject: [PATCH 36/78] working --- .../com/booleanuk/cohorts/controllers/CohortController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index df09d10..dc6c13b 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -46,11 +46,11 @@ public ResponseEntity getCohortById(@PathVariable int id) { return ResponseEntity.ok(cohortResponse); } - @GetMapping("/user/{id}") + @GetMapping("/teacher/{id}") public ResponseEntity getCohorstByUserId(@PathVariable int id) { User user = userRepository.findById(id).orElse(null); if (user == null) return new ResponseEntity<>("User for id " + Integer.valueOf(id) + " not found", HttpStatus.NOT_FOUND); - Profile teacherProfile = profileRepository.findById(user.getId()).orElse(null); + Profile teacherProfile = profileRepository.findById(user.getProfile().getId()).orElse(null); if (teacherProfile == null) return new ResponseEntity<>("Profile for user " + user.getEmail() +" not found", HttpStatus.NOT_FOUND); CohortResponse cohortResponse = new CohortResponse(); From 5f2346464947619703eaee30473a5fc27a911c14 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Tue, 16 Sep 2025 14:10:09 +0200 Subject: [PATCH 37/78] added getmappings to retrieve profiles --- .../controllers/ProfileController.java | 20 +++++++++++++++++-- .../cohorts/payload/response/ProfileData.java | 14 +++++++++++++ .../payload/response/ProfileListData.java | 16 +++++++++++++++ .../payload/response/ProfileListResponse.java | 15 ++++++++++++++ .../payload/response/ProfileResponse.java | 13 ++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/ProfileData.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/ProfileListData.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/ProfileListResponse.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/ProfileResponse.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 78c63ae..bb87b80 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -1,6 +1,7 @@ package com.booleanuk.cohorts.controllers; import com.booleanuk.cohorts.models.*; +import com.booleanuk.cohorts.payload.response.*; import com.booleanuk.cohorts.repository.CohortRepository; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.RoleRepository; @@ -56,9 +57,24 @@ record PostProfile( ){} @GetMapping - public List createProfile() { - return profileRepository.findAll(); + public ResponseEntity getAllProfiles() { + ProfileListResponse profileListResponse = new ProfileListResponse(); + profileListResponse.set(this.profileRepository.findAll()); + return ResponseEntity.ok(profileListResponse); } + + @GetMapping("{id}") + public ResponseEntity getById(@PathVariable int id){ + ProfileResponse profileResponse = new ProfileResponse(); + + Profile profile = this.profileRepository.findById(id).orElse(null); + if (profile == null) { + return new ResponseEntity<>("Not found", HttpStatus.NOT_FOUND); + } + profileResponse.set(profile); + return ResponseEntity.ok(profileResponse); + } + @PostMapping public ResponseEntity createProfile(@RequestBody PostProfile profile) { diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/ProfileData.java b/src/main/java/com/booleanuk/cohorts/payload/response/ProfileData.java new file mode 100644 index 0000000..2fbfa78 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/ProfileData.java @@ -0,0 +1,14 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Profile; +import lombok.Getter; + +@Getter +public class ProfileData extends Data { + protected Profile profile; + + @Override + public void set(Profile profile) { + this.profile = profile; + } +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/ProfileListData.java b/src/main/java/com/booleanuk/cohorts/payload/response/ProfileListData.java new file mode 100644 index 0000000..eebfd29 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/ProfileListData.java @@ -0,0 +1,16 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Profile; +import lombok.Getter; + +import java.util.List; + +@Getter +public class ProfileListData extends Data> { + protected List profiles; + + @Override + public void set(List profiles) { + this.profiles = profiles; + } +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/ProfileListResponse.java b/src/main/java/com/booleanuk/cohorts/payload/response/ProfileListResponse.java new file mode 100644 index 0000000..b71770e --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/ProfileListResponse.java @@ -0,0 +1,15 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Profile; +import lombok.Getter; + +import java.util.List; + +@Getter +public class ProfileListResponse extends Response { + public void set(List profiles) { + Data> data = new ProfileListData(); + data.set(profiles); + super.set(data); + } +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/ProfileResponse.java b/src/main/java/com/booleanuk/cohorts/payload/response/ProfileResponse.java new file mode 100644 index 0000000..6f9cce0 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/ProfileResponse.java @@ -0,0 +1,13 @@ +package com.booleanuk.cohorts.payload.response; + +import com.booleanuk.cohorts.models.Profile; +import lombok.Getter; + +@Getter +public class ProfileResponse extends Response { + public void set(Profile profile) { + Data data = new ProfileData(); + data.set(profile); + super.set(data); + } +} From e5140b1aa353055060baa95974d17bad67ce32e7 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Tue, 16 Sep 2025 14:11:54 +0200 Subject: [PATCH 38/78] changed cohort to contain profiles and not users --- .../com/booleanuk/cohorts/controllers/CohortController.java | 2 +- src/main/java/com/booleanuk/cohorts/models/Cohort.java | 6 ++++-- src/main/java/com/booleanuk/cohorts/models/Profile.java | 3 +-- src/main/java/com/booleanuk/cohorts/models/User.java | 2 ++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index e1445ad..f88b4b3 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 || cohort.getUsers().isEmpty()) { + if (cohort == null || cohort.getProfiles().isEmpty()) { ErrorResponse error = new ErrorResponse(); error.set("not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index 6b2f1eb..ca77da0 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -25,9 +25,11 @@ public class Cohort { ) private List cohort_courses; + @OneToMany(mappedBy = "cohort", fetch = FetchType.LAZY) - @JsonIgnoreProperties("users") - private List users; + @JsonIgnoreProperties("cohort") + private List profiles; + public Cohort(int id) { diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 6ea0d25..f3ec03b 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -23,7 +23,7 @@ public class Profile { @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "user_id", nullable = false) - @JsonIgnoreProperties("profile") + @JsonIgnore private User user; @NotNull(message = "First name is mandatory") @@ -61,7 +61,6 @@ public class Profile { @ManyToOne @JoinColumn(name = "cohort_id") - @JsonIgnore private Cohort cohort; @ManyToOne diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 107ae75..867c688 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.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.Email; @@ -44,6 +45,7 @@ public class User { @ManyToOne @JoinColumn(name = "cohort_id", nullable = true) @JsonIgnoreProperties({"users","cohort_courses"}) + @JsonIgnore private Cohort cohort; From ca8c475d5a6435d66adcdd5f0a93aa1fb5669b7a Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Tue, 16 Sep 2025 14:50:07 +0200 Subject: [PATCH 39/78] 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; + } +} From d8f2dfa86edb4d02fc153641a9c886eae2d37d06 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Tue, 16 Sep 2025 15:19:00 +0200 Subject: [PATCH 40/78] remove postrequest --- .../cohorts/controllers/PostRequest.java | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/main/java/com/booleanuk/cohorts/controllers/PostRequest.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostRequest.java b/src/main/java/com/booleanuk/cohorts/controllers/PostRequest.java deleted file mode 100644 index f2072bb..0000000 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -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; - } -} From 1020d0ff30a4028e94c826aec1225ecba6d7be1f Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Tue, 16 Sep 2025 15:23:28 +0200 Subject: [PATCH 41/78] first --- src/main/java/com/booleanuk/Main.java | 1 - src/main/java/com/booleanuk/cohorts/models/User.java | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index a3c4461..7a9626c 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -52,6 +52,5 @@ public void run(String... args) { if (!this.roleRepository.existsByName(ERole.ROLE_ADMIN)) { this.roleRepository.save(new Role(ERole.ROLE_ADMIN)); } - } } diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 0ffad34..0003578 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -12,6 +12,7 @@ import org.hibernate.annotations.OnDeleteAction; import java.util.HashSet; +import java.util.List; import java.util.Set; @NoArgsConstructor @@ -50,6 +51,13 @@ public class User { @JsonIgnore private Cohort cohort; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnoreProperties("user") + private List posts; + + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnoreProperties("user") + private List comments; @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnoreProperties("user") From 737d823cad8ac7acb4e5b548b5b7ce633bdba83d Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Wed, 17 Sep 2025 09:56:31 +0200 Subject: [PATCH 42/78] fixed bio length in profile --- src/main/java/com/booleanuk/cohorts/models/Profile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 6a9170c..8172eed 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -45,7 +45,7 @@ public class Profile { @Column private String username; - @Column + @Column(length = 300) private String bio; @Column From 1f2cba28c46b26076d37ff868a1af8a23b6f808b Mon Sep 17 00:00:00 2001 From: Isabell Date: Wed, 17 Sep 2025 11:28:49 +0200 Subject: [PATCH 43/78] Added student to a Cohort --- src/main/java/com/booleanuk/Main.java | 7 +++++++ .../cohorts/controllers/CohortController.java | 20 +++++++++++++++++-- .../payload/request/ProfileRequest.java | 19 ++++++++++++++++++ .../payload/request/StudentRequest.java | 2 -- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index a3c4461..a46ff64 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -33,6 +33,13 @@ public static void main(String[] args) { @Override public void run(String... args) { + // Create a cohort. + Cohort cohort; + if (!this.cohortRepository.existsById(1)) { + cohort = this.cohortRepository.save(new Cohort()); + } else { + cohort = this.cohortRepository.findById(1).orElse(null); + } Role teacherRole; if (!this.roleRepository.existsByName(ERole.ROLE_TEACHER)) { teacherRole = this.roleRepository.save(new Role(ERole.ROLE_TEACHER)); diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 92efdb0..65946a3 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -1,17 +1,17 @@ package com.booleanuk.cohorts.controllers; import com.booleanuk.cohorts.models.*; +import com.booleanuk.cohorts.payload.request.ProfileRequest; import com.booleanuk.cohorts.payload.response.*; import com.booleanuk.cohorts.repository.CohortRepository; 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 java.util.ArrayList; -import java.util.List; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @@ -59,4 +59,20 @@ public ResponseEntity getCohorstByUserId(@PathVariable int id) { return new ResponseEntity(cohortResponse, HttpStatus.OK); } + + @PatchMapping("/teacher/{id}") + public ResponseEntity addStudentToCohort(@PathVariable int id, @RequestBody ProfileRequest profileRequest){ + Cohort cohort = cohortRepository.findById(id).orElse(null); + if (cohort == null) return new ResponseEntity<>("Cohort for id " + Integer.valueOf(id) + " not found", HttpStatus.NOT_FOUND); + + Profile profile = profileRepository.findById(profileRequest.getUserId()).orElse(null); + if (profile == null) return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); + + Cohort updatedCohort = cohortRepository.findById(profileRequest.getCohort()).orElse(null); + if (updatedCohort == null) return new ResponseEntity<>("Cohort not found", HttpStatus.NOT_FOUND); + + profile.setCohort(updatedCohort); + + return new ResponseEntity<>(profileRepository.save(profile), HttpStatus.OK); + } } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java new file mode 100644 index 0000000..87d6ecf --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java @@ -0,0 +1,19 @@ +package com.booleanuk.cohorts.payload.request; + + + +import com.booleanuk.cohorts.models.Cohort; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ProfileRequest { + private int cohort; + private int userId; + + public ProfileRequest(){} + + public int getCohort() { return cohort; } + public int getUserId() { return userId; } +} diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java index bfa76a1..ba1380b 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java @@ -30,6 +30,4 @@ public StudentRequest(){} public String getPassword() { return password; } public String getBio() { return bio; } - - } From 5c9e92dfa173b1396510e0ead1aed1dae85e00b1 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Wed, 17 Sep 2025 11:54:04 +0200 Subject: [PATCH 44/78] added comments, posts and likedposts to user and fixed json-loops --- .../java/com/booleanuk/cohorts/models/Cohort.java | 7 +++++++ .../java/com/booleanuk/cohorts/models/Comment.java | 7 +++++++ .../java/com/booleanuk/cohorts/models/Post.java | 7 +++++++ .../java/com/booleanuk/cohorts/models/Profile.java | 12 +++++++++--- .../java/com/booleanuk/cohorts/models/User.java | 13 +++++++++++-- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index ca77da0..b9d9b60 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -1,12 +1,19 @@ package com.booleanuk.cohorts.models; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; +@JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "id" +) + @NoArgsConstructor @Data @Entity diff --git a/src/main/java/com/booleanuk/cohorts/models/Comment.java b/src/main/java/com/booleanuk/cohorts/models/Comment.java index 6e6fbe5..99fe9f2 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Comment.java +++ b/src/main/java/com/booleanuk/cohorts/models/Comment.java @@ -1,7 +1,9 @@ package com.booleanuk.cohorts.models; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -14,6 +16,11 @@ import lombok.Data; import lombok.NoArgsConstructor; +@JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "id" +) + @NoArgsConstructor @AllArgsConstructor @Data diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index 6bacfed..35c0d33 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -3,8 +3,10 @@ import java.time.OffsetDateTime; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -22,6 +24,11 @@ import lombok.Data; import lombok.NoArgsConstructor; +@JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "id" +) + @NoArgsConstructor @AllArgsConstructor @Data diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 6a9170c..0460258 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -1,7 +1,8 @@ package com.booleanuk.cohorts.models; -import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; import jakarta.persistence.*; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -10,10 +11,15 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; -import org.hibernate.engine.internal.Cascade; import java.time.LocalDate; + +@JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "id" +) + @NoArgsConstructor @Data @Entity @@ -45,7 +51,7 @@ public class Profile { @Column private String username; - @Column + @Column(length = 300) private String bio; @Column diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 0003578..95410da 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -1,20 +1,25 @@ package com.booleanuk.cohorts.models; +import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.ObjectIdGenerators; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; import lombok.NoArgsConstructor; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; import java.util.HashSet; import java.util.List; import java.util.Set; +@JsonIdentityInfo( + generator = ObjectIdGenerators.PropertyGenerator.class, + property = "id" +) + @NoArgsConstructor @Data @Entity @@ -55,6 +60,10 @@ public class User { @JsonIgnoreProperties("user") private List posts; + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnoreProperties("user") + private List likedPosts; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIgnoreProperties("user") private List comments; From e3924114710e6531de01c3b5b0b3efa5df080869 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 17 Sep 2025 12:46:16 +0200 Subject: [PATCH 45/78] added search endoint for profiles --- .../cohorts/controllers/SearchController.java | 53 +++++++++++++++++++ .../cohorts/repository/ProfileRepository.java | 12 +++++ .../cohorts/security/WebSecurityConfig.java | 3 +- 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/booleanuk/cohorts/controllers/SearchController.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/SearchController.java b/src/main/java/com/booleanuk/cohorts/controllers/SearchController.java new file mode 100644 index 0000000..3d3f4ad --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/controllers/SearchController.java @@ -0,0 +1,53 @@ +package com.booleanuk.cohorts.controllers; + +import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.payload.response.ProfileListResponse; +import com.booleanuk.cohorts.payload.response.UserListResponse; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; + +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.LinkedList; +import java.util.List; + +@CrossOrigin(origins = "*", maxAge = 3600) +@RestController +@RequestMapping("search") +public class SearchController { + @Autowired + private UserRepository userRepository; + + @Autowired + private ProfileRepository profileRepository; + + @GetMapping("/profiles/{query}") + public ResponseEntity searchProfiles(@PathVariable String query) { + List result = new LinkedList(); + profileRepository.getProfilesByFirstNameContainingIgnoreCase(query).forEach(result::add); + profileRepository.getProfilesByLastNameContainingIgnoreCase(query).forEach(result::add); + + + ProfileListResponse profileListResponse = new ProfileListResponse(); + profileListResponse.set(result); + + return new ResponseEntity<>(profileListResponse, HttpStatus.OK); + + + } + + @GetMapping("/profiles") + public ResponseEntity searchProfilesDefault() { + List result = new LinkedList(); + profileRepository.findTop10ByOrderByIdDesc().forEach(result::add); + + ProfileListResponse profileListResponse = new ProfileListResponse(); + profileListResponse.set(result); + return new ResponseEntity<>(profileListResponse,HttpStatus.OK); + } +} diff --git a/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java b/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java index 4064ea8..05c23c3 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/ProfileRepository.java @@ -4,5 +4,17 @@ import com.booleanuk.cohorts.models.User; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface ProfileRepository extends JpaRepository { + List getProfilesByFirstNameContains(String firstName); + + List getProfilesByLastNameContains(String lastName); + + List findTop10ByOrderByIdDesc(); + + List getProfilesByFirstNameContainingIgnoreCase(String firstName); + + List getProfilesByLastNameContainingIgnoreCase(String lastName); + } diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index c85242a..99d47a1 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -37,7 +37,7 @@ public AuthTokenFilter authenticationJwtTokenFilter() { public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService); -// authProvider.setUserDetailsService(userDetailsService); +// authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; @@ -65,6 +65,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/cohorts", "/cohorts/**").authenticated() .requestMatchers("/courses", "/courses/**").authenticated() .requestMatchers("/logs", "/logs/**").authenticated() + .requestMatchers("/search", "/search/**").authenticated() .requestMatchers("/").authenticated() ); http.authenticationProvider(authenticationProvider()); From 571fdc5cbf30ed710d157b8d3f4706006bd9fc0c Mon Sep 17 00:00:00 2001 From: Isabell Date: Wed, 17 Sep 2025 13:05:22 +0200 Subject: [PATCH 46/78] Added get students --- .../controllers/StudentController.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index a20aac2..174b4f0 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -5,6 +5,7 @@ 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.ProfileListResponse; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -14,6 +15,9 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("student") @@ -27,6 +31,25 @@ public class StudentController { @Autowired PasswordEncoder encoder; + @GetMapping + public ResponseEntity getAllStudents() { + List allProfiles = this.profileRepository.findAll(); + + List students = new ArrayList<>(); + + for (Profile profile : allProfiles) { + if (profile.getRole().getName().name().equals("ROLE_STUDENT")) { + students.add(profile); + } + } + + ProfileListResponse studentListResponse = new ProfileListResponse(); + studentListResponse.set(students); + + return ResponseEntity.ok(studentListResponse); + } + + @PatchMapping("{id}") public ResponseEntity updateStudent(@PathVariable int id, @RequestBody StudentRequest studentRequest) { From 140ceefd84d254cf3c8bf128a6c5de0facbf76a5 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Wed, 17 Sep 2025 13:08:20 +0200 Subject: [PATCH 47/78] added templates --- .github/ISSUE_TEMPLATE/bug.md | 0 .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/feature.md | 0 .github/ISSUE_TEMPLATE/other.md | 0 4 files changed, 1 insertion(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature.md create mode 100644 .github/ISSUE_TEMPLATE/other.md diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ec4bb38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md new file mode 100644 index 0000000..e69de29 From c67f4021a3669f2b209e53a67c14d30eec5c1099 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Wed, 17 Sep 2025 13:18:35 +0200 Subject: [PATCH 48/78] added md bodies --- .github/ISSUE_TEMPLATE/bug.md | 28 ++++++++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature.md | 20 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/other.md | 17 +++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index e69de29..672b75e 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -0,0 +1,28 @@ + +--- +name: "🐞 Bug Fix" +about: "Report a bug or something that is not working as expected" +title: "[FIX] " +labels: bug +assignees: "" +--- + +## 🐛 Bug Description +A clear and concise description of what the bug is. + +## 🔄 Steps to Reproduce +Steps to reproduce the behavior: +1. Go to ... +2. Click on ... +3. See error ... + +## ✅ Expected Behavior +What should have happened instead? + +## 📸 Screenshots / Logs +If applicable, add screenshots or error messages. + +## 🖥️ Environment +- OS: +- Version: +- Other relevant info: diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md index e69de29..0635906 100644 --- a/.github/ISSUE_TEMPLATE/feature.md +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,20 @@ + +--- +name: "✨ Feature Request" +about: "Suggest a new feature or improvement" +title: "[FEATURE] " +labels: enhancement +assignees: "" +--- + +## ✨ Description +What would you like to add or improve? + +## 🎯 Use Case +Why is this useful? Who benefits from it? + +## 💡 Proposed Solution +How could this be implemented? (optional) + +## 📎 Additional Info +Anything else you’d like to share? (related issues, links, references, etc.) diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md index e69de29..c696995 100644 --- a/.github/ISSUE_TEMPLATE/other.md +++ b/.github/ISSUE_TEMPLATE/other.md @@ -0,0 +1,17 @@ + +--- +name: "📌 Other" +about: "Questions, discussions, or anything else" +title: "[OTHER] " +labels: question +assignees: "" +--- + +## 📌 Description +What is this about? + +## 🌍 Context +Why is this relevant? + +## 📎 Additional Info +Anything else that might be useful. From 36db025a0fd38612e8fbfb0a4508d8909de063e0 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Wed, 17 Sep 2025 13:31:19 +0200 Subject: [PATCH 49/78] added deletemapping to postController --- .../cohorts/controllers/PostController.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java index 0f25e4b..158b046 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java @@ -128,7 +128,18 @@ public ResponseEntity getPostById(@PathVariable int id) { PostResponse postResponse = new PostResponse(); postResponse.set(post); return ResponseEntity.ok(postResponse); - } + } + + @DeleteMapping("/{id}") + public ResponseEntity deletePostById(@PathVariable int id) { + Post post = this.postRepository.findById(id).orElse(null); + if (post == null) return notFoundResponse("Post not found"); + + PostResponse postResponse = new PostResponse(); + postResponse.set(post); + postRepository.delete(post); + return ResponseEntity.ok(postResponse); + } @PostMapping("/{postId}/comments") public ResponseEntity addCommentToPost(@PathVariable int postId, @RequestBody CommentRequest commentRequest) { From 031d071689be8f30f0303c773ed1d88d1299827e Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Wed, 17 Sep 2025 13:32:20 +0200 Subject: [PATCH 50/78] Added patch for teachers editing student profiles --- .../controllers/TeacherController.java | 67 +++++++++++++++++++ .../request/TeacherEditStudentRequest.java | 22 ++++++ .../cohorts/security/WebSecurityConfig.java | 1 + 3 files changed, 90 insertions(+) create mode 100644 src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/TeacherEditStudentRequest.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java new file mode 100644 index 0000000..754664e --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java @@ -0,0 +1,67 @@ +package com.booleanuk.cohorts.controllers; + +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.models.Role; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.StudentRequest; +import com.booleanuk.cohorts.payload.request.TeacherEditStudentRequest; +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.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) +@RestController +@RequestMapping("teacher") +public class TeacherController { + + @Autowired + private UserRepository userRepository; + + @Autowired + private ProfileRepository profileRepository; + @Autowired private CohortRepository cohortRepository; + @Autowired private RoleRepository roleRepository; + + @Autowired + PasswordEncoder encoder; + + @PatchMapping("{id}") + public ResponseEntity updateStudent(@PathVariable int id, @RequestBody TeacherEditStudentRequest teacherEditStudentRequest) { + + User user = userRepository.findById(id).orElse(null); + if (user == null) { + return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); + } + + Profile profile = profileRepository.findById(user.getProfile().getId()).orElse(null); + if (profile == null) { + return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); + } + + if (profile.getRole().getName().name().equals("ROLE_TEACHER")) { + return new ResponseEntity<>("Teachers can only edit other Students!", HttpStatus.BAD_REQUEST); + } + + Cohort cohort = cohortRepository.findById(teacherEditStudentRequest.getCohort_id()).orElseThrow(); + Role role = roleRepository.findById(teacherEditStudentRequest.getRole_id()).orElseThrow(); + + profile.setCohort(cohort); + profile.setRole(role); + profile.setStartDate(teacherEditStudentRequest.getStart_date()); + profile.setEndDate(teacherEditStudentRequest.getEnd_date()); + + + return new ResponseEntity<>(profileRepository.save(profile),HttpStatus.OK); + } + +} + diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/TeacherEditStudentRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/TeacherEditStudentRequest.java new file mode 100644 index 0000000..efd9d1c --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/TeacherEditStudentRequest.java @@ -0,0 +1,22 @@ +package com.booleanuk.cohorts.payload.request; + +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Course; +import com.booleanuk.cohorts.models.Role; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDate; + +@Data +@NoArgsConstructor +public class TeacherEditStudentRequest { + + + private int role_id; + private int cohort_id; + private LocalDate start_date; + private LocalDate end_date; +} diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index c85242a..eef8522 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -60,6 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/signup", "/signup/**").permitAll() .requestMatchers("/profiles", "/profiles/**").authenticated() .requestMatchers("/student", "/student/**").authenticated() + .requestMatchers("/teacher", "/teacher/**").authenticated() .requestMatchers("/users", "/users/**").authenticated() .requestMatchers("/posts", "/posts/**").authenticated() .requestMatchers("/cohorts", "/cohorts/**").authenticated() From e39fda1ed7a6f5b17c9bf03f71998a4a1fd9fc38 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Wed, 17 Sep 2025 14:25:59 +0200 Subject: [PATCH 51/78] added patchmapping --- .../cohorts/controllers/UserController.java | 41 +++++++++++++++++++ .../com/booleanuk/cohorts/models/User.java | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index 8a7f5d3..9b3e536 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -1,12 +1,14 @@ package com.booleanuk.cohorts.controllers; import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Post; 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.PostRepository; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -15,6 +17,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + import static java.util.Arrays.stream; @CrossOrigin(origins = "*", maxAge = 3600) @@ -27,6 +31,9 @@ public class UserController { @Autowired private ProfileRepository profileRepository; + @Autowired + private PostRepository postRepository; + @GetMapping public ResponseEntity getAllUsers() { UserListResponse userListResponse = new UserListResponse(); @@ -34,6 +41,10 @@ public ResponseEntity getAllUsers() { return ResponseEntity.ok(userListResponse); } + record PostId( + int post_id + ){} + @GetMapping("{id}") public ResponseEntity getUserById(@PathVariable int id) { User user = this.userRepository.findById(id).orElse(null); @@ -66,6 +77,36 @@ public ResponseEntity deleteUser(@PathVariable int id) { } } + @PatchMapping("{user_id}") + public ResponseEntity updateLikedPosts(@PathVariable int user_id, @RequestBody PostId postId){ + int post_id = postId.post_id; + User user = userRepository.findById(user_id).orElse(null); + Post post = postRepository.findById(post_id).orElse(null); + ErrorResponse errorResponse = new ErrorResponse(); + + if (user == null) { + errorResponse.set("User not found"); + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + if (post == null) { + errorResponse.set("Post not found"); + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + List likedPosts = user.getLikedPosts(); + + if (likedPosts.contains(post)) { + likedPosts.remove(post); + } else { + likedPosts.add(post); + } + user.setLikedPosts(likedPosts); + userRepository.save(user); + + UserResponse userResponse = new UserResponse(); + userResponse.set(user); + return ResponseEntity.ok(userResponse); + } + @PostMapping public void registerUser() { System.out.println("Register endpoint hit"); diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 95410da..9392ff6 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -60,7 +60,7 @@ public class User { @JsonIgnoreProperties("user") private List posts; - @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @OneToMany @JsonIgnoreProperties("user") private List likedPosts; From 08f3d8c63aeeb11644f014aaf80054894eca35ce Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Wed, 17 Sep 2025 14:40:21 +0200 Subject: [PATCH 52/78] I created it so that name and lastname gets from the token when loggin in --- .../cohorts/controllers/AuthController.java | 32 ++++++++----- .../controllers/ProfileController.java | 42 +++++++++-------- .../cohorts/repository/UserRepository.java | 11 ++++- .../cohorts/security/jwt/JwtUtils.java | 33 +++++++++---- .../security/services/UserDetailsImpl.java | 47 ++++++++++++++----- .../services/UserDetailsServiceImpl.java | 11 +++-- 6 files changed, 116 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java index 3a20429..089b7be 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java @@ -1,5 +1,23 @@ package com.booleanuk.cohorts.controllers; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.PostMapping; +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.ERole; import com.booleanuk.cohorts.models.Role; import com.booleanuk.cohorts.models.User; @@ -12,20 +30,8 @@ import com.booleanuk.cohorts.repository.UserRepository; import com.booleanuk.cohorts.security.jwt.JwtUtils; import com.booleanuk.cohorts.security.services.UserDetailsImpl; -import jakarta.validation.Valid; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.web.bind.annotation.*; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; +import jakarta.validation.Valid; //fixed issue with login. @CrossOrigin(origins = "*", maxAge = 3600) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index b156432..7ce1b16 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -1,28 +1,32 @@ package com.booleanuk.cohorts.controllers; -import com.booleanuk.cohorts.models.*; -import com.booleanuk.cohorts.payload.response.*; -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; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -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; -import java.util.List; -import java.util.Map; import java.util.Optional; -import static java.lang.Integer.parseInt; +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; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.ERole; +import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.models.Role; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.response.ProfileListResponse; +import com.booleanuk.cohorts.payload.response.ProfileResponse; +import com.booleanuk.cohorts.repository.CohortRepository; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.RoleRepository; +import com.booleanuk.cohorts.repository.UserRepository; @CrossOrigin(origins = "*", maxAge = 3600) @RestController diff --git a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java index ba5998d..e48c3e9 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java @@ -1,13 +1,20 @@ package com.booleanuk.cohorts.repository; -import com.booleanuk.cohorts.models.User; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; -import java.util.Optional; +import com.booleanuk.cohorts.models.User; @Repository public interface UserRepository extends JpaRepository { Optional findByEmail(String email); + + @Query("SELECT u FROM User u LEFT JOIN FETCH u.profile LEFT JOIN FETCH u.roles WHERE u.email = :email") + Optional findByEmailWithProfile(@Param("email") String email); + Boolean existsByEmail(String email); } 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 8f4ce40..20ed442 100644 --- a/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java +++ b/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java @@ -1,20 +1,23 @@ package com.booleanuk.cohorts.security.jwt; +import java.util.Date; + +import javax.crypto.SecretKey; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + import com.booleanuk.cohorts.security.services.UserDetailsImpl; + import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; -import java.util.Date; @Component public class JwtUtils { @@ -32,6 +35,8 @@ public String generateJwtToken(Authentication authentication) { return Jwts.builder() .subject((userPrincipal.getUsername())) .claim("userId", userPrincipal.getId()) + .claim("firstName", userPrincipal.getFirstName()) + .claim("lastName", userPrincipal.getLastName()) .issuedAt(new Date()) .expiration(new Date((new Date()).getTime() + this.jwtExpirationMs)) .signWith(this.key()) @@ -46,6 +51,18 @@ public String getUserNameFromJwtToken(String token) { return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().getSubject(); } + public String getFirstNameFromJwtToken(String token) { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("firstName", String.class); + } + + public String getLastNameFromJwtToken(String token) { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("lastName", String.class); + } + + public Integer getUserIdFromJwtToken(String token) { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("userId", Integer.class); + } + public boolean validateJwtToken(String authToken) { try { Jwts.parser().verifyWith(this.key()).build().parse(authToken); diff --git a/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java b/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java index 08e45e5..c071105 100644 --- a/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java +++ b/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java @@ -1,35 +1,41 @@ package com.booleanuk.cohorts.security.services; -import com.booleanuk.cohorts.models.User; -import com.fasterxml.jackson.annotation.JsonIgnore; -import lombok.Getter; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; - import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.booleanuk.cohorts.models.User; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import lombok.Getter; + @Getter public class UserDetailsImpl implements UserDetails { private static final long serialVersionUID = 1L; - private int id; - private String username; - private String email; + private final int id; + private final String username; + private final String email; + private final String firstName; + private final String lastName; @JsonIgnore - private String password; + private final String password; - private Collection authorities; + private final Collection authorities; - public UserDetailsImpl(int id, String username, String email, String password, Collection authorities) { + public UserDetailsImpl(int id, String username, String email, String password, String firstName, String lastName, Collection authorities) { this.id = id; this.username = email; this.email = email; this.password = password; + this.firstName = firstName; + this.lastName = lastName; this.authorities = authorities; } @@ -37,11 +43,26 @@ public static UserDetailsImpl build(User user) { List authorities = user.getRoles().stream() .map(role -> new SimpleGrantedAuthority(role.getName().name())) .collect(Collectors.toList()); + + // Get firstName and lastName from profile, with fallbacks if profile is null + String firstName = ""; + String lastName = ""; + + if (user.getProfile() != null) { + firstName = user.getProfile().getFirstName() != null ? user.getProfile().getFirstName() : ""; + lastName = user.getProfile().getLastName() != null ? user.getProfile().getLastName() : ""; + System.out.println("Profile found for user " + user.getEmail() + ": " + firstName + " " + lastName); + } else { + System.out.println("No profile found for user " + user.getEmail()); + } + return new UserDetailsImpl( user.getId(), user.getUsername(), user.getEmail(), user.getPassword(), + firstName, + lastName, authorities); } diff --git a/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsServiceImpl.java b/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsServiceImpl.java index 6095644..ac67c71 100644 --- a/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsServiceImpl.java +++ b/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsServiceImpl.java @@ -1,14 +1,16 @@ package com.booleanuk.cohorts.security.services; -import com.booleanuk.cohorts.models.User; -import com.booleanuk.cohorts.repository.UserRepository; -import jakarta.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.repository.UserRepository; + +import jakarta.transaction.Transactional; + @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired @@ -17,10 +19,9 @@ public class UserDetailsServiceImpl implements UserDetailsService { @Override @Transactional public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - User user = userRepository.findByEmail(email).orElseThrow( + User user = userRepository.findByEmailWithProfile(email).orElseThrow( () -> new UsernameNotFoundException("User not found with email: " + email) ); return UserDetailsImpl.build(user); - } } \ No newline at end of file From d5feb0200f320b5c975052eb13ebcd79865632a4 Mon Sep 17 00:00:00 2001 From: Isabell Date: Wed, 17 Sep 2025 14:45:52 +0200 Subject: [PATCH 53/78] Added new endpoint and nullpointexception --- .../cohorts/controllers/StudentController.java | 16 ++++++++++++++-- .../cohorts/payload/request/CohortRequest.java | 4 ++++ .../cohorts/security/WebSecurityConfig.java | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index 174b4f0..e333777 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -20,7 +20,7 @@ @CrossOrigin(origins = "*", maxAge = 3600) @RestController -@RequestMapping("student") +@RequestMapping("students") public class StudentController { @Autowired private UserRepository userRepository; @@ -35,10 +35,16 @@ public class StudentController { public ResponseEntity getAllStudents() { List allProfiles = this.profileRepository.findAll(); + for(Profile p: allProfiles){ + System.err.println("Navn: " + p.getFirstName()); + } + List students = new ArrayList<>(); for (Profile profile : allProfiles) { - if (profile.getRole().getName().name().equals("ROLE_STUDENT")) { + if (profile.getRole() != null && + profile.getRole().getName() != null && + "ROLE_STUDENT".equals(profile.getRole().getName().name())) { students.add(profile); } } @@ -46,6 +52,12 @@ public ResponseEntity getAllStudents() { ProfileListResponse studentListResponse = new ProfileListResponse(); studentListResponse.set(students); + System.err.println("Her kommer studenter"); + + for(Profile p: students){ + System.err.println("Navn: " + p.getFirstName()); + } + return ResponseEntity.ok(studentListResponse); } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java new file mode 100644 index 0000000..81c77aa --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java @@ -0,0 +1,4 @@ +package com.booleanuk.cohorts.payload.request; + +public class CohortRequest { +} diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index c85242a..98819b8 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -59,7 +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("/students", "/students/**").authenticated() .requestMatchers("/users", "/users/**").authenticated() .requestMatchers("/posts", "/posts/**").authenticated() .requestMatchers("/cohorts", "/cohorts/**").authenticated() From e9b18148c5d3944bd96fe5dd8d2d709418b08101 Mon Sep 17 00:00:00 2001 From: Isabell Date: Wed, 17 Sep 2025 15:13:48 +0200 Subject: [PATCH 54/78] added Cohort Request --- .../cohorts/controllers/CohortController.java | 20 ++++++++++++++++-- .../payload/request/CohortRequest.java | 21 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 92efdb0..238e24a 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -1,6 +1,7 @@ package com.booleanuk.cohorts.controllers; import com.booleanuk.cohorts.models.*; +import com.booleanuk.cohorts.payload.request.CohortRequest; import com.booleanuk.cohorts.payload.response.*; import com.booleanuk.cohorts.repository.CohortRepository; import com.booleanuk.cohorts.repository.ProfileRepository; @@ -10,8 +11,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; -import java.util.List; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @@ -59,4 +58,21 @@ public ResponseEntity getCohorstByUserId(@PathVariable int id) { return new ResponseEntity(cohortResponse, HttpStatus.OK); } + + @PatchMapping("{id}") + public ResponseEntity editCohortById(@PathVariable int id){ + Cohort + } + + @PatchMapping("{id}") + public ResponseEntity editCohortbyId(@PathVariable int id, @RequestBody CohortRequest cohortRequest){ + Cohort cohort = cohortRepository.findById(id).orElse(null); + if (cohort == null){ + return new ResponseEntity<>("Cohort not found", HttpStatus.NOT_FOUND); + } + + cohort.setCourse(cohortRequest.getCourse()); + cohort.setStartDate + } } + diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java new file mode 100644 index 0000000..9e20559 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java @@ -0,0 +1,21 @@ +package com.booleanuk.cohorts.payload.request; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class CohortRequest { + private String course; + private String start_date; + private String end_date; + + public CohortRequest(){} + + public String getCourse() { return course; } + + public String getStart_date() { return start_date; } + + public String getEnd_date() { return end_date; } + +} From 23cc06f0e57f7025958667d5f2905e59a486cdee Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Wed, 17 Sep 2025 15:17:36 +0200 Subject: [PATCH 55/78] Fix merge bugs --- src/main/java/com/booleanuk/cohorts/models/Cohort.java | 2 ++ src/main/java/com/booleanuk/cohorts/models/Comment.java | 4 ++-- src/main/java/com/booleanuk/cohorts/models/Post.java | 8 +++----- src/main/java/com/booleanuk/cohorts/models/Profile.java | 4 ++-- src/main/java/com/booleanuk/cohorts/models/User.java | 8 +++++--- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index b9d9b60..3337b2d 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -9,10 +9,12 @@ import java.util.List; +/* @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" ) +*/ @NoArgsConstructor @Data diff --git a/src/main/java/com/booleanuk/cohorts/models/Comment.java b/src/main/java/com/booleanuk/cohorts/models/Comment.java index 99fe9f2..cd30f1f 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Comment.java +++ b/src/main/java/com/booleanuk/cohorts/models/Comment.java @@ -16,10 +16,10 @@ import lombok.Data; import lombok.NoArgsConstructor; -@JsonIdentityInfo( +/*@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" -) +)*/ @NoArgsConstructor @AllArgsConstructor diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index 35c0d33..5a20b55 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -3,10 +3,8 @@ import java.time.OffsetDateTime; import java.util.List; -import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.ObjectIdGenerators; import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -24,10 +22,10 @@ import lombok.Data; import lombok.NoArgsConstructor; -@JsonIdentityInfo( +/*@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" -) +)*/ @NoArgsConstructor @AllArgsConstructor @@ -53,7 +51,7 @@ public class Post { @ManyToOne @JoinColumn(name = "user_id", nullable = false) - @JsonIgnoreProperties("users") + @JsonIgnoreProperties(value = {"posts", "comments", "cohort", "roles", "likedPosts"}) private User user; @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY) diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 0460258..376c1da 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -15,10 +15,10 @@ import java.time.LocalDate; -@JsonIdentityInfo( +/*@JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" -) +)*/ @NoArgsConstructor @Data diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 9392ff6..6ebe774 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -15,10 +15,12 @@ import java.util.List; import java.util.Set; +/* @JsonIdentityInfo( generator = ObjectIdGenerators.PropertyGenerator.class, property = "id" ) +*/ @NoArgsConstructor @Data @@ -57,15 +59,15 @@ public class User { private Cohort cohort; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnoreProperties("user") + @JsonIgnore private List posts; @OneToMany - @JsonIgnoreProperties("user") + @JsonIgnore private List likedPosts; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnoreProperties("user") + @JsonIgnore private List comments; @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) From 7efe5a48d299410dbdf4e23f25abb71cf5a4971d Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Wed, 17 Sep 2025 15:18:28 +0200 Subject: [PATCH 56/78] fixed bugs merge --- .../cohorts/controllers/UserController.java | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index 9b3e536..4f6c189 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -1,8 +1,21 @@ package com.booleanuk.cohorts.controllers; -import com.booleanuk.cohorts.models.Cohort; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +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.Post; -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; @@ -11,15 +24,6 @@ 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.dao.DataIntegrityViolationException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -import static java.util.Arrays.stream; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @@ -58,7 +62,7 @@ public ResponseEntity getUserById(@PathVariable int id) { return ResponseEntity.ok(userResponse); } - @DeleteMapping("{id}") + @DeleteMapping("{id}") public ResponseEntity deleteUser(@PathVariable int id) { User user = this.userRepository.findById(id).orElse(null); if (user == null){ From 8019cfbb7da46b7e2bbd93df2ba45b7fc1764ccb Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden <53274139+fanden@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:24:04 +0200 Subject: [PATCH 57/78] Creating gradle build script --- .github/workflows/gradle.yml | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 0000000..87d5ecb --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,67 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. + # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md + - name: Setup Gradle + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + + - name: Build with Gradle Wrapper + run: ./gradlew build + + # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). + # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. + # + # - name: Setup Gradle + # uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + # with: + # gradle-version: '8.9' + # + # - name: Build with Gradle 8.9 + # run: gradle build + + dependency-submission: + + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. + # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 From 6c81d2add333f3689626602b8ed04b579dce5906 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden <53274139+fanden@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:56:18 +0200 Subject: [PATCH 58/78] Update gradle.yml --- .github/workflows/gradle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 87d5ecb..cc37ba3 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -34,7 +34,7 @@ jobs: uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - name: Build with Gradle Wrapper - run: ./gradlew build + run: ./gradlew build --exclude-task test # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. From d4795b0eabbc70ecfc4bf32de8ad19e2f4d32eb7 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden <53274139+fanden@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:59:01 +0200 Subject: [PATCH 59/78] Update gradle.yml --- .github/workflows/gradle.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index cc37ba3..bb15599 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -46,22 +46,3 @@ jobs: # # - name: Build with Gradle 8.9 # run: gradle build - - dependency-submission: - - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. - # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md - - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 From 04680143e1b040625835cb537a9975fc41fc5ab3 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden <53274139+fanden@users.noreply.github.com> Date: Wed, 17 Sep 2025 16:23:03 +0200 Subject: [PATCH 60/78] Update gradle.yml --- .github/workflows/gradle.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index bb15599..3aecabc 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -36,13 +36,8 @@ jobs: - name: Build with Gradle Wrapper run: ./gradlew build --exclude-task test - # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). - # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. - # - # - name: Setup Gradle - # uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - # with: - # gradle-version: '8.9' - # - # - name: Build with Gradle 8.9 - # run: gradle build + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: Package + path: build/libs From 97eb64880d4732037f221520ca83546c665e2073 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Thu, 18 Sep 2025 08:36:51 +0200 Subject: [PATCH 61/78] removed template yml --- .github/ISSUE_TEMPLATE/config.yml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index ec4bb38..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1 +0,0 @@ -blank_issues_enabled: false \ No newline at end of file From 97d513ec04e31d697d8e759f47e919fbdc2d2433 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Thu, 18 Sep 2025 08:43:47 +0200 Subject: [PATCH 62/78] fixed .md-files --- .github/ISSUE_TEMPLATE/bug.md | 1 - .github/ISSUE_TEMPLATE/feature.md | 1 - .github/ISSUE_TEMPLATE/other.md | 1 - 3 files changed, 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 672b75e..c0ac7b3 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -1,4 +1,3 @@ - --- name: "🐞 Bug Fix" about: "Report a bug or something that is not working as expected" diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md index 0635906..647c8c4 100644 --- a/.github/ISSUE_TEMPLATE/feature.md +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -1,4 +1,3 @@ - --- name: "✨ Feature Request" about: "Suggest a new feature or improvement" diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md index c696995..8846635 100644 --- a/.github/ISSUE_TEMPLATE/other.md +++ b/.github/ISSUE_TEMPLATE/other.md @@ -1,4 +1,3 @@ - --- name: "📌 Other" about: "Questions, discussions, or anything else" From ba4d5cfa6590a10c79b48118574aea90999b237c Mon Sep 17 00:00:00 2001 From: Isabell Date: Thu, 18 Sep 2025 09:15:09 +0200 Subject: [PATCH 63/78] added start_date and end_date, started on edit Course --- .../cohorts/controllers/CohortController.java | 9 ++++----- .../java/com/booleanuk/cohorts/models/Cohort.java | 11 ++++++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 238e24a..2acfe6b 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -59,10 +59,6 @@ public ResponseEntity getCohorstByUserId(@PathVariable int id) { return new ResponseEntity(cohortResponse, HttpStatus.OK); } - @PatchMapping("{id}") - public ResponseEntity editCohortById(@PathVariable int id){ - Cohort - } @PatchMapping("{id}") public ResponseEntity editCohortbyId(@PathVariable int id, @RequestBody CohortRequest cohortRequest){ @@ -72,7 +68,10 @@ public ResponseEntity editCohortbyId(@PathVariable int id, @RequestBody Cohor } cohort.setCourse(cohortRequest.getCourse()); - cohort.setStartDate + cohort.setStartDate(cohortRequest.getStart_date()); + cohort.setEndDate(cohortRequest.getEnd_date()); + + return new ResponseEntity<>(cohortRepository.save(cohort), HttpStatus.OK); } } diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index ca77da0..a6827b6 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -5,6 +5,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.time.LocalDate; import java.util.List; @NoArgsConstructor @@ -16,7 +17,6 @@ public class Cohort { @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; - @ManyToMany @JsonIgnoreProperties("cohorts") @JoinTable(name = "cohort_course", @@ -30,12 +30,21 @@ public class Cohort { @JsonIgnoreProperties("cohort") private List profiles; + @Column + private LocalDate startDate; + @Column + private LocalDate endDate; public Cohort(int id) { this.id = id; } + public Cohort(LocalDate startDate, LocalDate endDate){ + this.startDate = startDate; + this.endDate = endDate; + } + @Override public String toString(){ From 973541fab59eac0abf6b274dc5c5f32ab0e72e0f Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Thu, 18 Sep 2025 09:40:52 +0200 Subject: [PATCH 64/78] fix jsonignore loops --- .../com/booleanuk/cohorts/models/Course.java | 3 ++- .../com/booleanuk/cohorts/models/Profile.java | 23 +++++++++++++------ .../com/booleanuk/cohorts/models/User.java | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/models/Course.java b/src/main/java/com/booleanuk/cohorts/models/Course.java index ffc559a..8e08404 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Course.java +++ b/src/main/java/com/booleanuk/cohorts/models/Course.java @@ -1,6 +1,7 @@ package com.booleanuk.cohorts.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; import lombok.Data; import lombok.NoArgsConstructor; @@ -21,8 +22,8 @@ public class Course { @Column private String name; - @JsonIgnoreProperties("cohort_courses") @ManyToMany(mappedBy = "cohort_courses") + @JsonIncludeProperties("id") private List cohorts; public Course(String name) { diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 376c1da..380f969 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -1,18 +1,26 @@ package com.booleanuk.cohorts.models; -import com.fasterxml.jackson.annotation.JsonIdentityInfo; +import java.time.LocalDate; + +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.ObjectIdGenerators; -import jakarta.persistence.*; + +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.OneToOne; +import jakarta.persistence.Table; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import lombok.Data; import lombok.NoArgsConstructor; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; - -import java.time.LocalDate; /*@JsonIdentityInfo( @@ -71,6 +79,7 @@ public class Profile { @ManyToOne @JoinColumn(name = "cohort_id") + @JsonIgnoreProperties({"profiles", "users", "deliveryLogs", "cohortCourses"}) private Cohort cohort; @ManyToOne diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 6ebe774..6bd308e 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -71,7 +71,7 @@ public class User { private List comments; @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnoreProperties("user") + @JsonIgnoreProperties({"user", "role"}) private Profile profile; public User(String email, String password) { From 2977a0548fd39be07988ee4f51a6d69fc8faba72 Mon Sep 17 00:00:00 2001 From: Isabell Date: Thu, 18 Sep 2025 09:44:00 +0200 Subject: [PATCH 65/78] Added GET all teachers --- .../controllers/StudentController.java | 10 ------- .../controllers/TeacherController.java | 26 ++++++++++++++++++- .../cohorts/security/WebSecurityConfig.java | 2 +- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index e333777..3af2b2a 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -35,10 +35,6 @@ public class StudentController { public ResponseEntity getAllStudents() { List allProfiles = this.profileRepository.findAll(); - for(Profile p: allProfiles){ - System.err.println("Navn: " + p.getFirstName()); - } - List students = new ArrayList<>(); for (Profile profile : allProfiles) { @@ -52,12 +48,6 @@ public ResponseEntity getAllStudents() { ProfileListResponse studentListResponse = new ProfileListResponse(); studentListResponse.set(students); - System.err.println("Her kommer studenter"); - - for(Profile p: students){ - System.err.println("Navn: " + p.getFirstName()); - } - return ResponseEntity.ok(studentListResponse); } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java index 754664e..9d0b358 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java @@ -6,6 +6,7 @@ import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.request.StudentRequest; import com.booleanuk.cohorts.payload.request.TeacherEditStudentRequest; +import com.booleanuk.cohorts.payload.response.ProfileListResponse; import com.booleanuk.cohorts.repository.CohortRepository; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.RoleRepository; @@ -17,10 +18,13 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController -@RequestMapping("teacher") +@RequestMapping("teachers") public class TeacherController { @Autowired @@ -63,5 +67,25 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Teache return new ResponseEntity<>(profileRepository.save(profile),HttpStatus.OK); } + @GetMapping + public ResponseEntity getAllTeachers(){ + List allProfiles = this.profileRepository.findAll(); + + List teachers = new ArrayList<>(); + + for (Profile profile : allProfiles) { + if (profile.getRole() != null && + profile.getRole().getName() != null && + "ROLE_TEACHER".equals(profile.getRole().getName().name())) { + teachers.add(profile); + } + } + + ProfileListResponse teacherListResponse = new ProfileListResponse(); + teacherListResponse.set(teachers); + + return ResponseEntity.ok(teacherListResponse); + } + } diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index 3a40060..c5c7674 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -60,7 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/signup", "/signup/**").permitAll() .requestMatchers("/profiles", "/profiles/**").authenticated() .requestMatchers("/students", "/students/**").authenticated() - .requestMatchers("/teacher", "/teacher/**").authenticated() + .requestMatchers("/teachers", "/teachers/**").authenticated() .requestMatchers("/users", "/users/**").authenticated() .requestMatchers("/posts", "/posts/**").authenticated() .requestMatchers("/cohorts", "/cohorts/**").authenticated() From d61f8584c64f939ae800f9670d2810c2c186de8a Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Thu, 18 Sep 2025 10:14:47 +0200 Subject: [PATCH 66/78] fixed infinite loops again --- .../com/booleanuk/cohorts/models/Comment.java | 2 +- .../java/com/booleanuk/cohorts/models/Post.java | 1 - .../java/com/booleanuk/cohorts/models/User.java | 16 ++++++---------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/models/Comment.java b/src/main/java/com/booleanuk/cohorts/models/Comment.java index cd30f1f..af5f176 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Comment.java +++ b/src/main/java/com/booleanuk/cohorts/models/Comment.java @@ -36,7 +36,7 @@ public class Comment { @ManyToOne @JoinColumn(name = "user_id", nullable = false) - @JsonIgnoreProperties("comments") + @JsonIgnoreProperties({"comments", "posts", "likedPosts", "cohort", "roles"}) private User user; @ManyToOne diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index 5a20b55..69daec8 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -4,7 +4,6 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; - import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 6bd308e..58ec681 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -1,9 +1,6 @@ package com.booleanuk.cohorts.models; -import com.fasterxml.jackson.annotation.JsonIdentityInfo; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.ObjectIdGenerators; +import com.fasterxml.jackson.annotation.*; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; @@ -54,24 +51,23 @@ public class User { @ManyToOne @JoinColumn(name = "cohort_id", nullable = true) - @JsonIgnoreProperties({"users","cohort_courses"}) - @JsonIgnore + @JsonIncludeProperties({"id", "cohort_courses"}) private Cohort cohort; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnore + @JsonIncludeProperties({"id", "content", "likes", "timeCreated", "timeUpdated" }) private List posts; @OneToMany - @JsonIgnore + @JsonIncludeProperties({"id","content", "likes" }) private List likedPosts; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnore + @JsonIncludeProperties({"id","body" }) private List comments; @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnoreProperties({"user", "role"}) + @JsonIgnoreProperties({"user", "role", "cohort"}) private Profile profile; public User(String email, String password) { From 519cea9091625adae2c70569ffdb9e1c0745aa95 Mon Sep 17 00:00:00 2001 From: Isabell Date: Thu, 18 Sep 2025 10:37:50 +0200 Subject: [PATCH 67/78] Added Get teachers by Cohort ID --- .../controllers/TeacherController.java | 32 ++++++++++++++++++- .../com/booleanuk/cohorts/models/User.java | 1 + .../cohorts/security/WebSecurityConfig.java | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java index 754664e..7fc0360 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java @@ -6,6 +6,8 @@ import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.request.StudentRequest; import com.booleanuk.cohorts.payload.request.TeacherEditStudentRequest; +import com.booleanuk.cohorts.payload.response.ProfileListResponse; +import com.booleanuk.cohorts.payload.response.Response; import com.booleanuk.cohorts.repository.CohortRepository; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.RoleRepository; @@ -17,10 +19,13 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController -@RequestMapping("teacher") +@RequestMapping("teachers") public class TeacherController { @Autowired @@ -63,5 +68,30 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Teache return new ResponseEntity<>(profileRepository.save(profile),HttpStatus.OK); } + @GetMapping("{id}") + public ResponseEntity getTeachersByCohortId(@PathVariable int id){ + Cohort cohort = cohortRepository.findById(id).orElse(null); + if (cohort == null){ + return new ResponseEntity<>("Cohort not found", HttpStatus.NOT_FOUND); + } + + List allProfiles = profileRepository.findAll(); + + List teachers = new ArrayList<>(); + + for (Profile profile : allProfiles) { + if (profile.getRole() != null && + profile.getRole().getName() != null && + "ROLE_TEACHER".equals(profile.getRole().getName().name()) && + profile.getCohort().getId() == cohort.getId()) { + teachers.add(profile); + } + } + ProfileListResponse teacherListResponse = new ProfileListResponse(); + teacherListResponse.set(teachers); + + return ResponseEntity.ok(teacherListResponse); + } + } diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 6bd308e..45271f1 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -50,6 +50,7 @@ public class User { @ManyToMany(fetch = FetchType.LAZY) @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id")) + @JsonIgnore private Set roles = new HashSet<>(); @ManyToOne diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index 3a40060..c5c7674 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -60,7 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/signup", "/signup/**").permitAll() .requestMatchers("/profiles", "/profiles/**").authenticated() .requestMatchers("/students", "/students/**").authenticated() - .requestMatchers("/teacher", "/teacher/**").authenticated() + .requestMatchers("/teachers", "/teachers/**").authenticated() .requestMatchers("/users", "/users/**").authenticated() .requestMatchers("/posts", "/posts/**").authenticated() .requestMatchers("/cohorts", "/cohorts/**").authenticated() From d8cc0ae510d73aff95aa1d7b17069c4d2864cb3b Mon Sep 17 00:00:00 2001 From: Isabell Date: Thu, 18 Sep 2025 13:03:37 +0200 Subject: [PATCH 68/78] Fixed so fields that are not included are overwritten with null --- .../controllers/StudentController.java | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index 3af2b2a..3bf55cb 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -65,20 +65,45 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); } - if (profile.getRole().equals("ROLE_TEACHER")) { + if (profile.getRole().getName().name().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()); - profile.setUsername(studentRequest.getUsername()); - profile.setGithubUrl(studentRequest.getGithub_username()); + if (studentRequest.getPhoto() != null) { + profile.setPhoto(studentRequest.getPhoto()); + } + + if (studentRequest.getFirst_name() != null) { + profile.setFirstName(studentRequest.getFirst_name()); + } + + if (studentRequest.getLast_name() != null) { + profile.setLastName(studentRequest.getLast_name()); + } + + if (studentRequest.getUsername() != null) { + profile.setUsername(studentRequest.getUsername()); + } + + if (studentRequest.getGithub_username() != null) { + profile.setGithubUrl(studentRequest.getGithub_username()); + } + + if (studentRequest.getEmail() != null) { + user.setEmail(studentRequest.getEmail()); + } + + if (studentRequest.getMobile() != null) { + profile.setMobile(studentRequest.getMobile()); + } + + if (studentRequest.getPassword() != null) { + user.setPassword(encoder.encode(studentRequest.getPassword())); + } - user.setEmail(studentRequest.getEmail()); - profile.setMobile(studentRequest.getMobile()); - user.setPassword(encoder.encode(studentRequest.getPassword())); - profile.setBio(studentRequest.getBio()); + if (studentRequest.getBio() != null) { + profile.setBio(studentRequest.getBio()); + } profileRepository.save(profile); From a28e298ad5af18a11efb40b43183e985f4b2c830 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Thu, 18 Sep 2025 13:16:07 +0200 Subject: [PATCH 69/78] added note model, controller, repo and request --- .../cohorts/controllers/NoteController.java | 90 +++++++++++++++++++ .../com/booleanuk/cohorts/models/Note.java | 39 ++++++++ .../com/booleanuk/cohorts/models/User.java | 3 + .../cohorts/payload/request/NoteReqeuest.java | 13 +++ .../cohorts/repository/NoteRepository.java | 7 ++ .../cohorts/security/WebSecurityConfig.java | 1 + 6 files changed, 153 insertions(+) create mode 100644 src/main/java/com/booleanuk/cohorts/controllers/NoteController.java create mode 100644 src/main/java/com/booleanuk/cohorts/models/Note.java create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/NoteReqeuest.java create mode 100644 src/main/java/com/booleanuk/cohorts/repository/NoteRepository.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/NoteController.java b/src/main/java/com/booleanuk/cohorts/controllers/NoteController.java new file mode 100644 index 0000000..0636cca --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/controllers/NoteController.java @@ -0,0 +1,90 @@ +package com.booleanuk.cohorts.controllers; + +import com.booleanuk.cohorts.models.ERole; +import com.booleanuk.cohorts.models.Note; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.NoteReqeuest; +import com.booleanuk.cohorts.payload.response.ErrorResponse; +import com.booleanuk.cohorts.repository.NoteRepository; +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.time.LocalDate; +import java.util.List; + +@RestController +@RequestMapping("notes") +public class NoteController { + + @Autowired private NoteRepository noteRepository; + @Autowired private UserRepository userRepository; + + + @GetMapping + public ResponseEntity getALlNotes(){ + + List notes = noteRepository.findAll(); + + if(notes.isEmpty()){ + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set("No notes created"); + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(notes,HttpStatus.OK); + } + + @GetMapping("id") + public ResponseEntity getNoteById(@PathVariable int id){ + + Note note = noteRepository.findById(id).orElseThrow(); + + if(note.getUser() == null || note.getTitle().isEmpty() || note.getDescription().isEmpty()){ + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set("Invalid note data"); + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(note,HttpStatus.OK); + } + + + + @PostMapping + private ResponseEntity createNote(@RequestBody NoteReqeuest noteReqeuest){ + + User user = userRepository.findById(noteReqeuest.getUser_id()).orElse(null); + + if(user == null ){ + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set("No user with that ID exists"); + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + + if(user.getProfile().getRole().getName().equals(ERole.ROLE_TEACHER)){ + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set("Cannot add note to a teacher"); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + if(noteReqeuest.getTitle().isEmpty() || noteReqeuest.getDescription().isEmpty()){ + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.set("Note is missing description or title"); + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + + Note note = new Note(); + note.setTitle(noteReqeuest.getTitle()); + note.setDescription(noteReqeuest.getDescription()); + note.setUser(user); + note.setCreated(LocalDate.now()); + + + return new ResponseEntity<>(note,HttpStatus.CREATED); + + } +} diff --git a/src/main/java/com/booleanuk/cohorts/models/Note.java b/src/main/java/com/booleanuk/cohorts/models/Note.java new file mode 100644 index 0000000..3eda85b --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/models/Note.java @@ -0,0 +1,39 @@ +package com.booleanuk.cohorts.models; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; + +@Data +@Table(name = "notes") +@Entity +@AllArgsConstructor +@NoArgsConstructor +public class Note { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + + @Column + private String title; + + @Column + private String description; + + @Column + private LocalDate created; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + public Note(User user, String title, String description) { + this.user = user; + this.title = title; + this.description = description; + } +} diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 58ec681..93c9dad 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -70,6 +70,9 @@ public class User { @JsonIgnoreProperties({"user", "role", "cohort"}) private Profile profile; + @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private List notes; + public User(String email, String password) { this.email = email; this.password = password; diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/NoteReqeuest.java b/src/main/java/com/booleanuk/cohorts/payload/request/NoteReqeuest.java new file mode 100644 index 0000000..64eed93 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/NoteReqeuest.java @@ -0,0 +1,13 @@ +package com.booleanuk.cohorts.payload.request; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class NoteReqeuest { + + private String title; + private String description; + private int user_id; +} diff --git a/src/main/java/com/booleanuk/cohorts/repository/NoteRepository.java b/src/main/java/com/booleanuk/cohorts/repository/NoteRepository.java new file mode 100644 index 0000000..3890e77 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/repository/NoteRepository.java @@ -0,0 +1,7 @@ +package com.booleanuk.cohorts.repository; + +import com.booleanuk.cohorts.models.Note; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface NoteRepository 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 c5c7674..3c97ac6 100644 --- a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java +++ b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java @@ -60,6 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/signup", "/signup/**").permitAll() .requestMatchers("/profiles", "/profiles/**").authenticated() .requestMatchers("/students", "/students/**").authenticated() + .requestMatchers("/notes", "/notes/**").authenticated() .requestMatchers("/teachers", "/teachers/**").authenticated() .requestMatchers("/users", "/users/**").authenticated() .requestMatchers("/posts", "/posts/**").authenticated() From 03cb7f12291530cfb1785f96c04c6857028f8c72 Mon Sep 17 00:00:00 2001 From: Isabell Date: Thu, 18 Sep 2025 13:48:22 +0200 Subject: [PATCH 70/78] Fixed bugs and added unique email --- .../booleanuk/cohorts/controllers/StudentController.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index 3bf55cb..f8e7ee4 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -60,7 +60,7 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); } - Profile profile = profileRepository.findById(id).orElse(null); + Profile profile = profileRepository.findById(user.getProfile().getId()).orElse(null); if (profile == null) { return new ResponseEntity<>("Profile not found", HttpStatus.NOT_FOUND); } @@ -89,7 +89,12 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen profile.setGithubUrl(studentRequest.getGithub_username()); } + if (studentRequest.getEmail() != null) { + boolean emailExists = userRepository.existsByEmail(studentRequest.getEmail()); + if (emailExists && !studentRequest.getEmail().equals(user.getEmail())){ + return new ResponseEntity<>("Email is already in use", HttpStatus.BAD_REQUEST); + } user.setEmail(studentRequest.getEmail()); } From d06fe5a6f012465ce5a3bbd45dc8ad5070bd0245 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Thu, 18 Sep 2025 14:19:47 +0200 Subject: [PATCH 71/78] added new classes --- .gitignore | 3 +- build.gradle | 8 +++ .../booleanuk/config/ApplicationConfig.java | 24 +++++++ .../TeamDevSimApplicationTests.java | 13 ---- .../controllerTests/CohortControllerTest.java | 4 ++ .../controllerTests/CourseControllerTest.java | 4 ++ .../DeliveryLogControllerTest.java | 4 ++ .../controllerTests/PostControllerTest.java | 4 ++ .../ProfileControllerTest.java | 4 ++ .../controllerTests/SearchControllerTest.java | 66 +++++++++++++++++++ .../StudentControllerTest.java | 4 ++ .../TeacherControllerTest.java | 4 ++ .../controllerTests/UserControllerTest.java | 4 ++ 13 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/booleanuk/config/ApplicationConfig.java delete mode 100644 src/test/java/com/booleanuk/TeamDevSim/TeamDevSimApplicationTests.java create mode 100644 src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java create mode 100644 src/test/java/com/booleanuk/controllerTests/CourseControllerTest.java create mode 100644 src/test/java/com/booleanuk/controllerTests/DeliveryLogControllerTest.java create mode 100644 src/test/java/com/booleanuk/controllerTests/PostControllerTest.java create mode 100644 src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java create mode 100644 src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java create mode 100644 src/test/java/com/booleanuk/controllerTests/StudentControllerTest.java create mode 100644 src/test/java/com/booleanuk/controllerTests/TeacherControllerTest.java create mode 100644 src/test/java/com/booleanuk/controllerTests/UserControllerTest.java diff --git a/.gitignore b/.gitignore index 72a5930..dc24404 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ out/ ### Project specific application.yml build -.DS_Store \ No newline at end of file +.DS_Store +application-azuread.yml \ No newline at end of file diff --git a/build.gradle b/build.gradle index 481ead5..7a4365e 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,14 @@ dependencies { testImplementation 'org.springframework.security:spring-security-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // https://mvnrepository.com/artifact/org.springframework/spring-test + testImplementation 'org.springframework:spring-test' + // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api + testImplementation 'org.junit.jupiter:junit-jupiter-api' + // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine + testImplementation 'org.junit.jupiter:junit-jupiter-engine' + + // https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api implementation 'jakarta.validation:jakarta.validation-api:3.1.1' } diff --git a/src/main/java/com/booleanuk/config/ApplicationConfig.java b/src/main/java/com/booleanuk/config/ApplicationConfig.java new file mode 100644 index 0000000..d89e518 --- /dev/null +++ b/src/main/java/com/booleanuk/config/ApplicationConfig.java @@ -0,0 +1,24 @@ +package com.booleanuk.config; + +import com.booleanuk.cohorts.repository.UserRepository; +import com.booleanuk.cohorts.controllers.SearchController; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ApplicationConfig { + + @Autowired + private UserRepository userRepository; + + @Bean(name = "searchController") + public SearchController searchController() { + return new SearchController(); + } + + @Bean + public UserRepository userRepository() { + return this.userRepository; + } +} \ No newline at end of file diff --git a/src/test/java/com/booleanuk/TeamDevSim/TeamDevSimApplicationTests.java b/src/test/java/com/booleanuk/TeamDevSim/TeamDevSimApplicationTests.java deleted file mode 100644 index b174732..0000000 --- a/src/test/java/com/booleanuk/TeamDevSim/TeamDevSimApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.booleanuk.TeamDevSim; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class TeamDevSimApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java b/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java new file mode 100644 index 0000000..41eeea9 --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java @@ -0,0 +1,4 @@ +package com.booleanuk.controllerTests; + +public class CohortControllerTest { +} diff --git a/src/test/java/com/booleanuk/controllerTests/CourseControllerTest.java b/src/test/java/com/booleanuk/controllerTests/CourseControllerTest.java new file mode 100644 index 0000000..978802e --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/CourseControllerTest.java @@ -0,0 +1,4 @@ +package com.booleanuk.controllerTests; + +public class CourseControllerTest { +} diff --git a/src/test/java/com/booleanuk/controllerTests/DeliveryLogControllerTest.java b/src/test/java/com/booleanuk/controllerTests/DeliveryLogControllerTest.java new file mode 100644 index 0000000..3e8bcf1 --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/DeliveryLogControllerTest.java @@ -0,0 +1,4 @@ +package com.booleanuk.controllerTests; + +public class DeliveryLogControllerTest { +} diff --git a/src/test/java/com/booleanuk/controllerTests/PostControllerTest.java b/src/test/java/com/booleanuk/controllerTests/PostControllerTest.java new file mode 100644 index 0000000..b6b6516 --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/PostControllerTest.java @@ -0,0 +1,4 @@ +package com.booleanuk.controllerTests; + +public class PostControllerTest { +} diff --git a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java new file mode 100644 index 0000000..fc7a322 --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java @@ -0,0 +1,4 @@ +package com.booleanuk.controllerTests; + +public class ProfileControllerTest { +} diff --git a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java new file mode 100644 index 0000000..eb8f54a --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java @@ -0,0 +1,66 @@ +package com.booleanuk.controllerTests; + +import com.booleanuk.cohorts.controllers.SearchController; +import com.booleanuk.cohorts.repository.UserRepository; +import jakarta.servlet.ServletContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.AssertionsKt.assertNotNull; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; + +@WebAppConfiguration +@SpringBootTest +class SearchControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + + @Configuration + static class Config { + @MockitoBean + UserRepository userRepository; + + @Bean + UserRepository userRepository() { + return this.userRepository; + } + } + + private MockMvc mockMvc; + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + } + + @Test + public void heuristicsTryGettingBeanSearchController() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("searchController")); + } + + @Test + public void tryGetingBaseURL_andGetSomeResponse() throws Exception { + this.mockMvc.perform(get("/")).andDo(print()) + .andExpect(view().name("")); + } + + +} diff --git a/src/test/java/com/booleanuk/controllerTests/StudentControllerTest.java b/src/test/java/com/booleanuk/controllerTests/StudentControllerTest.java new file mode 100644 index 0000000..d746cd5 --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/StudentControllerTest.java @@ -0,0 +1,4 @@ +package com.booleanuk.controllerTests; + +public class StudentControllerTest { +} diff --git a/src/test/java/com/booleanuk/controllerTests/TeacherControllerTest.java b/src/test/java/com/booleanuk/controllerTests/TeacherControllerTest.java new file mode 100644 index 0000000..9cd76f4 --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/TeacherControllerTest.java @@ -0,0 +1,4 @@ +package com.booleanuk.controllerTests; + +public class TeacherControllerTest { +} diff --git a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java new file mode 100644 index 0000000..45c01d1 --- /dev/null +++ b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java @@ -0,0 +1,4 @@ +package com.booleanuk.controllerTests; + +public class UserControllerTest { +} From 62a614fa367f0d826443000f75499b3647b2d266 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Thu, 18 Sep 2025 14:43:45 +0200 Subject: [PATCH 72/78] added patch to cohortController --- .../cohorts/controllers/CohortController.java | 82 +++++++++++++++++-- .../cohorts/controllers/UserController.java | 2 - .../com/booleanuk/cohorts/models/Cohort.java | 19 ++++- .../com/booleanuk/cohorts/models/Course.java | 3 +- .../com/booleanuk/cohorts/models/User.java | 2 - .../payload/request/CohortRequest.java | 14 ++-- .../payload/request/CommentRequest.java | 21 ++--- .../payload/request/CourseRequest.java | 4 +- .../cohorts/payload/request/PostRequest.java | 21 ++--- .../payload/request/StudentRequest.java | 12 --- 10 files changed, 113 insertions(+), 67 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 2acfe6b..9fa1b3c 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -4,6 +4,7 @@ import com.booleanuk.cohorts.payload.request.CohortRequest; import com.booleanuk.cohorts.payload.response.*; import com.booleanuk.cohorts.repository.CohortRepository; +import com.booleanuk.cohorts.repository.CourseRepository; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -11,6 +12,13 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @@ -19,6 +27,9 @@ public class CohortController { @Autowired private CohortRepository cohortRepository; + @Autowired + private CourseRepository courseRepository; + @Autowired private UserRepository userRepository; @@ -46,9 +57,9 @@ public ResponseEntity getCohortById(@PathVariable int id) { } @GetMapping("/teacher/{id}") - public ResponseEntity getCohorstByUserId(@PathVariable int id) { + public ResponseEntity getCohortByUserId(@PathVariable int id) { User user = userRepository.findById(id).orElse(null); - if (user == null) return new ResponseEntity<>("User for id " + Integer.valueOf(id) + " not found", HttpStatus.NOT_FOUND); + if (user == null) return new ResponseEntity<>("User for id " + id + " not found", HttpStatus.NOT_FOUND); Profile teacherProfile = profileRepository.findById(user.getProfile().getId()).orElse(null); if (teacherProfile == null) return new ResponseEntity<>("Profile for user " + user.getEmail() +" not found", HttpStatus.NOT_FOUND); @@ -56,20 +67,77 @@ public ResponseEntity getCohorstByUserId(@PathVariable int id) { Cohort cohort = teacherProfile.getCohort(); cohortResponse.set(cohort); - return new ResponseEntity(cohortResponse, HttpStatus.OK); + return new ResponseEntity<>(cohortResponse, HttpStatus.OK); } @PatchMapping("{id}") - public ResponseEntity editCohortbyId(@PathVariable int id, @RequestBody CohortRequest cohortRequest){ + public ResponseEntity editCohortById(@PathVariable int id, @RequestBody CohortRequest cohortRequest){ Cohort cohort = cohortRepository.findById(id).orElse(null); if (cohort == null){ return new ResponseEntity<>("Cohort not found", HttpStatus.NOT_FOUND); } - cohort.setCourse(cohortRequest.getCourse()); - cohort.setStartDate(cohortRequest.getStart_date()); - cohort.setEndDate(cohortRequest.getEnd_date()); + List profilesToInclude = cohortRequest.getProfileIds().stream() + .map(profileId -> profileRepository.findById(profileId) + .orElseThrow(() -> new RuntimeException("Profile with id " + profileId + " not found"))) + .toList(); + + List courses = cohortRequest.getCourseIds().stream() + .map(courseId -> courseRepository.findById(courseId) + .orElseThrow(() -> new RuntimeException("Profile with id " + courseId + " not found"))) + .collect(Collectors.toList()); + + if (cohortRequest.getName().isBlank()) { + return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); + } + + if (cohortRequest.getStart_date().isBlank() || cohortRequest.getEnd_date().isBlank()) { + return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); + } + + cohort.setCohort_courses(courses); + + List usersToInclude = userRepository.findAll().stream().filter(it -> + profilesToInclude.contains(it.getProfile())).toList(); + + List usersToExclude = userRepository.findAll().stream().filter(it -> + it.getCohort().getId() == cohort.getId() && !(profilesToInclude.contains(it.getProfile()))).toList(); + + List profilesToExclude = usersToExclude.stream().map(User::getProfile).toList().stream().filter(it -> + it.getCohort().getId() == cohort.getId() && !(profilesToInclude.contains(it))).toList(); + + cohort.setName(cohortRequest.getName()); + cohort.setStartDate(LocalDate.parse(cohortRequest.getStart_date())); + cohort.setEndDate(LocalDate.parse(cohortRequest.getEnd_date())); + + Cohort cohortRes = cohortRepository.findById(99).orElse(null); + if (cohortRes == null) { + return new ResponseEntity<>("Could not find RESERVE", HttpStatus.BAD_REQUEST); + } + + for (User user: usersToExclude){ + user.setCohort(cohortRes); + } + + for (Profile profile: profilesToExclude){ + profile.setCohort(cohortRes); + List prevProf = cohortRes.getProfiles(); + prevProf.add(profile); + cohortRes.setProfiles(prevProf); + } + + for (User user: usersToInclude){ + user.setCohort(cohort); + } + for (Profile prof : profilesToInclude){ + prof.setCohort(cohort); + } + profileRepository.saveAll(profilesToInclude); + profileRepository.saveAll(profilesToExclude); + userRepository.saveAll(usersToInclude); + userRepository.saveAll(usersToExclude); + cohortRepository.save(cohortRes); return new ResponseEntity<>(cohortRepository.save(cohort), HttpStatus.OK); } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index 8a7f5d3..3f023a0 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -15,8 +15,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import static java.util.Arrays.stream; - @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("users") diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index a6827b6..e474d8a 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import jakarta.persistence.*; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -9,6 +10,7 @@ import java.util.List; @NoArgsConstructor +@AllArgsConstructor @Data @Entity @Table(name = "cohorts") @@ -17,6 +19,9 @@ public class Cohort { @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; + @Column + private String name; + @ManyToMany @JsonIgnoreProperties("cohorts") @JoinTable(name = "cohort_course", @@ -45,9 +50,21 @@ public Cohort(LocalDate startDate, LocalDate endDate){ this.endDate = endDate; } + public Cohort(String name, LocalDate startDate, LocalDate endDate){ + this.name = name; + this.startDate = startDate; + this.endDate = endDate; + } + + public Cohort(int id, String name, LocalDate startDate, LocalDate endDate) { + this.id = id; + this.name = name; + this.startDate = startDate; + this.endDate = endDate; + } + @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 index ffc559a..2d0cf91 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Course.java +++ b/src/main/java/com/booleanuk/cohorts/models/Course.java @@ -21,12 +21,11 @@ public class Course { @Column private String name; - @JsonIgnoreProperties("cohort_courses") @ManyToMany(mappedBy = "cohort_courses") + @JsonIgnoreProperties("cohort_courses") private List cohorts; public Course(String name) { - this.id = id; this.name = name; } diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 0ffad34..ef2f749 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -8,8 +8,6 @@ import jakarta.validation.constraints.Size; import lombok.Data; import lombok.NoArgsConstructor; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; import java.util.HashSet; import java.util.Set; diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java index 9e20559..2fd665f 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java @@ -1,21 +1,21 @@ package com.booleanuk.cohorts.payload.request; +import com.booleanuk.cohorts.models.Course; +import com.booleanuk.cohorts.models.Profile; import lombok.Getter; import lombok.Setter; +import java.util.List; + @Getter @Setter public class CohortRequest { - private String course; + private String name; + private List courseIds; + private List profileIds; private String start_date; private String end_date; public CohortRequest(){} - public String getCourse() { return course; } - - public String getStart_date() { return start_date; } - - public String getEnd_date() { return end_date; } - } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CommentRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CommentRequest.java index d3bbab2..97900ae 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CommentRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CommentRequest.java @@ -1,7 +1,11 @@ package com.booleanuk.cohorts.payload.request; import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; +@Setter +@Getter public class CommentRequest { @NotBlank private String body; @@ -14,20 +18,5 @@ 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/request/CourseRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java index d9f90c2..6afd10c 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java @@ -1,7 +1,9 @@ package com.booleanuk.cohorts.payload.request; import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +@Getter public class CourseRequest { @NotBlank private String name; @@ -12,7 +14,5 @@ public CourseRequest(String name){ this.name = name; } - public String getName() { return name; } - } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java index 052d54f..e237361 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java @@ -1,7 +1,11 @@ package com.booleanuk.cohorts.payload.request; import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.Setter; +@Setter +@Getter public class PostRequest { @NotBlank private String content; @@ -14,20 +18,5 @@ 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; - } + } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java index bfa76a1..d4e2afa 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java @@ -19,17 +19,5 @@ public class StudentRequest { 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; } - - } From d7692e5dfcf646707c2d2354f520f642311ef261 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Thu, 18 Sep 2025 14:44:08 +0200 Subject: [PATCH 73/78] Updated post to actually save to the repository --- .../com/booleanuk/cohorts/controllers/NoteController.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/NoteController.java b/src/main/java/com/booleanuk/cohorts/controllers/NoteController.java index 0636cca..70690ae 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/NoteController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/NoteController.java @@ -82,9 +82,7 @@ private ResponseEntity createNote(@RequestBody NoteReqeuest noteReqeuest){ note.setDescription(noteReqeuest.getDescription()); note.setUser(user); note.setCreated(LocalDate.now()); - - - return new ResponseEntity<>(note,HttpStatus.CREATED); + return new ResponseEntity<>(noteRepository.save(note),HttpStatus.CREATED); } } From df382aac7a42c2fb77c7467c04e8414be1fa315f Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Thu, 18 Sep 2025 14:50:53 +0200 Subject: [PATCH 74/78] Got an example test working --- .../booleanuk/config/ApplicationConfig.java | 24 -------------- .../controllerTests/SearchControllerTest.java | 31 ++++++++----------- 2 files changed, 13 insertions(+), 42 deletions(-) delete mode 100644 src/main/java/com/booleanuk/config/ApplicationConfig.java diff --git a/src/main/java/com/booleanuk/config/ApplicationConfig.java b/src/main/java/com/booleanuk/config/ApplicationConfig.java deleted file mode 100644 index d89e518..0000000 --- a/src/main/java/com/booleanuk/config/ApplicationConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.booleanuk.config; - -import com.booleanuk.cohorts.repository.UserRepository; -import com.booleanuk.cohorts.controllers.SearchController; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class ApplicationConfig { - - @Autowired - private UserRepository userRepository; - - @Bean(name = "searchController") - public SearchController searchController() { - return new SearchController(); - } - - @Bean - public UserRepository userRepository() { - return this.userRepository; - } -} \ No newline at end of file diff --git a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java index eb8f54a..94c0313 100644 --- a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java @@ -6,9 +6,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.mock.web.MockServletContext; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.web.WebAppConfiguration; @@ -17,31 +16,28 @@ import org.springframework.web.context.WebApplicationContext; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.AssertionsKt.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; @WebAppConfiguration @SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class SearchControllerTest { @Autowired private WebApplicationContext webApplicationContext; - @Configuration - static class Config { - @MockitoBean - UserRepository userRepository; + @Autowired + private UserRepository userRepository; - @Bean - UserRepository userRepository() { - return this.userRepository; - } - } + @Autowired + private SearchController searchController; private MockMvc mockMvc; + @BeforeEach public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); @@ -57,10 +53,9 @@ public void heuristicsTryGettingBeanSearchController() { } @Test - public void tryGetingBaseURL_andGetSomeResponse() throws Exception { - this.mockMvc.perform(get("/")).andDo(print()) - .andExpect(view().name("")); + public void tryGettingBaseURL_andGetSomeResponse() throws Exception { + this.mockMvc.perform(get("/search/profiles")) + .andDo(print()) + .andExpect(status().isOk()); } - - } From f04fb7ae5bb595cc1e84d9198f0627339cb30988 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Thu, 18 Sep 2025 14:52:14 +0200 Subject: [PATCH 75/78] fixed brackets --- .../cohorts/payload/request/CohortRequest.java | 2 +- .../cohorts/payload/request/ProfileRequest.java | 2 -- .../cohorts/payload/request/StudentRequest.java | 14 +++----------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java index fb6ec75..a102f21 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java @@ -16,5 +16,5 @@ public class CohortRequest { private String start_date; private String end_date; - public CohortRequest(){} + public CohortRequest(){}} diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java index 87d6ecf..414e30e 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java @@ -14,6 +14,4 @@ public class ProfileRequest { public ProfileRequest(){} - public int getCohort() { return cohort; } - public int getUserId() { return userId; } } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java index e21fcc2..ba75e43 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java @@ -17,16 +17,8 @@ public class StudentRequest { private String password; private String bio; - public StudentRequest(){} + 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; } +} From 86f4bc941c33db78afe9c0be5c70649ef461fae9 Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Thu, 18 Sep 2025 15:49:02 +0200 Subject: [PATCH 76/78] minor fix in user/profile --- 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 72f3841..120d74a 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -68,7 +68,7 @@ public class User { private List comments; @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnoreProperties({"user", "role", "cohort"}) + @JsonIgnoreProperties({"user", "cohort"}) private Profile profile; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) From f17058456d89f76678a4bac6f01609acf39d8005 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Thu, 18 Sep 2025 15:52:39 +0200 Subject: [PATCH 77/78] New features to posts pages --- .../booleanuk/cohorts/controllers/UserController.java | 5 +++-- .../java/com/booleanuk/cohorts/models/Comment.java | 2 ++ src/main/java/com/booleanuk/cohorts/models/Post.java | 2 ++ .../java/com/booleanuk/cohorts/models/Profile.java | 2 ++ src/main/java/com/booleanuk/cohorts/models/User.java | 11 ++++++++--- 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index 4f6c189..67f5e4c 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -1,6 +1,7 @@ package com.booleanuk.cohorts.controllers; import java.util.List; +import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -81,7 +82,7 @@ public ResponseEntity deleteUser(@PathVariable int id) { } } - @PatchMapping("{user_id}") + @PatchMapping("{user_id}/like") public ResponseEntity updateLikedPosts(@PathVariable int user_id, @RequestBody PostId postId){ int post_id = postId.post_id; User user = userRepository.findById(user_id).orElse(null); @@ -96,7 +97,7 @@ public ResponseEntity updateLikedPosts(@PathVariable int user_id, @Req errorResponse.set("Post not found"); return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); } - List likedPosts = user.getLikedPosts(); + Set likedPosts = user.getLikedPosts(); if (likedPosts.contains(post)) { likedPosts.remove(post); diff --git a/src/main/java/com/booleanuk/cohorts/models/Comment.java b/src/main/java/com/booleanuk/cohorts/models/Comment.java index af5f176..11bde19 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Comment.java +++ b/src/main/java/com/booleanuk/cohorts/models/Comment.java @@ -14,6 +14,7 @@ import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /*@JsonIdentityInfo( @@ -24,6 +25,7 @@ @NoArgsConstructor @AllArgsConstructor @Data +@EqualsAndHashCode(exclude = {"user", "post"}) @Entity @Table(name = "comments") public class Comment { diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index 69daec8..eb38ee2 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -19,6 +19,7 @@ import jakarta.persistence.Transient; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /*@JsonIdentityInfo( @@ -29,6 +30,7 @@ @NoArgsConstructor @AllArgsConstructor @Data +@EqualsAndHashCode(exclude = {"user", "comments"}) @Entity @Table(name = "posts") public class Post { diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 380f969..e6bcf2a 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -20,6 +20,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -30,6 +31,7 @@ @NoArgsConstructor @Data +@EqualsAndHashCode(exclude = {"user", "cohort", "role"}) @Entity @Table(name = "profiles") public class Profile { diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 58ec681..0345fac 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import java.util.HashSet; @@ -21,6 +22,7 @@ @NoArgsConstructor @Data +@EqualsAndHashCode(exclude = {"profile", "posts", "comments", "likedPosts", "cohort"}) @Entity @Table(name = "users", uniqueConstraints = { @@ -58,9 +60,12 @@ public class User { @JsonIncludeProperties({"id", "content", "likes", "timeCreated", "timeUpdated" }) private List posts; - @OneToMany - @JsonIncludeProperties({"id","content", "likes" }) - private List likedPosts; + @ManyToMany + @JoinTable(name = "user_liked_posts", + joinColumns = @JoinColumn(name = "user_id"), + inverseJoinColumns = @JoinColumn(name = "post_id")) + @JsonIncludeProperties(value="id") + private Set likedPosts = new HashSet<>(); @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIncludeProperties({"id","body" }) From ed2d1fc76a2fa2b45462b3d33fbc924d0fc331cc Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Fri, 19 Sep 2025 11:46:46 +0200 Subject: [PATCH 78/78] Added user role to token --- .../com/booleanuk/cohorts/security/jwt/JwtUtils.java | 5 +++++ .../cohorts/security/services/UserDetailsImpl.java | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) 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 20ed442..159bbe2 100644 --- a/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java +++ b/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java @@ -37,6 +37,7 @@ public String generateJwtToken(Authentication authentication) { .claim("userId", userPrincipal.getId()) .claim("firstName", userPrincipal.getFirstName()) .claim("lastName", userPrincipal.getLastName()) + .claim("roleId", userPrincipal.getRoleId()) .issuedAt(new Date()) .expiration(new Date((new Date()).getTime() + this.jwtExpirationMs)) .signWith(this.key()) @@ -63,6 +64,10 @@ public Integer getUserIdFromJwtToken(String token) { return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("userId", Integer.class); } + public Integer getRoleIdFromJwtToken(String token) { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("roleId", Integer.class); + } + public boolean validateJwtToken(String authToken) { try { Jwts.parser().verifyWith(this.key()).build().parse(authToken); diff --git a/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java b/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java index c071105..407dd70 100644 --- a/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java +++ b/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java @@ -23,19 +23,21 @@ public class UserDetailsImpl implements UserDetails { private final String email; private final String firstName; private final String lastName; + private final Integer roleId; @JsonIgnore private final String password; private final Collection authorities; - public UserDetailsImpl(int id, String username, String email, String password, String firstName, String lastName, Collection authorities) { + public UserDetailsImpl(int id, String username, String email, String password, String firstName, String lastName, Integer roleId, Collection authorities) { this.id = id; this.username = email; this.email = email; this.password = password; this.firstName = firstName; this.lastName = lastName; + this.roleId = roleId; this.authorities = authorities; } @@ -55,6 +57,12 @@ public static UserDetailsImpl build(User user) { } else { System.out.println("No profile found for user " + user.getEmail()); } + + // Get the first role's ID (assuming users have only one primary role) + Integer roleId = null; + if (!user.getRoles().isEmpty()) { + roleId = user.getRoles().iterator().next().getId(); + } return new UserDetailsImpl( user.getId(), @@ -63,6 +71,7 @@ public static UserDetailsImpl build(User user) { user.getPassword(), firstName, lastName, + roleId, authorities); }