From 28c85be0f8c7dad515c46909ce4a0b9a6216bf1d Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 27 Aug 2025 13:10:51 +0200 Subject: [PATCH 001/119] 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 002/119] 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 003/119] 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 004/119] 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 005/119] 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 006/119] 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 007/119] 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 008/119] 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 009/119] 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 010/119] 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 011/119] 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 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

@$6IC$H8l8F{8XhxF4h4)Dyg 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 Date: Wed, 10 Sep 2025 15:58:13 +0200 Subject: [PATCH 012/119] 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 013/119] 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 014/119] 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 015/119] 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 016/119] 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 017/119] 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 018/119] 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 019/119] 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 020/119] 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 021/119] 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 022/119] 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 023/119] 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 024/119] 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 025/119] 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 026/119] 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 027/119] 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 028/119] 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 029/119] 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 030/119] 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 031/119] 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 032/119] 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 033/119] 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 034/119] 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 035/119] 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 036/119] 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 037/119] 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 038/119] 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 039/119] 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 040/119] 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 041/119] 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 042/119] 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 043/119] 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 044/119] 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 045/119] 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 046/119] 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 047/119] 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 048/119] 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 049/119] 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 050/119] 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 051/119] 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 052/119] 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 053/119] 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 054/119] 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 055/119] 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 056/119] 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 057/119] 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 058/119] 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 059/119] 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 060/119] 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 061/119] 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 062/119] 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 063/119] 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 064/119] 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 065/119] 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 066/119] 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 067/119] 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 068/119] 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 069/119] 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 070/119] 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 071/119] 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 072/119] 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 073/119] 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 074/119] 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 075/119] 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 076/119] 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 077/119] 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 078/119] 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); } From 3d13acbb898b298991d6a3a60574ec7f9eafa476 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Fri, 19 Sep 2025 13:30:16 +0200 Subject: [PATCH 079/119] Fixed relations --- .../cohorts/controllers/AuthController.java | 3 - .../cohorts/controllers/CohortController.java | 173 +++++++++++------- .../cohorts/controllers/CourseController.java | 28 ++- .../cohorts/controllers/PostController.java | 6 +- .../controllers/ProfileController.java | 1 - .../controllers/StudentController.java | 1 - .../com/booleanuk/cohorts/models/Cohort.java | 31 ++-- .../com/booleanuk/cohorts/models/Comment.java | 4 - .../com/booleanuk/cohorts/models/Course.java | 10 +- .../com/booleanuk/cohorts/models/Post.java | 5 - .../com/booleanuk/cohorts/models/Profile.java | 6 - .../com/booleanuk/cohorts/models/User.java | 22 +-- .../payload/request/CohortRequest.java | 6 +- .../request/CohortRequestWithProfiles.java | 20 ++ .../payload/request/ProfileRequest.java | 7 +- 15 files changed, 185 insertions(+), 138 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java index 089b7be..f18f0fb 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java @@ -89,9 +89,6 @@ public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signupRe // Create a new user add salt here if using one User user = new User(signupRequest.getEmail(), encoder.encode(signupRequest.getPassword())); - if (signupRequest.getCohort() != null) { - user.setCohort(signupRequest.getCohort()); - } Set strRoles = signupRequest.getRole(); Set roles = new HashSet<>(); diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index aec3665..ed99550 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -2,21 +2,19 @@ import com.booleanuk.cohorts.models.*; import com.booleanuk.cohorts.payload.request.CohortRequest; +import com.booleanuk.cohorts.payload.request.CohortRequestWithProfiles; import com.booleanuk.cohorts.payload.request.ProfileRequest; import com.booleanuk.cohorts.payload.response.*; +import org.springframework.beans.factory.annotation.Autowired; 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; -import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.boot.autoconfigure.graphql.GraphQlProperties; import org.springframework.http.HttpStatus; 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; @@ -70,90 +68,133 @@ public ResponseEntity getCohortByUserId(@PathVariable int id) { return new ResponseEntity<>(cohortResponse, HttpStatus.OK); } + @PostMapping + public ResponseEntity addCohort(@RequestBody CohortRequest cohortRequest){ - @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); - } + Course course = courseRepository.findById(cohortRequest.getCourseId()).orElse(null); + if (course == null) return new ResponseEntity<>("Course not found", HttpStatus.NOT_FOUND); - List profilesToInclude = cohortRequest.getProfileIds().stream() - .map(profileId -> profileRepository.findById(profileId) - .orElseThrow(() -> new RuntimeException("Profile with id " + profileId + " not found"))) - .toList(); + String name = cohortRequest.getName(); + if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - List courses = cohortRequest.getCourseIds().stream() - .map(courseId -> courseRepository.findById(courseId) - .orElseThrow(() -> new RuntimeException("Profile with id " + courseId + " not found"))) - .collect(Collectors.toList()); + String startDate = cohortRequest.getStart_date(); + String endDate = cohortRequest.getEnd_date(); + if (startDate.isBlank() || endDate.isBlank()) return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); - if (cohortRequest.getName().isBlank()) { - return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - } + Cohort cohort = new Cohort(cohortRequest.getName(), course, LocalDate.parse(startDate), LocalDate.parse(endDate)); + return ResponseEntity.ok(cohortRepository.save(cohort)); + } - if (cohortRequest.getStart_date().isBlank() || cohortRequest.getEnd_date().isBlank()) { - return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); + @PatchMapping("{id}") + public ResponseEntity editCohortById(@PathVariable int id, @RequestBody CohortRequestWithProfiles cohortRequest) { + Cohort cohort = cohortRepository.findById(id).orElse(null); + if (cohort == null) { + return new ResponseEntity<>("Cohort not found", HttpStatus.NOT_FOUND); } + Course course = courseRepository.findById(cohortRequest.getCourseId()).orElse(null); + if (course == null) return new ResponseEntity<>("Course not found", HttpStatus.NOT_FOUND); - cohort.setCohort_courses(courses); + String name = cohortRequest.getName(); + if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - List usersToInclude = userRepository.findAll().stream().filter(it -> - profilesToInclude.contains(it.getProfile())).toList(); + String startDate = cohortRequest.getStart_date(); + String endDate = cohortRequest.getEnd_date(); - List usersToExclude = userRepository.findAll().stream().filter(it -> - it.getCohort().getId() == cohort.getId() && !(profilesToInclude.contains(it.getProfile()))).toList(); + if (startDate.isBlank() || endDate.isBlank()) + return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); - List profilesToExclude = usersToExclude.stream().map(User::getProfile).toList().stream().filter(it -> - it.getCohort().getId() == cohort.getId() && !(profilesToInclude.contains(it))).toList(); + List profiles = profileRepository.findAll().stream().filter(it -> cohortRequest.getProfileIds().contains(it.getId())).toList(); + cohort.setProfiles(profiles); + cohort.setCourse(course); 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); - } + cohort.setStartDate(LocalDate.parse(startDate)); + cohort.setEndDate(LocalDate.parse(endDate)); - for (User user: usersToExclude){ - user.setCohort(cohortRes); - } - - for (Profile profile: profilesToExclude){ - profile.setCohort(cohortRes); - List prevProf = cohortRes.getProfiles(); - prevProf.add(profile); - cohortRes.setProfiles(prevProf); - } + return ResponseEntity.ok(cohortRepository.save(cohort)); + } - 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); - } +// @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); +// } +// +// 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); +// } @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); + Profile profile = profileRepository.findById(profileRequest.getProfileId()).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); + profile.setCohort(cohort); return new ResponseEntity<>(profileRepository.save(profile), HttpStatus.OK); } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java b/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java index 13a5f12..bcadd7d 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java @@ -1,18 +1,19 @@ 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.models.*; 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.CohortRepository; 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.*; +import java.util.ArrayList; +import java.util.List; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("courses") @@ -20,6 +21,9 @@ public class CourseController { @Autowired private CourseRepository courseRepository; + @Autowired + private CohortRepository cohortRepository; + @GetMapping public ResponseEntity getAllCourse(){ CourseListResponse courseListResponse = new CourseListResponse(); @@ -27,6 +31,22 @@ public ResponseEntity getAllCourse(){ return ResponseEntity.ok(courseListResponse); } + @GetMapping("students/{id}") + public ResponseEntity getAllStudents(@PathVariable int id){ + Course course = courseRepository.findById(id).orElse(null); + if (course == null) return new ResponseEntity<>("Course not found", HttpStatus.NOT_FOUND); + + List cohorts = cohortRepository.findAll().stream().filter(it -> course.getCohorts().contains(it)).toList(); + List profiles = new ArrayList<>(); + for (Cohort cohrt : cohorts) { + profiles.addAll(cohrt.getProfiles()); + } + List students = profiles.stream().filter(it -> it.getRole().toString().equals("ROLE_STUDENT")).toList(); + ProfileListResponse profileListResponse = new ProfileListResponse(); + profileListResponse.set(students); + return ResponseEntity.ok(profileListResponse); + } + @GetMapping("{id}") public ResponseEntity getCourseById(@PathVariable int id){ Course course = this.courseRepository.findById(id).orElse(null); diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java index 158b046..0585292 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java @@ -35,6 +35,8 @@ import com.booleanuk.cohorts.repository.UserRepository; import com.booleanuk.cohorts.security.services.UserDetailsImpl; +import static java.lang.Integer.parseInt; + @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("posts") @@ -83,11 +85,11 @@ private ResponseEntity forbiddenResponse(String message) { private void setAuthorInfo(Post post) { User user = post.getUser(); - if (user != null && user.getCohort() != null) { + if (user != null) { Profile profile = user.getProfile(); if (profile != null) { - Author author = new Author(user.getId(), user.getCohort().getId(), + Author author = new Author(user.getId(), profile.getCohort().getId(), profile.getFirstName(), profile.getLastName(), user.getEmail(), profile.getBio(), profile.getGithubUrl()); post.setAuthor(author); diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 7ce1b16..ea7f6c0 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -131,7 +131,6 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { newProfile.setUser(user); user.setProfile(newProfile); - user.setCohort(cohort); try { return new ResponseEntity<>(userRepository.save(user), HttpStatus.OK); diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index f8e7ee4..565f8cd 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -113,7 +113,6 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen profileRepository.save(profile); user.setProfile(profile); - user.setCohort(profile.getCohort()); try { return new ResponseEntity<>(userRepository.save(user), 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 d82f996..474f7bb 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -11,12 +11,6 @@ import java.time.LocalDate; import java.util.List; -/* -@JsonIdentityInfo( - generator = ObjectIdGenerators.PropertyGenerator.class, - property = "id" -) -*/ @NoArgsConstructor @AllArgsConstructor @@ -31,14 +25,10 @@ public class Cohort { @Column private String name; - @ManyToMany + @ManyToOne + @JoinColumn(name = "course_id") @JsonIgnoreProperties("cohorts") - @JoinTable(name = "cohort_course", - joinColumns = @JoinColumn(name = "cohort_id"), - inverseJoinColumns = @JoinColumn(name = "course_id") - ) - private List cohort_courses; - + private Course course; @OneToMany(mappedBy = "cohort", fetch = FetchType.LAZY) @JsonIgnoreProperties("cohort") @@ -59,12 +49,27 @@ public Cohort(LocalDate startDate, LocalDate endDate){ this.endDate = endDate; } + public Cohort(String name, List profiles, Course course, LocalDate startDate, LocalDate endDate){ + this.name = name; + this.profiles = profiles; + this.course = course; + this.startDate = startDate; + this.endDate = endDate; + } + public Cohort(String name, LocalDate startDate, LocalDate endDate){ this.name = name; this.startDate = startDate; this.endDate = endDate; } + public Cohort(String name, Course course, LocalDate startDate, LocalDate endDate){ + this.name = name; + this.course = course; + this.startDate = startDate; + this.endDate = endDate; + } + public Cohort(int id, String name, LocalDate startDate, LocalDate endDate) { this.id = id; this.name = name; diff --git a/src/main/java/com/booleanuk/cohorts/models/Comment.java b/src/main/java/com/booleanuk/cohorts/models/Comment.java index 11bde19..be637d6 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Comment.java +++ b/src/main/java/com/booleanuk/cohorts/models/Comment.java @@ -17,10 +17,6 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; -/*@JsonIdentityInfo( - generator = ObjectIdGenerators.PropertyGenerator.class, - property = "id" -)*/ @NoArgsConstructor @AllArgsConstructor diff --git a/src/main/java/com/booleanuk/cohorts/models/Course.java b/src/main/java/com/booleanuk/cohorts/models/Course.java index d210c9a..c09d8d4 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Course.java +++ b/src/main/java/com/booleanuk/cohorts/models/Course.java @@ -1,8 +1,8 @@ package com.booleanuk.cohorts.models; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIncludeProperties; import jakarta.persistence.*; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -10,6 +10,7 @@ @NoArgsConstructor +@AllArgsConstructor @Data @Entity @Table(name = "courses") @@ -22,7 +23,7 @@ public class Course { @Column private String name; - @ManyToMany(mappedBy = "cohort_courses") + @OneToMany(mappedBy = "course", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIncludeProperties("id") private List cohorts; @@ -30,6 +31,11 @@ public Course(String name) { this.name = name; } + public Course(String name, List cohorts){ + this.name = name; + this.cohorts = cohorts; + } + @Override public String toString(){ diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index eb38ee2..b2606ff 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -22,11 +22,6 @@ import lombok.EqualsAndHashCode; 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 e6bcf2a..7d6858e 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -23,12 +23,6 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; - -/*@JsonIdentityInfo( - generator = ObjectIdGenerators.PropertyGenerator.class, - property = "id" -)*/ - @NoArgsConstructor @Data @EqualsAndHashCode(exclude = {"user", "cohort", "role"}) diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 556ad2e..5b19fa4 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -13,16 +13,9 @@ import java.util.List; import java.util.Set; -/* -@JsonIdentityInfo( - generator = ObjectIdGenerators.PropertyGenerator.class, - property = "id" -) -*/ - @NoArgsConstructor @Data -@EqualsAndHashCode(exclude = {"profile", "posts", "comments", "likedPosts", "cohort"}) +@EqualsAndHashCode(exclude = {"profile", "posts", "comments", "likedPosts"}) @Entity @Table(name = "users", uniqueConstraints = { @@ -52,11 +45,6 @@ public class User { @JsonIgnore private Set roles = new HashSet<>(); - @ManyToOne - @JoinColumn(name = "cohort_id", nullable = true) - @JsonIncludeProperties({"id", "cohort_courses"}) - private Cohort cohort; - @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) @JsonIncludeProperties({"id", "content", "likes", "timeCreated", "timeUpdated" }) private List posts; @@ -84,15 +72,9 @@ public User(String email, String password) { this.password = password; } - public User(String email, String password, Cohort cohort) { - this.email = email; - this.password = password; - this.cohort = cohort; - } - public User(String email, String password, Cohort cohort, Profile profile) { + public User(String email, String password, Profile profile) { this.email = email; this.password = password; - this.cohort = cohort; this.profile = profile; } } 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 a102f21..80278be 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java @@ -1,7 +1,5 @@ package com.booleanuk.cohorts.payload.request; -import com.booleanuk.cohorts.models.Course; -import com.booleanuk.cohorts.models.Profile; import lombok.Getter; import lombok.Setter; @@ -11,10 +9,8 @@ @Setter public class CohortRequest { private String name; - private List courseIds; - private List profileIds; + private int courseId; private String start_date; private String end_date; public CohortRequest(){}} - diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java new file mode 100644 index 0000000..97fbae0 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java @@ -0,0 +1,20 @@ +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 CohortRequestWithProfiles { + private String name; + private int courseId; + private List profileIds; + private String start_date; + private String end_date; + + public CohortRequestWithProfiles(){}} + 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 414e30e..e1a5e13 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/ProfileRequest.java @@ -1,17 +1,12 @@ 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; + private int profileId; public ProfileRequest(){} - } From 63b4d4f3589a338aa95f496e8c9df86a8344b158 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Fri, 19 Sep 2025 13:56:03 +0200 Subject: [PATCH 080/119] fixed everything! --- .../cohorts/controllers/CohortController.java | 92 +++---------------- .../payload/request/CohortRequest.java | 4 +- .../request/CohortRequestWithProfiles.java | 4 +- 3 files changed, 19 insertions(+), 81 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index ed99550..e673aa9 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -15,6 +15,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -77,8 +78,8 @@ public ResponseEntity addCohort(@RequestBody CohortRequest cohortRequest){ String name = cohortRequest.getName(); if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - String startDate = cohortRequest.getStart_date(); - String endDate = cohortRequest.getEnd_date(); + String startDate = cohortRequest.getStartDate(); + String endDate = cohortRequest.getEndDate(); if (startDate.isBlank() || endDate.isBlank()) return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); Cohort cohort = new Cohort(cohortRequest.getName(), course, LocalDate.parse(startDate), LocalDate.parse(endDate)); @@ -97,13 +98,22 @@ public ResponseEntity editCohortById(@PathVariable int id, @RequestBody Cohor String name = cohortRequest.getName(); if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - String startDate = cohortRequest.getStart_date(); - String endDate = cohortRequest.getEnd_date(); + String startDate = cohortRequest.getStartDate(); + String endDate = cohortRequest.getEndDate(); if (startDate.isBlank() || endDate.isBlank()) return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); - List profiles = profileRepository.findAll().stream().filter(it -> cohortRequest.getProfileIds().contains(it.getId())).toList(); + List profiles = profileRepository.findAll().stream().filter(it -> cohortRequest.getProfileIds().contains(it.getId())).collect(Collectors.toList()); + for (Profile oldProfile : new ArrayList<>(cohort.getProfiles())) { + if (!profiles.contains(oldProfile)) { + oldProfile.setCohort(null); + } + } + + for (Profile newProfile : profiles) { + newProfile.setCohort(cohort); + } cohort.setProfiles(profiles); cohort.setCourse(course); @@ -114,78 +124,6 @@ public ResponseEntity editCohortById(@PathVariable int id, @RequestBody Cohor return ResponseEntity.ok(cohortRepository.save(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); -// } -// -// 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); -// } - @PatchMapping("/teacher/{id}") public ResponseEntity addStudentToCohort(@PathVariable int id, @RequestBody ProfileRequest profileRequest){ Cohort cohort = cohortRepository.findById(id).orElse(null); 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 80278be..1a1185f 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java @@ -10,7 +10,7 @@ public class CohortRequest { private String name; private int courseId; - private String start_date; - private String end_date; + private String startDate; + private String endDate; public CohortRequest(){}} diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java index 97fbae0..53ffb1e 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java @@ -13,8 +13,8 @@ public class CohortRequestWithProfiles { private String name; private int courseId; private List profileIds; - private String start_date; - private String end_date; + private String startDate; + private String endDate; public CohortRequestWithProfiles(){}} From 3af0b6366bfd356d945781d5ea1e43eae945f384 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Fri, 19 Sep 2025 14:13:11 +0200 Subject: [PATCH 081/119] minor fix in getStudents --- .../booleanuk/cohorts/controllers/CourseController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java b/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java index bcadd7d..90952fa 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java @@ -36,12 +36,12 @@ public ResponseEntity getAllStudents(@PathVariable int id){ Course course = courseRepository.findById(id).orElse(null); if (course == null) return new ResponseEntity<>("Course not found", HttpStatus.NOT_FOUND); - List cohorts = cohortRepository.findAll().stream().filter(it -> course.getCohorts().contains(it)).toList(); + List cohorts = course.getCohorts(); List profiles = new ArrayList<>(); - for (Cohort cohrt : cohorts) { - profiles.addAll(cohrt.getProfiles()); + for (Cohort cohort : cohorts) { + profiles.addAll(cohort.getProfiles()); } - List students = profiles.stream().filter(it -> it.getRole().toString().equals("ROLE_STUDENT")).toList(); + List students = profiles.stream().filter(it -> it.getRole().getId() == 2).toList(); ProfileListResponse profileListResponse = new ProfileListResponse(); profileListResponse.set(students); return ResponseEntity.ok(profileListResponse); From a79ac1548fb7892378576e1a06cf80585e0e8002 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Fri, 19 Sep 2025 15:22:01 +0200 Subject: [PATCH 082/119] fixed users --- 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 5b19fa4..6f8e729 100644 --- a/src/main/java/com/booleanuk/cohorts/models/User.java +++ b/src/main/java/com/booleanuk/cohorts/models/User.java @@ -61,7 +61,7 @@ public class User { private List comments; @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnoreProperties({"user", "cohort"}) + @JsonIgnoreProperties({"user"}) private Profile profile; @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) From eb7640d3f5d1d9fd5457d49699bd46e78a02f016 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Fri, 19 Sep 2025 15:50:53 +0200 Subject: [PATCH 083/119] written tests for ProfileController and SearchController --- build.gradle | 2 + .../controllers/ProfileController.java | 2 +- .../payload/request/SignupRequest.java | 5 + .../cohorts/repository/UserRepository.java | 3 + .../ProfileControllerTest.java | 170 ++++++++++++++++++ .../controllerTests/SearchControllerTest.java | 125 ++++++++++++- 6 files changed, 298 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 7a4365e..3e06b89 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,8 @@ dependencies { 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/com.h2database/h2 + testImplementation 'com.h2database:h2' // https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index 7ce1b16..6afc39d 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -44,7 +44,7 @@ public class ProfileController { @Autowired private UserRepository userRepository; - record PostProfile( + public record PostProfile( int userId, String first_name, String last_name, diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java index 0fc94de..2f0efce 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java @@ -24,4 +24,9 @@ public class SignupRequest { private String password; private Cohort cohort; + + public SignupRequest(String email, String password) { + this.email = email; + this.password = password; + } } diff --git a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java index e48c3e9..ba87b4b 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java @@ -1,5 +1,6 @@ package com.booleanuk.cohorts.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -17,4 +18,6 @@ public interface UserRepository extends JpaRepository { Optional findByEmailWithProfile(@Param("email") String email); Boolean existsByEmail(String email); + + List getTopById(int id); } diff --git a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java index fc7a322..be4562a 100644 --- a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java @@ -1,4 +1,174 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.AuthController; +import com.booleanuk.cohorts.controllers.ProfileController; +import com.booleanuk.cohorts.controllers.SearchController; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +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.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +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; + +@WebAppConfiguration +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional public class ProfileControllerTest { + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private UserRepository userRepository; + + @Autowired + private SearchController searchController; + + @Autowired + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @PersistenceContext + private EntityManager entityManager; + + private MockMvc mockMvc; + + private int actualUserId; + + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + userRepository.deleteAll(); + profileRepository.deleteAll(); + + SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); + ResponseEntity registerResponse = this.authController.registerUser(signupRequest); + entityManager.flush(); + entityManager.clear(); + + + actualUserId = userRepository.findAll().get(0).getId(); + + } + + @Test + public void heuristics_testClassSetup() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("searchController")); + } + + @Test + public void tryCreateProfile_testFirstNameOnCreatedProfile() throws Exception { + String profileJson = """ + { + "userId": %d, + "first_name": "Thomas", + "last_name": "Ladder", + "username": "gottaStepUp", + "mobile": "244783772", + "github_username": "tallerThanU", + "bio": "GI need a ladder, but can't afford one. So, steps will have to be taken", + "role": "ROLE_STUDENT", + "specialism": "Big moves", + "cohort": 1, + "start_date": "1999-01-01", + "end_date": "2039-01-01", + "photo": "https://media.makeameme.org/created/ladder-i.jpg" + } + """.formatted(actualUserId); + + MvcResult result = this.mockMvc.perform(post("/profiles") + .contentType(MediaType.APPLICATION_JSON) + .content(profileJson)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertNotNull(result); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json); + + JSONObject profile = jsonObject.getJSONObject("profile"); + String firstName = profile.getString("firstName"); + String username = profile.getString("username"); + + System.out.println("First Name: " + firstName); + assertEquals("Thomas", firstName, "Profile first name should be Thomas"); + assertEquals("gottaStepUp", username, "Profile username should be gottaStepUp"); + } + + @Test + public void tryGetProfileForId_testFirstNameOnFoundProfile_withProfilesInDB() throws Exception { + ResponseEntity profileResponse = profileController.createProfile(new ProfileController.PostProfile( + actualUserId, // Use the actual user ID + "Thomas", + "Ladder", + "gottaStepUp", + "244783772", + "tallerThanU", + "GI need a ladder, but can't afford one. So, steps will have to be taken", + "ROLE_STUDENT", + "Big moves", + 1, + "1999-01-01", + "2039-01-01", + "https://media.makeameme.org/created/ladder-i.jpg" + )); + + entityManager.flush(); + entityManager.clear(); + + MvcResult result = this.mockMvc.perform(get("/profiles/"+ actualUserId)) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn(); + + assertNotNull(result); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json); + + JSONObject profile = jsonObject.getJSONObject("data").getJSONObject("profile"); + String firstName = profile.getString("firstName"); + String username = profile.getString("username"); + System.out.println("First Name: " + firstName); + assertEquals("Thomas", firstName, "Profile first name should be Thomas"); + assertEquals("gottaStepUp", username, "Profile first name should be Thomas"); + } + } diff --git a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java index 94c0313..7d20ad5 100644 --- a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java @@ -1,30 +1,49 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.AuthController; +import com.booleanuk.cohorts.controllers.ProfileController; import com.booleanuk.cohorts.controllers.SearchController; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.Assertions; 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.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; 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.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; 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; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebAppConfiguration @SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional class SearchControllerTest { @Autowired @@ -36,15 +55,62 @@ class SearchControllerTest { @Autowired private SearchController searchController; + @Autowired + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @PersistenceContext + private EntityManager entityManager; + private MockMvc mockMvc; @BeforeEach public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + userRepository.deleteAll(); + profileRepository.deleteAll(); + + SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); + ResponseEntity registerResponse = this.authController.registerUser(signupRequest); + entityManager.flush(); + entityManager.clear(); + + List users = userRepository.findAll(); + User createdUser = users.get(0); + int actualUserId = createdUser.getId(); + + System.out.println("Using user ID for profile creation: " + actualUserId); + + + ResponseEntity profileResponse = profileController.createProfile(new ProfileController.PostProfile( + actualUserId, // Use the actual user ID + "Thomas", + "Ladder", + "gottaStepUp", + "244783772", + "tallerThanU", + "GI need a ladder, but can't afford one. So, steps will have to be taken", + "ROLE_STUDENT", + "Big moves", + 1, + "1999-01-01", + "2039-01-01", + "https://media.makeameme.org/created/ladder-i.jpg" + )); + + + entityManager.flush(); + entityManager.clear(); + } @Test - public void heuristicsTryGettingBeanSearchController() { + public void heuristics_testClassSetup() { ServletContext servletContext = webApplicationContext.getServletContext(); assertNotNull(servletContext); @@ -53,9 +119,52 @@ public void heuristicsTryGettingBeanSearchController() { } @Test - public void tryGettingBaseURL_andGetSomeResponse() throws Exception { - this.mockMvc.perform(get("/search/profiles")) + public void trySearchProfilesDefault_withProfilesInDB() throws Exception { + MvcResult result = this.mockMvc.perform(get("/search/profiles")) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + + JSONObject jsonObject = new JSONObject(json); + JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("profiles"); + + assertTrue(jsonArray.length() == 1, "Should return at least one profile"); + } + + @Test + public void trySearchProfilesQuery_testFirstNameOnFirstFoundProfile_withProfilesInDB() throws Exception { + MvcResult result = this.mockMvc.perform(get("/search/profiles/thomas")) + .andDo(print()) + .andExpect(status().isOk()) .andDo(print()) - .andExpect(status().isOk()); + .andReturn(); + + String json = result.getResponse().getContentAsString(); + + JSONObject jsonObject = new JSONObject(json); + JSONArray profileArray = jsonObject.getJSONObject("data").getJSONArray("profiles"); + assertTrue(profileArray.length() > 0, "Should return at least one profile"); + + JSONObject firstProfile = profileArray.getJSONObject(0); + String profileFirstName = firstProfile.getString("firstName"); + assertTrue(profileFirstName.toLowerCase().contains("thomas"), + "Profile first name should contain 'thomas'"); + } + + @Test + public void trySearchProfileQuery_testNoProfilesFound_withProfilesInDB() throws Exception { + MvcResult result = this.mockMvc.perform(get("/search/profiles/firstnamethatdoesnotexsist")) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json); + + JSONArray profileArray = jsonObject.getJSONObject("data").getJSONArray("profiles"); + assertTrue(profileArray.length() == 0, "Should return no profiles"); } } From c9d11e8b375631d8a9bd62ce700ff999447e8710 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Fri, 19 Sep 2025 15:54:29 +0200 Subject: [PATCH 084/119] bug fixes --- .../booleanuk/cohorts/controllers/PostController.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java index 0585292..c85260c 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java @@ -137,6 +137,15 @@ public ResponseEntity deletePostById(@PathVariable int id) { Post post = this.postRepository.findById(id).orElse(null); if (post == null) return notFoundResponse("Post not found"); + // First, remove this post from all users' liked posts to avoid foreign key constraint violation + List allUsers = this.userRepository.findAll(); + for (User user : allUsers) { + if (user.getLikedPosts().contains(post)) { + user.getLikedPosts().remove(post); + this.userRepository.save(user); + } + } + PostResponse postResponse = new PostResponse(); postResponse.set(post); postRepository.delete(post); From 683862464c891ac39953a43ef9c121e05967262d Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Mon, 22 Sep 2025 10:40:45 +0200 Subject: [PATCH 085/119] Deleted date from profile, cohort and related files. added date to course --- .../cohorts/controllers/CohortController.java | 14 +--------- .../cohorts/controllers/CourseController.java | 15 +++++++++-- .../controllers/ProfileController.java | 3 --- .../controllers/TeacherController.java | 2 -- .../com/booleanuk/cohorts/models/Cohort.java | 26 +++---------------- .../com/booleanuk/cohorts/models/Course.java | 11 +++++++- .../com/booleanuk/cohorts/models/Profile.java | 10 +------ .../payload/request/CohortRequest.java | 3 --- .../request/CohortRequestWithProfiles.java | 2 -- .../payload/request/CourseRequest.java | 8 +++++- 10 files changed, 36 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index e673aa9..3900a9a 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -10,11 +10,9 @@ import com.booleanuk.cohorts.repository.CourseRepository; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; -import org.springframework.boot.autoconfigure.graphql.GraphQlProperties; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -78,11 +76,8 @@ public ResponseEntity addCohort(@RequestBody CohortRequest cohortRequest){ String name = cohortRequest.getName(); if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - String startDate = cohortRequest.getStartDate(); - String endDate = cohortRequest.getEndDate(); - if (startDate.isBlank() || endDate.isBlank()) return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); - Cohort cohort = new Cohort(cohortRequest.getName(), course, LocalDate.parse(startDate), LocalDate.parse(endDate)); + Cohort cohort = new Cohort(cohortRequest.getName(), course); return ResponseEntity.ok(cohortRepository.save(cohort)); } @@ -98,11 +93,6 @@ public ResponseEntity editCohortById(@PathVariable int id, @RequestBody Cohor String name = cohortRequest.getName(); if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - String startDate = cohortRequest.getStartDate(); - String endDate = cohortRequest.getEndDate(); - - if (startDate.isBlank() || endDate.isBlank()) - return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); List profiles = profileRepository.findAll().stream().filter(it -> cohortRequest.getProfileIds().contains(it.getId())).collect(Collectors.toList()); for (Profile oldProfile : new ArrayList<>(cohort.getProfiles())) { @@ -118,8 +108,6 @@ public ResponseEntity editCohortById(@PathVariable int id, @RequestBody Cohor cohort.setProfiles(profiles); cohort.setCourse(course); cohort.setName(cohortRequest.getName()); - cohort.setStartDate(LocalDate.parse(startDate)); - cohort.setEndDate(LocalDate.parse(endDate)); return ResponseEntity.ok(cohortRepository.save(cohort)); } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java b/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java index 90952fa..f1f2893 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CourseController.java @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -61,8 +62,18 @@ public ResponseEntity getCourseById(@PathVariable int id){ } @PostMapping - public ResponseEntity createCourse(@RequestBody CourseRequest courseRequest){ - Course course = new Course(courseRequest.getName()); + public ResponseEntity createCourse(@RequestBody CourseRequest courseRequest){ + Course course = new Course(); + String startDate = courseRequest.getStartDate(); + String endDate = courseRequest.getEndDate(); + + + if (startDate.isBlank() || endDate.isBlank()) + return new ResponseEntity<>("Date cannot be blank", HttpStatus.BAD_REQUEST); + + course.setName(courseRequest.getName()); + course.setStartDate(LocalDate.parse(courseRequest.getStartDate())); + course.setEndDate(LocalDate.parse(courseRequest.getEndDate())); Course saveCourse = this.courseRepository.save(course); CourseResponse courseResponse = new CourseResponse(); courseResponse.set(saveCourse); diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index ea7f6c0..cec73eb 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -1,6 +1,5 @@ package com.booleanuk.cohorts.controllers; -import java.time.LocalDate; import java.time.format.DateTimeParseException; import java.util.Optional; @@ -120,8 +119,6 @@ public ResponseEntity createProfile(@RequestBody PostProfile profile) { role, profile.specialism, cohort, - LocalDate.parse(profile.start_date), - LocalDate.parse(profile.end_date), profile.photo ); } catch (DateTimeParseException e) { diff --git a/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java index 3c0cf42..3f68ed1 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java @@ -61,8 +61,6 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Teache 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/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index 474f7bb..e7f1712 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -8,7 +8,6 @@ import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDate; import java.util.List; @@ -34,47 +33,30 @@ 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; - } - public Cohort(String name, List profiles, Course course, LocalDate startDate, LocalDate endDate){ + public Cohort(String name, List profiles, Course course){ this.name = name; this.profiles = profiles; this.course = course; - this.startDate = startDate; - this.endDate = endDate; } - public Cohort(String name, LocalDate startDate, LocalDate endDate){ + public Cohort(String name){ this.name = name; - this.startDate = startDate; - this.endDate = endDate; } - public Cohort(String name, Course course, LocalDate startDate, LocalDate endDate){ + public Cohort(String name, Course course){ this.name = name; this.course = course; - this.startDate = startDate; - this.endDate = endDate; } - public Cohort(int id, String name, LocalDate startDate, LocalDate endDate) { + public Cohort(int id, String name) { this.id = id; this.name = name; - this.startDate = startDate; - this.endDate = endDate; } @Override diff --git a/src/main/java/com/booleanuk/cohorts/models/Course.java b/src/main/java/com/booleanuk/cohorts/models/Course.java index c09d8d4..05c7efe 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Course.java +++ b/src/main/java/com/booleanuk/cohorts/models/Course.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.time.LocalDate; import java.util.List; @@ -27,12 +28,20 @@ public class Course { @JsonIncludeProperties("id") private List cohorts; + @Column + private LocalDate startDate; + + @Column + private LocalDate endDate; + public Course(String name) { this.name = name; } - public Course(String name, List cohorts){ + public Course(String name, LocalDate startDate, LocalDate endDate, List cohorts){ this.name = name; + this.startDate = startDate; + this.endDate = endDate; this.cohorts = cohorts; } diff --git a/src/main/java/com/booleanuk/cohorts/models/Profile.java b/src/main/java/com/booleanuk/cohorts/models/Profile.java index 7d6858e..190a618 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -1,6 +1,5 @@ package com.booleanuk.cohorts.models; -import java.time.LocalDate; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; @@ -67,11 +66,6 @@ public class Profile { @Column private String specialism; - @Column - private LocalDate startDate; - - @Column - private LocalDate endDate; @ManyToOne @JoinColumn(name = "cohort_id") @@ -91,7 +85,7 @@ public Profile(int id) { } 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) { + String bio, Role role, String specialism, Cohort cohort, String photo) { this.user = user; this.firstName = firstName; this.lastName = lastName; @@ -102,8 +96,6 @@ public Profile(User user, String firstName, String lastName, String username, St 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/payload/request/CohortRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java index 1a1185f..f5864ce 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java @@ -3,14 +3,11 @@ import lombok.Getter; import lombok.Setter; -import java.util.List; @Getter @Setter public class CohortRequest { private String name; private int courseId; - private String startDate; - private String endDate; public CohortRequest(){}} diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java index 53ffb1e..fc6f540 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java @@ -13,8 +13,6 @@ public class CohortRequestWithProfiles { private String name; private int courseId; private List profileIds; - private String startDate; - private String endDate; public CohortRequestWithProfiles(){}} 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 6afd10c..ec4f3a8 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CourseRequest.java @@ -3,15 +3,21 @@ import jakarta.validation.constraints.NotBlank; import lombok.Getter; +import java.time.LocalDate; + @Getter public class CourseRequest { @NotBlank private String name; + private String startDate; + private String endDate; public CourseRequest() {} - public CourseRequest(String name){ + public CourseRequest(String name, String startDate, String endDate){ this.name = name; + this.startDate = startDate; + this.endDate = endDate; } From 366e7cf34998805d5934f4aa9b75d9aa0458e2ea Mon Sep 17 00:00:00 2001 From: Richard Persson Date: Mon, 22 Sep 2025 10:49:29 +0200 Subject: [PATCH 086/119] Increased the length the photo attribute can have in the db --- 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 190a618..a2dcca8 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -77,7 +77,7 @@ public class Profile { @JsonIgnoreProperties("cohort") private Role role; - @Column + @Column(length = 25_000) private String photo; public Profile(int id) { From a806d5892e023da56f143910143553100dc89bb9 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Mon, 22 Sep 2025 11:47:45 +0200 Subject: [PATCH 087/119] Added tests for ProfileController and UserController --- .../ProfileControllerTest.java | 3 - .../controllerTests/UserControllerTest.java | 265 ++++++++++++++++++ 2 files changed, 265 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java index be4562a..7468124 100644 --- a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java @@ -46,9 +46,6 @@ public class ProfileControllerTest { @Autowired private UserRepository userRepository; - @Autowired - private SearchController searchController; - @Autowired AuthController authController; diff --git a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java index 45c01d1..45d7607 100644 --- a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java @@ -1,4 +1,269 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.*; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.PostRequest; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.payload.response.PostResponse; +import com.booleanuk.cohorts.repository.PostRepository; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; +import com.booleanuk.cohorts.security.services.UserDetailsImpl; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +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.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebAppConfiguration +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional public class UserControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserController userController; + + @Autowired + private AuthController authController; + + @Autowired + private PostRepository postRepository; + + @Autowired + private PostController postController; + + @PersistenceContext + private EntityManager entityManager; + + private MockMvc mockMvc; + + private int actualUserId; + private User testUser; + + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + userRepository.deleteAll(); + + SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); + ResponseEntity registerResponse = this.authController.registerUser(signupRequest); + entityManager.flush(); + entityManager.clear(); + + testUser = userRepository.findAll().get(0); + actualUserId = testUser.getId(); + } + + private void authenticateUser(User user) { + UserDetailsImpl userDetails = UserDetailsImpl.build(user); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + @Test + public void heuristics_testClassSetup() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("userController")); + } + + @Test + public void tryGetAllUsers_testEmailOnFirstUser_withSingleUserInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/users") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertNotNull(result); + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + + JSONArray jsonArray = jsonObject.getJSONArray("users"); + String email = jsonArray.getJSONObject(0).getString("email"); + + assertTrue(jsonArray.length() == 1); + assertEquals("thomas@ladder.com", email, "Email should be thomas@ladder.com"); + } + + @Test + public void tryGetAllUsers_testEmailOnMultipleUsers_withMultipleUserInDb() throws Exception { + this.authController.registerUser(new SignupRequest("fredrik@ladder.com", "@Qwerty12345")); + this.authController.registerUser(new SignupRequest("sara@ladder.com", "@Qwerty12345")); + this.authController.registerUser(new SignupRequest("josefine@ladder.com", "@Qwerty12345")); + entityManager.flush(); + entityManager.clear(); + + MvcResult result = this.mockMvc.perform(get("/users") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertNotNull(result); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + JSONArray jsonArray = jsonObject.getJSONArray("users"); + + assertTrue(jsonArray.length() == 4); + + String emailThomas = jsonArray.getJSONObject(0).getString("email"); + String emailFredrik = jsonArray.getJSONObject(1).getString("email"); + String emailSara = jsonArray.getJSONObject(2).getString("email"); + String emailJosefine = jsonArray.getJSONObject(3).getString("email"); + + assertEquals("thomas@ladder.com", emailThomas, "Email should be thomas@ladder.com"); + assertEquals("fredrik@ladder.com", emailFredrik, "Email should be fredrik@ladder.com"); + assertEquals("sara@ladder.com", emailSara, "Email should be sara@ladder.com"); + assertEquals("josefine@ladder.com", emailJosefine, "Email should be josefine@ladder.com"); + } + + @Test + public void tryGetUserById_testEmailOnFirstUser_withSingleUserInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/users/" + actualUserId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertNotNull(result); + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + + JSONObject user = jsonObject.getJSONObject("user"); + String email = user.getString("email"); + assertEquals("thomas@ladder.com", email, "Email should be thomas@ladder.com"); + } + + @Test + public void tryDeleteUserById_testReturnCodeAndIfUserIsActuallyDeleted() throws Exception { + MvcResult result = this.mockMvc.perform(delete("/users/" + actualUserId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertTrue(userRepository.findById(actualUserId).isEmpty(), "User list should be empty"); + } + + @Test + public void tryUpdateLikedPosts_testAddingSingleLikedPost_checkUserObjectAndResponseBody_withSingleUserIndb() throws Exception { + authenticateUser(testUser); + + ResponseEntity postResponse = this.postController.createPost(new PostRequest("It's not DNS... There's no way it's DNS... It was DNS", actualUserId)); + + entityManager.flush(); + entityManager.clear(); + + int postId = this.postRepository.findAll().get(0).getId(); + + // Create the request body with the post_id + String requestBody = "{\"post_id\": " + postId + "}"; + + MvcResult result = this.mockMvc.perform(patch("/users/" + actualUserId) + .with(user(UserDetailsImpl.build(testUser))) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + JSONObject user = jsonObject.getJSONObject("user"); + JSONArray likedPosts = user.getJSONArray("likedPosts"); + + assertEquals(1, likedPosts.length(), "User should have 1 liked post in their likedPosts array"); + assertEquals("It's not DNS... There's no way it's DNS... It was DNS", likedPosts.getJSONObject(0).getString("content")); + + User userWithLike = userRepository.getReferenceById(actualUserId); + assertTrue(userWithLike.getLikedPosts().size() == 1, "User should have 1 liked post in their likedPosts array"); + } + + @Test + public void tryUpdateLikedPosts_testAddingMultipleLikedPost_checkUserObjectAndResponseBody_withSingleUserIndb() throws Exception { + authenticateUser(testUser); + + ResponseEntity postResponse = this.postController.createPost(new PostRequest("It's not DNS... There's no way it's DNS... It was DNS", actualUserId)); + ResponseEntity postResponse2 = this.postController.createPost(new PostRequest("Sorry I forgot", actualUserId)); + entityManager.flush(); + entityManager.clear(); + + // Add first post + int postId = this.postRepository.findAll().get(0).getId(); + String requestBody = "{\"post_id\": " + postId + "}"; + + MvcResult result = this.mockMvc.perform(patch("/users/" + actualUserId) + .with(user(UserDetailsImpl.build(testUser))) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + // Add second post + postId = this.postRepository.findAll().get(1).getId(); + requestBody = "{\"post_id\": " + postId + "}"; + + result = this.mockMvc.perform(patch("/users/" + actualUserId) + .with(user(UserDetailsImpl.build(testUser))) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + // Actual testing logic + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + JSONObject user = jsonObject.getJSONObject("user"); + JSONArray likedPosts = user.getJSONArray("likedPosts"); + + assertEquals(2, likedPosts.length(), "User should have 1 liked post in their likedPosts array"); + assertEquals("It's not DNS... There's no way it's DNS... It was DNS", likedPosts.getJSONObject(0).getString("content")); + assertEquals("Sorry I forgot", likedPosts.getJSONObject(1).getString("content")); + + User userWithLike = userRepository.getReferenceById(actualUserId); + assertTrue(userWithLike.getLikedPosts().size() == 2, "User should have 1 liked post in their likedPosts array"); + } } From ffdac48c60f0cf48945eee8f309a18b8c41adb8f Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Mon, 22 Sep 2025 15:05:58 +0200 Subject: [PATCH 088/119] fixed refresh of tokens, and added fields to JWT token --- .gitignore | 4 +- src/main/java/com/booleanuk/Main.java | 245 ++++++++++++++++-- .../cohorts/controllers/AuthController.java | 44 ++++ .../response/RefreshTokenResponse.java | 16 ++ .../cohorts/security/WebSecurityConfig.java | 1 + .../cohorts/security/jwt/JwtUtils.java | 39 +++ .../security/services/UserDetailsImpl.java | 14 +- 7 files changed, 335 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/payload/response/RefreshTokenResponse.java diff --git a/.gitignore b/.gitignore index dc24404..fa510f7 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,6 @@ out/ application.yml build .DS_Store -application-azuread.yml \ No newline at end of file +application-azuread.yml +*.yml +Maincopy.md \ No newline at end of file diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 092e9fe..509c680 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -1,19 +1,40 @@ package com.booleanuk; -import com.booleanuk.cohorts.models.*; -import com.booleanuk.cohorts.repository.*; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; 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; +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Course; +import com.booleanuk.cohorts.models.ERole; +import com.booleanuk.cohorts.models.Post; +import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.models.Role; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.repository.CohortRepository; +import com.booleanuk.cohorts.repository.CourseRepository; +import com.booleanuk.cohorts.repository.PostRepository; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.RoleRepository; +import com.booleanuk.cohorts.repository.UserRepository; @SpringBootApplication public class Main implements CommandLineRunner { + + // DATABASE SIZE CONFIGURATION + // Set to true for LARGE database: 3 courses, 4 cohorts per course (12 total), 30 students per cohort (360 total) + // Set to false for SMALL database: 3 courses, 1 cohort per course (3 total), 10 students per cohort (30 total) + private static final boolean USE_LARGE_DATABASE = false; + @Autowired private RoleRepository roleRepository; @Autowired @@ -25,6 +46,8 @@ public class Main implements CommandLineRunner { @Autowired private PostRepository postRepository; @Autowired + private CourseRepository courseRepository; + @Autowired PasswordEncoder encoder; public static void main(String[] args) { @@ -33,31 +56,201 @@ 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()); + System.out.println("Initializing sample data..."); + + // Create roles + + Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); + Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); + + // Create courses + Course javaFundamentals = createOrGetCourse("Software Development"); + Course springBoot = createOrGetCourse("Front-End Development"); + Course reactFundamentals = createOrGetCourse("Data Analytics"); + + // Create cohorts based on configuration + List allCohorts = new ArrayList<>(); + + if (USE_LARGE_DATABASE) { + // LARGE DATABASE: 4 cohorts per course (12 total) + + // Software Development cohorts (4 cohorts) + allCohorts.add(createOrGetCohort("Software Development 2024 Q1", + javaFundamentals, LocalDate.of(2024, 1, 15), LocalDate.of(2024, 6, 15))); + allCohorts.add(createOrGetCohort("Software Development 2024 Q2", + javaFundamentals, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 9, 1))); + allCohorts.add(createOrGetCohort("Software Development 2025 Q1", + javaFundamentals, LocalDate.of(2025, 1, 15), LocalDate.of(2025, 6, 15))); + allCohorts.add(createOrGetCohort("Software Development 2025 Q2", + javaFundamentals, LocalDate.of(2025, 4, 1), LocalDate.of(2025, 9, 1))); + + // Front-End Development cohorts (4 cohorts) + allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", + springBoot, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 7, 1))); + allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", + springBoot, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 10, 1))); + allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", + springBoot, LocalDate.of(2025, 2, 1), LocalDate.of(2025, 7, 1))); + allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", + springBoot, LocalDate.of(2025, 5, 1), LocalDate.of(2025, 10, 1))); + + // Data Analytics cohorts (4 cohorts) + allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", + reactFundamentals, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 8, 1))); + allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", + reactFundamentals, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 11, 1))); + allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", + reactFundamentals, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 1))); + allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", + reactFundamentals, LocalDate.of(2025, 6, 1), LocalDate.of(2025, 11, 1))); } else { - cohort = this.cohortRepository.findById(1).orElse(null); + // SMALL DATABASE: 1 cohort per course (3 total) + allCohorts.add(createOrGetCohort("Software Development 2025", + javaFundamentals, LocalDate.of(2025, 1, 15), LocalDate.of(2025, 6, 15))); + allCohorts.add(createOrGetCohort("Front-End Development 2025", + springBoot, LocalDate.of(2025, 2, 1), LocalDate.of(2025, 7, 1))); + allCohorts.add(createOrGetCohort("Data Analytics 2025", + reactFundamentals, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 1))); } - Role teacherRole; - if (!this.roleRepository.existsByName(ERole.ROLE_TEACHER)) { - teacherRole = this.roleRepository.save(new Role(ERole.ROLE_TEACHER)); - } else { - teacherRole = this.roleRepository.findByName(ERole.ROLE_TEACHER).orElse(null); + + + // Create teacher users + User teacherJohn = createUser("t@t.com", "p", teacherRole); + if (teacherJohn.getProfile() == null) { + Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", + "https://github.com/johnsmith", "+44123456790", + "Experienced Java developer and educator with 10+ years in software development.", + teacherRole, "Java Development", allCohorts.get(0), LocalDate.of(2024, 1, 10), null, null); + profileRepository.save(johnProfile); + teacherJohn.setProfile(johnProfile); + userRepository.save(teacherJohn); } - Set teacherRoles = new HashSet<>(); - teacherRoles.add(teacherRole); - Role studentRole; - if (!this.roleRepository.existsByName(ERole.ROLE_STUDENT)) { - studentRole = this.roleRepository.save(new Role(ERole.ROLE_STUDENT)); - } else { - studentRole = this.roleRepository.findByName(ERole.ROLE_STUDENT).orElse(null); + + User teacherSarah = createUser("tt@t.com", "p", teacherRole); + if (teacherSarah.getProfile() == null) { + Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", + "https://github.com/sarahjones", "+44123456791", + "Frontend specialist with expertise in React and modern web technologies.", + teacherRole, "Frontend Development", allCohorts.get(1), LocalDate.of(2025, 1, 25), null, null); + profileRepository.save(sarahProfile); + teacherSarah.setProfile(sarahProfile); + userRepository.save(teacherSarah); + } + + // Create student users + List students = new ArrayList<>(); + String[] firstNames = { + "Alice", "Bob", "Carol", "David", "Emma", "Frank", "Grace", "Henry", + "Ivy", "Jack", "Kate", "Liam", "Maya", "Noah", "Olivia", "Paul", + "Quinn", "Ruby", "Sam", "Tina", "Uma", "Victor", "Wendy", "Xavier", + "Yara", "Zoe", "Alex", "Blake", "Chloe", "Dan", "Eva", "Felix", + "Gina", "Hugo", "Iris", "Jake", "Luna", "Max", "Nina", "Oscar" + }; + String[] lastNames = { + "Johnson", "Wilson", "Brown", "Taylor", "Davis", "Miller", "Anderson", "Thomas", + "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", + "Clark", "Rodriguez", "Lewis", "Lee", "Walker", "Hall", "Allen", "Young", + "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", + "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner" + }; + + // Create students based on configuration + int totalStudents = USE_LARGE_DATABASE ? 360 : 30; // 360 for large (30 per cohort), 30 for small (10 per cohort) + int studentsPerCohort = USE_LARGE_DATABASE ? 30 : 10; + + for (int i = 0; i < totalStudents; i++) { + String firstName = firstNames[i % firstNames.length]; + String lastName = lastNames[i % lastNames.length]; + String email = firstName.toLowerCase() + i + "@s.com"; + + User student = createUser(email, "p", studentRole); + if (student.getProfile() == null) { + // Distribute students evenly across all cohorts + Cohort assignedCohort = allCohorts.get(i / studentsPerCohort); + + Profile studentProfile = new Profile(student, firstName, lastName, + firstName.toLowerCase() + lastName.toLowerCase() + i, + "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, + "+4412345679" + String.format("%03d", i), + "Passionate about learning software development and building amazing applications.", + studentRole, "Software Development", assignedCohort, + assignedCohort.getStartDate(), assignedCohort.getEndDate(), null); + profileRepository.save(studentProfile); + student.setProfile(studentProfile); + student = userRepository.save(student); + } + students.add(student); } - Set studentRoles = new HashSet<>(); - studentRoles.add(studentRole); - if (!this.roleRepository.existsByName(ERole.ROLE_ADMIN)) { - this.roleRepository.save(new Role(ERole.ROLE_ADMIN)); + + // Create sample posts + List samplePosts = Arrays.asList( + "Just finished my first Java application! Excited to learn more about Spring Boot.", + "Working on a React project today. The component lifecycle is fascinating!", + "Had a great debugging session today. Finally understood how to use breakpoints effectively.", + "Group project is going well. Collaboration through Git is becoming second nature.", + "Completed the database design exercise. Understanding relationships is key!", + "Learned about REST APIs today. Can't wait to build my own!", + "Struggling with CSS Grid but making progress. Practice makes perfect!", + "Just deployed my first application to the cloud. Such a great feeling!", + "Code review session was very helpful. Learning from others is invaluable.", + "Working on the final project. Bringing everything together is challenging but rewarding." + ); + + // Create posts from different users (only if no posts exist) + if (postRepository.count() == 0) { + for (int i = 0; i < samplePosts.size(); i++) { + User author = (i < 2) ? (i == 0 ? teacherJohn : teacherSarah) : students.get(i % students.size()); + Post post = new Post(author, samplePosts.get(i)); + post.setLikes((int) (Math.random() * 10)); // Random likes 0-9 + postRepository.save(post); + } } + + System.out.println("Sample data initialization completed!"); + System.out.println("Created:"); + System.out.println("- 3 roles (Admin, Teacher, Student)"); + System.out.println("- 3 courses"); + System.out.println("- 12 cohorts (4 per course)"); + System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); + System.out.println("- 30 students per cohort across 12 cohorts"); + System.out.println("- Software Development: 4 cohorts (120 students)"); + System.out.println("- Front-End Development: 4 cohorts (120 students)"); + System.out.println("- Data Analytics: 4 cohorts (120 students)"); + System.out.println("- " + samplePosts.size() + " sample posts"); + } + + private Role createOrGetRole(ERole roleName) { + return roleRepository.findByName(roleName) + .orElseGet(() -> roleRepository.save(new Role(roleName))); + } + + private User createUser(String email, String password, Role role) { + // Check if user already exists + return userRepository.findByEmail(email) + .orElseGet(() -> { + User user = new User(email, encoder.encode(password)); + Set roles = new HashSet<>(); + roles.add(role); + user.setRoles(roles); + return userRepository.save(user); + }); + } + + private Course createOrGetCourse(String name) { + return courseRepository.findAll().stream() + .filter(course -> course.getName().equals(name)) + .findFirst() + .orElseGet(() -> courseRepository.save(new Course(name))); + } + + private Cohort createOrGetCohort(String name, Course course, LocalDate startDate, LocalDate endDate) { + return cohortRepository.findAll().stream() + .filter(cohort -> cohort.getName().equals(name)) + .findFirst() + .orElseGet(() -> { + Cohort cohort = new Cohort(name, startDate, endDate); + cohort.setCourse(course); + return cohortRepository.save(cohort); + }); } } diff --git a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java index f18f0fb..52951fd 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java @@ -18,6 +18,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import jakarta.servlet.http.HttpServletRequest; + import com.booleanuk.cohorts.models.ERole; import com.booleanuk.cohorts.models.Role; import com.booleanuk.cohorts.models.User; @@ -25,6 +27,7 @@ import com.booleanuk.cohorts.payload.request.SignupRequest; import com.booleanuk.cohorts.payload.response.JwtResponse; import com.booleanuk.cohorts.payload.response.MessageResponse; +import com.booleanuk.cohorts.payload.response.RefreshTokenResponse; import com.booleanuk.cohorts.payload.response.TokenResponse; import com.booleanuk.cohorts.repository.RoleRepository; import com.booleanuk.cohorts.repository.UserRepository; @@ -117,4 +120,45 @@ public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signupRe userRepository.save(user); return ResponseEntity.ok((new MessageResponse("User registered successfully"))); } + + @PostMapping("/auth/refresh") + public ResponseEntity refreshToken(HttpServletRequest request) { + try { + // 1. Extract token from Authorization header + String headerAuth = request.getHeader("Authorization"); + if (headerAuth == null || !headerAuth.startsWith("Bearer ")) { + return ResponseEntity.badRequest().body(new MessageResponse("Authorization header missing or invalid")); + } + + String token = headerAuth.substring(7); + + // 2. Validate the token (allowing expired tokens for refresh) + if (!jwtUtils.validateJwtTokenForRefresh(token)) { + return ResponseEntity.badRequest().body(new MessageResponse("Invalid token")); + } + + // 3. Decode token to get user email (handling expired tokens) + String userEmail = jwtUtils.getUserNameFromExpiredJwtToken(token); + + // 4. Fetch updated user from database + User user = userRepository.findByEmailWithProfile(userEmail).orElse(null); + if (user == null) { + return ResponseEntity.badRequest().body(new MessageResponse("User not found")); + } + + // 5. Create new authentication object with fresh user data + UserDetailsImpl userDetails = UserDetailsImpl.build(user); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + + // 6. Generate new token with fresh user data + String newJwt = jwtUtils.generateJwtToken(authentication); + + // 7. Return new token in response + return ResponseEntity.ok(new RefreshTokenResponse(newJwt, "Token refreshed successfully")); + + } catch (Exception e) { + return ResponseEntity.badRequest().body(new MessageResponse("Error refreshing token: " + e.getMessage())); + } + } } diff --git a/src/main/java/com/booleanuk/cohorts/payload/response/RefreshTokenResponse.java b/src/main/java/com/booleanuk/cohorts/payload/response/RefreshTokenResponse.java new file mode 100644 index 0000000..182542c --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/response/RefreshTokenResponse.java @@ -0,0 +1,16 @@ +package com.booleanuk.cohorts.payload.response; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RefreshTokenResponse { + private String token; + private String message; + + public RefreshTokenResponse(String token, String message) { + this.token = token; + this.message = message; + } +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java b/src/main/java/com/booleanuk/cohorts/security/WebSecurityConfig.java index 3c97ac6..4ceef15 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("/auth/refresh", "/auth/refresh/**").permitAll() .requestMatchers("/profiles", "/profiles/**").authenticated() .requestMatchers("/students", "/students/**").authenticated() .requestMatchers("/notes", "/notes/**").authenticated() 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 159bbe2..e113128 100644 --- a/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java +++ b/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java @@ -38,6 +38,8 @@ public String generateJwtToken(Authentication authentication) { .claim("firstName", userPrincipal.getFirstName()) .claim("lastName", userPrincipal.getLastName()) .claim("roleId", userPrincipal.getRoleId()) + .claim("specialism", userPrincipal.getSpecialism()) + .claim("cohortId", userPrincipal.getCohortId()) .issuedAt(new Date()) .expiration(new Date((new Date()).getTime() + this.jwtExpirationMs)) .signWith(this.key()) @@ -68,6 +70,14 @@ public Integer getRoleIdFromJwtToken(String token) { return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("roleId", Integer.class); } + public String getSpecialismFromJwtToken(String token) { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("specialism", String.class); + } + + public Integer getCohortIdFromJwtToken(String token) { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("cohortId", Integer.class); + } + public boolean validateJwtToken(String authToken) { try { Jwts.parser().verifyWith(this.key()).build().parse(authToken); @@ -83,5 +93,34 @@ public boolean validateJwtToken(String authToken) { } return false; } + + public boolean validateJwtTokenForRefresh(String authToken) { + try { + Jwts.parser().verifyWith(this.key()).build().parse(authToken); + return true; + } catch (MalformedJwtException e) { + logger.error("Invalid JWT token: {}", e.getMessage()); + return false; + } catch (ExpiredJwtException e) { + // For refresh, we allow expired tokens to be processed + logger.info("JWT token has expired but allowing for refresh: {}", e.getMessage()); + return true; + } catch (UnsupportedJwtException e) { + logger.error("JWT token is unsupported: {}", e.getMessage()); + return false; + } catch (IllegalArgumentException e) { + logger.error("JWT claims string is empty: {}", e.getMessage()); + return false; + } + } + + public String getUserNameFromExpiredJwtToken(String token) { + try { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().getSubject(); + } catch (ExpiredJwtException e) { + // Extract username from expired token + return e.getClaims().getSubject(); + } + } } 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 407dd70..4a7e3a2 100644 --- a/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java +++ b/src/main/java/com/booleanuk/cohorts/security/services/UserDetailsImpl.java @@ -24,13 +24,15 @@ public class UserDetailsImpl implements UserDetails { private final String firstName; private final String lastName; private final Integer roleId; + private final String specialism; + private final Integer cohortId; @JsonIgnore private final String password; private final Collection authorities; - public UserDetailsImpl(int id, String username, String email, String password, String firstName, String lastName, Integer roleId, Collection authorities) { + public UserDetailsImpl(int id, String username, String email, String password, String firstName, String lastName, Integer roleId, String specialism, Integer cohortId, Collection authorities) { this.id = id; this.username = email; this.email = email; @@ -38,6 +40,8 @@ public UserDetailsImpl(int id, String username, String email, String password, S this.firstName = firstName; this.lastName = lastName; this.roleId = roleId; + this.specialism = specialism; + this.cohortId = cohortId; this.authorities = authorities; } @@ -49,10 +53,16 @@ public static UserDetailsImpl build(User user) { // Get firstName and lastName from profile, with fallbacks if profile is null String firstName = ""; String lastName = ""; + String specialism = ""; + Integer cohortId = null; if (user.getProfile() != null) { firstName = user.getProfile().getFirstName() != null ? user.getProfile().getFirstName() : ""; lastName = user.getProfile().getLastName() != null ? user.getProfile().getLastName() : ""; + specialism = user.getProfile().getSpecialism() != null ? user.getProfile().getSpecialism() : ""; + if (user.getProfile().getCohort() != null) { + cohortId = user.getProfile().getCohort().getId(); + } System.out.println("Profile found for user " + user.getEmail() + ": " + firstName + " " + lastName); } else { System.out.println("No profile found for user " + user.getEmail()); @@ -72,6 +82,8 @@ public static UserDetailsImpl build(User user) { firstName, lastName, roleId, + specialism, + cohortId, authorities); } From 574d1c68678df9d7562af48a58cf274824e24cf5 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Mon, 22 Sep 2025 15:31:20 +0200 Subject: [PATCH 089/119] fix bug for merge --- 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 a2dcca8..9151b82 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -77,7 +77,7 @@ public class Profile { @JsonIgnoreProperties("cohort") private Role role; - @Column(length = 25_000) + @Column(length = 25000000) private String photo; public Profile(int id) { From 73c1f7c8ad45fa68831511652786ed5e9a70535b Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Mon, 22 Sep 2025 15:36:15 +0200 Subject: [PATCH 090/119] merge bug --- 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 9151b82..75cf924 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -77,7 +77,7 @@ public class Profile { @JsonIgnoreProperties("cohort") private Role role; - @Column(length = 25000000) + @Column(length = 2500000) private String photo; public Profile(int id) { From f9aa8cbdee50d19c8357321ec9e24633c69a9b5c Mon Sep 17 00:00:00 2001 From: Emanuels <47167383+emazau@users.noreply.github.com> Date: Mon, 22 Sep 2025 15:40:30 +0200 Subject: [PATCH 091/119] Delete .github/workflows directory --- .github/workflows/gradle.yml | 43 ------------------------------------ 1 file changed, 43 deletions(-) delete mode 100644 .github/workflows/gradle.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 3aecabc..0000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,43 +0,0 @@ -# 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 --exclude-task test - - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: Package - path: build/libs From ae52ba1b767407619243d825edf3aff808358a30 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Mon, 22 Sep 2025 15:40:55 +0200 Subject: [PATCH 092/119] please work --- 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 75cf924..b9c6906 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Profile.java +++ b/src/main/java/com/booleanuk/cohorts/models/Profile.java @@ -77,7 +77,7 @@ public class Profile { @JsonIgnoreProperties("cohort") private Role role; - @Column(length = 2500000) + @Column(length = 2500000) private String photo; public Profile(int id) { From 3f6edc2a0c7963143c99570c84202924b493ac73 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Mon, 22 Sep 2025 15:41:18 +0200 Subject: [PATCH 093/119] Merge branch 'main' of https://github.com/boolean-uk/java-team-dev-server-2508-team-1 fix merge --- src/main/java/com/booleanuk/Main.java | 85 ++++++++++++--------------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 509c680..34e80cf 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -1,6 +1,5 @@ package com.booleanuk; -import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -75,42 +74,27 @@ public void run(String... args) { // LARGE DATABASE: 4 cohorts per course (12 total) // Software Development cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Software Development 2024 Q1", - javaFundamentals, LocalDate.of(2024, 1, 15), LocalDate.of(2024, 6, 15))); - allCohorts.add(createOrGetCohort("Software Development 2024 Q2", - javaFundamentals, LocalDate.of(2024, 4, 1), LocalDate.of(2024, 9, 1))); - allCohorts.add(createOrGetCohort("Software Development 2025 Q1", - javaFundamentals, LocalDate.of(2025, 1, 15), LocalDate.of(2025, 6, 15))); - allCohorts.add(createOrGetCohort("Software Development 2025 Q2", - javaFundamentals, LocalDate.of(2025, 4, 1), LocalDate.of(2025, 9, 1))); + allCohorts.add(createOrGetCohort("Software Development 2024 Q1", javaFundamentals)); + allCohorts.add(createOrGetCohort("Software Development 2024 Q2", javaFundamentals)); + allCohorts.add(createOrGetCohort("Software Development 2025 Q1", javaFundamentals)); + allCohorts.add(createOrGetCohort("Software Development 2025 Q2", javaFundamentals)); // Front-End Development cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", - springBoot, LocalDate.of(2024, 2, 1), LocalDate.of(2024, 7, 1))); - allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", - springBoot, LocalDate.of(2024, 5, 1), LocalDate.of(2024, 10, 1))); - allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", - springBoot, LocalDate.of(2025, 2, 1), LocalDate.of(2025, 7, 1))); - allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", - springBoot, LocalDate.of(2025, 5, 1), LocalDate.of(2025, 10, 1))); + allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", springBoot)); + allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", springBoot)); + allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", springBoot)); + allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", springBoot)); // Data Analytics cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", - reactFundamentals, LocalDate.of(2024, 3, 1), LocalDate.of(2024, 8, 1))); - allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", - reactFundamentals, LocalDate.of(2024, 6, 1), LocalDate.of(2024, 11, 1))); - allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", - reactFundamentals, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 1))); - allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", - reactFundamentals, LocalDate.of(2025, 6, 1), LocalDate.of(2025, 11, 1))); + allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", reactFundamentals)); + allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", reactFundamentals)); + allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", reactFundamentals)); + allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", reactFundamentals)); } else { // SMALL DATABASE: 1 cohort per course (3 total) - allCohorts.add(createOrGetCohort("Software Development 2025", - javaFundamentals, LocalDate.of(2025, 1, 15), LocalDate.of(2025, 6, 15))); - allCohorts.add(createOrGetCohort("Front-End Development 2025", - springBoot, LocalDate.of(2025, 2, 1), LocalDate.of(2025, 7, 1))); - allCohorts.add(createOrGetCohort("Data Analytics 2025", - reactFundamentals, LocalDate.of(2025, 3, 1), LocalDate.of(2025, 8, 1))); + allCohorts.add(createOrGetCohort("Software Development 2025", javaFundamentals)); + allCohorts.add(createOrGetCohort("Front-End Development 2025", springBoot)); + allCohorts.add(createOrGetCohort("Data Analytics 2025", reactFundamentals)); } @@ -120,7 +104,7 @@ public void run(String... args) { Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", "https://github.com/johnsmith", "+44123456790", "Experienced Java developer and educator with 10+ years in software development.", - teacherRole, "Java Development", allCohorts.get(0), LocalDate.of(2024, 1, 10), null, null); + teacherRole, "Java Development", allCohorts.get(0), null); profileRepository.save(johnProfile); teacherJohn.setProfile(johnProfile); userRepository.save(teacherJohn); @@ -131,7 +115,7 @@ public void run(String... args) { Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", "https://github.com/sarahjones", "+44123456791", "Frontend specialist with expertise in React and modern web technologies.", - teacherRole, "Frontend Development", allCohorts.get(1), LocalDate.of(2025, 1, 25), null, null); + teacherRole, "Frontend Development", allCohorts.get(1), null); profileRepository.save(sarahProfile); teacherSarah.setProfile(sarahProfile); userRepository.save(teacherSarah); @@ -173,8 +157,7 @@ public void run(String... args) { "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, "+4412345679" + String.format("%03d", i), "Passionate about learning software development and building amazing applications.", - studentRole, "Software Development", assignedCohort, - assignedCohort.getStartDate(), assignedCohort.getEndDate(), null); + studentRole, "Software Development", assignedCohort, null); profileRepository.save(studentProfile); student.setProfile(studentProfile); student = userRepository.save(student); @@ -208,14 +191,25 @@ public void run(String... args) { System.out.println("Sample data initialization completed!"); System.out.println("Created:"); - System.out.println("- 3 roles (Admin, Teacher, Student)"); - System.out.println("- 3 courses"); - System.out.println("- 12 cohorts (4 per course)"); - System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); - System.out.println("- 30 students per cohort across 12 cohorts"); - System.out.println("- Software Development: 4 cohorts (120 students)"); - System.out.println("- Front-End Development: 4 cohorts (120 students)"); - System.out.println("- Data Analytics: 4 cohorts (120 students)"); + System.out.println("- 2 roles (Teacher, Student)"); + System.out.println("- 3 courses (Software Development, Front-End Development, Data Analytics)"); + + if (USE_LARGE_DATABASE) { + System.out.println("- 12 cohorts (4 per course)"); + System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); + System.out.println("- 30 students per cohort across 12 cohorts (360 total students)"); + System.out.println(" - Software Development: 4 cohorts (120 students)"); + System.out.println(" - Front-End Development: 4 cohorts (120 students)"); + System.out.println(" - Data Analytics: 4 cohorts (120 students)"); + } else { + System.out.println("- 3 cohorts (1 per course)"); + System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); + System.out.println("- 10 students per cohort across 3 cohorts (30 total students)"); + System.out.println(" - Software Development: 1 cohort (10 students)"); + System.out.println(" - Front-End Development: 1 cohort (10 students)"); + System.out.println(" - Data Analytics: 1 cohort (10 students)"); + } + System.out.println("- " + samplePosts.size() + " sample posts"); } @@ -243,13 +237,12 @@ private Course createOrGetCourse(String name) { .orElseGet(() -> courseRepository.save(new Course(name))); } - private Cohort createOrGetCohort(String name, Course course, LocalDate startDate, LocalDate endDate) { + private Cohort createOrGetCohort(String name, Course course) { return cohortRepository.findAll().stream() .filter(cohort -> cohort.getName().equals(name)) .findFirst() .orElseGet(() -> { - Cohort cohort = new Cohort(name, startDate, endDate); - cohort.setCourse(course); + Cohort cohort = new Cohort(name, course); return cohortRepository.save(cohort); }); } From 02fa1e0e78923ab0a1b95a66e53ae4796d0adf55 Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Tue, 23 Sep 2025 09:16:15 +0200 Subject: [PATCH 094/119] fixed enddate and stratdate --- src/main/java/com/booleanuk/Main.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 34e80cf..9de1e2a 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -1,5 +1,6 @@ package com.booleanuk; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -62,10 +63,13 @@ public void run(String... args) { Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); - // Create courses - Course javaFundamentals = createOrGetCourse("Software Development"); - Course springBoot = createOrGetCourse("Front-End Development"); - Course reactFundamentals = createOrGetCourse("Data Analytics"); + // Create courses with start and end dates + Course javaFundamentals = createOrGetCourse("Software Development", + LocalDate.of(2024, 1, 15), LocalDate.of(2024, 7, 15)); + Course springBoot = createOrGetCourse("Front-End Development", + LocalDate.of(2024, 2, 1), LocalDate.of(2024, 8, 1)); + Course reactFundamentals = createOrGetCourse("Data Analytics", + LocalDate.of(2024, 3, 1), LocalDate.of(2024, 9, 1)); // Create cohorts based on configuration List allCohorts = new ArrayList<>(); @@ -192,7 +196,7 @@ public void run(String... args) { System.out.println("Sample data initialization completed!"); System.out.println("Created:"); System.out.println("- 2 roles (Teacher, Student)"); - System.out.println("- 3 courses (Software Development, Front-End Development, Data Analytics)"); + System.out.println("- 3 courses with date ranges (Software Development: Jan-Jul 2024, Front-End Development: Feb-Aug 2024, Data Analytics: Mar-Sep 2024)"); if (USE_LARGE_DATABASE) { System.out.println("- 12 cohorts (4 per course)"); @@ -230,11 +234,11 @@ private User createUser(String email, String password, Role role) { }); } - private Course createOrGetCourse(String name) { + private Course createOrGetCourse(String name, LocalDate startDate, LocalDate endDate) { return courseRepository.findAll().stream() .filter(course -> course.getName().equals(name)) .findFirst() - .orElseGet(() -> courseRepository.save(new Course(name))); + .orElseGet(() -> courseRepository.save(new Course(name, startDate, endDate, null))); } private Cohort createOrGetCohort(String name, Course course) { From 34e848efc134392bdc7cecf67348c30458d28f44 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Tue, 23 Sep 2025 12:59:13 +0200 Subject: [PATCH 095/119] Fixed delete logic for users --- src/main/java/com/booleanuk/Main.java | 70 +++++++++---------- .../cohorts/controllers/UserController.java | 6 ++ .../com/booleanuk/cohorts/models/Post.java | 19 ++--- .../com/booleanuk/cohorts/models/User.java | 2 +- .../cohorts/payload/request/PostRequest.java | 1 - .../cohorts/repository/UserRepository.java | 1 + 6 files changed, 49 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 34e80cf..517d786 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -28,12 +28,12 @@ @SpringBootApplication public class Main implements CommandLineRunner { - + // DATABASE SIZE CONFIGURATION // Set to true for LARGE database: 3 courses, 4 cohorts per course (12 total), 30 students per cohort (360 total) // Set to false for SMALL database: 3 courses, 1 cohort per course (3 total), 10 students per cohort (30 total) private static final boolean USE_LARGE_DATABASE = false; - + @Autowired private RoleRepository roleRepository; @Autowired @@ -56,23 +56,23 @@ public static void main(String[] args) { @Override public void run(String... args) { System.out.println("Initializing sample data..."); - + // Create roles - + Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); - + // Create courses Course javaFundamentals = createOrGetCourse("Software Development"); Course springBoot = createOrGetCourse("Front-End Development"); Course reactFundamentals = createOrGetCourse("Data Analytics"); - + // Create cohorts based on configuration List allCohorts = new ArrayList<>(); - + if (USE_LARGE_DATABASE) { // LARGE DATABASE: 4 cohorts per course (12 total) - + // Software Development cohorts (4 cohorts) allCohorts.add(createOrGetCohort("Software Development 2024 Q1", javaFundamentals)); allCohorts.add(createOrGetCohort("Software Development 2024 Q2", javaFundamentals)); @@ -97,30 +97,30 @@ public void run(String... args) { allCohorts.add(createOrGetCohort("Data Analytics 2025", reactFundamentals)); } - + // Create teacher users User teacherJohn = createUser("t@t.com", "p", teacherRole); if (teacherJohn.getProfile() == null) { - Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", - "https://github.com/johnsmith", "+44123456790", - "Experienced Java developer and educator with 10+ years in software development.", + Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", + "https://github.com/johnsmith", "+44123456790", + "Experienced Java developer and educator with 10+ years in software development.", teacherRole, "Java Development", allCohorts.get(0), null); profileRepository.save(johnProfile); teacherJohn.setProfile(johnProfile); userRepository.save(teacherJohn); } - + User teacherSarah = createUser("tt@t.com", "p", teacherRole); if (teacherSarah.getProfile() == null) { - Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", - "https://github.com/sarahjones", "+44123456791", - "Frontend specialist with expertise in React and modern web technologies.", + Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", + "https://github.com/sarahjones", "+44123456791", + "Frontend specialist with expertise in React and modern web technologies.", teacherRole, "Frontend Development", allCohorts.get(1), null); profileRepository.save(sarahProfile); teacherSarah.setProfile(sarahProfile); userRepository.save(teacherSarah); } - + // Create student users List students = new ArrayList<>(); String[] firstNames = { @@ -137,11 +137,11 @@ public void run(String... args) { "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner" }; - + // Create students based on configuration int totalStudents = USE_LARGE_DATABASE ? 360 : 30; // 360 for large (30 per cohort), 30 for small (10 per cohort) int studentsPerCohort = USE_LARGE_DATABASE ? 30 : 10; - + for (int i = 0; i < totalStudents; i++) { String firstName = firstNames[i % firstNames.length]; String lastName = lastNames[i % lastNames.length]; @@ -151,12 +151,12 @@ public void run(String... args) { if (student.getProfile() == null) { // Distribute students evenly across all cohorts Cohort assignedCohort = allCohorts.get(i / studentsPerCohort); - - Profile studentProfile = new Profile(student, firstName, lastName, - firstName.toLowerCase() + lastName.toLowerCase() + i, - "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, - "+4412345679" + String.format("%03d", i), - "Passionate about learning software development and building amazing applications.", + + Profile studentProfile = new Profile(student, firstName, lastName, + firstName.toLowerCase() + lastName.toLowerCase() + i, + "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, + "+4412345679" + String.format("%03d", i), + "Passionate about learning software development and building amazing applications.", studentRole, "Software Development", assignedCohort, null); profileRepository.save(studentProfile); student.setProfile(studentProfile); @@ -164,7 +164,7 @@ public void run(String... args) { } students.add(student); } - + // Create sample posts List samplePosts = Arrays.asList( "Just finished my first Java application! Excited to learn more about Spring Boot.", @@ -178,7 +178,7 @@ public void run(String... args) { "Code review session was very helpful. Learning from others is invaluable.", "Working on the final project. Bringing everything together is challenging but rewarding." ); - + // Create posts from different users (only if no posts exist) if (postRepository.count() == 0) { for (int i = 0; i < samplePosts.size(); i++) { @@ -188,12 +188,12 @@ public void run(String... args) { postRepository.save(post); } } - + System.out.println("Sample data initialization completed!"); System.out.println("Created:"); System.out.println("- 2 roles (Teacher, Student)"); System.out.println("- 3 courses (Software Development, Front-End Development, Data Analytics)"); - + if (USE_LARGE_DATABASE) { System.out.println("- 12 cohorts (4 per course)"); System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); @@ -209,15 +209,15 @@ public void run(String... args) { System.out.println(" - Front-End Development: 1 cohort (10 students)"); System.out.println(" - Data Analytics: 1 cohort (10 students)"); } - + System.out.println("- " + samplePosts.size() + " sample posts"); } - + private Role createOrGetRole(ERole roleName) { return roleRepository.findByName(roleName) .orElseGet(() -> roleRepository.save(new Role(roleName))); } - + private User createUser(String email, String password, Role role) { // Check if user already exists return userRepository.findByEmail(email) @@ -229,14 +229,14 @@ private User createUser(String email, String password, Role role) { return userRepository.save(user); }); } - + private Course createOrGetCourse(String name) { return courseRepository.findAll().stream() .filter(course -> course.getName().equals(name)) .findFirst() .orElseGet(() -> courseRepository.save(new Course(name))); } - + private Cohort createOrGetCohort(String name, Course course) { return cohortRepository.findAll().stream() .filter(cohort -> cohort.getName().equals(name)) @@ -246,4 +246,4 @@ private Cohort createOrGetCohort(String name, Course course) { return cohortRepository.save(cohort); }); } -} +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index b65e317..7dba973 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Set; +import jakarta.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -76,9 +77,14 @@ public ResponseEntity deleteUser(@PathVariable int id) { error.set("not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } + for (Post post : user.getLikedPosts()){ + post.getLikedByUsers().remove(user); + } user.getRoles().clear(); + user.getLikedPosts().clear(); UserResponse userResponse = new UserResponse(); userResponse.set(user); + try { userRepository.delete(user); return ResponseEntity.ok(userResponse); diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index b2606ff..e7a81b8 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -1,22 +1,12 @@ package com.booleanuk.cohorts.models; import java.time.OffsetDateTime; +import java.util.HashSet; import java.util.List; +import java.util.Set; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -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.PrePersist; -import jakarta.persistence.Table; -import jakarta.persistence.Transient; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; @@ -45,6 +35,9 @@ public class Post { @Column(name = "time_updated", nullable = false, columnDefinition = "TIMESTAMP WITH TIME ZONE") private OffsetDateTime timeUpdated; + @ManyToMany(mappedBy = "likedPosts") + private Set likedByUsers = new HashSet<>(); + @ManyToOne @JoinColumn(name = "user_id", nullable = false) @JsonIgnoreProperties(value = {"posts", "comments", "cohort", "roles", "likedPosts"}) diff --git a/src/main/java/com/booleanuk/cohorts/models/User.java b/src/main/java/com/booleanuk/cohorts/models/User.java index 6f8e729..1652dbb 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 { @JsonIncludeProperties({"id", "content", "likes", "timeCreated", "timeUpdated" }) private List posts; - @ManyToMany + @ManyToMany(cascade = CascadeType.ALL) @JoinTable(name = "user_liked_posts", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "post_id")) 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 e237361..3e976eb 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/PostRequest.java @@ -18,5 +18,4 @@ public PostRequest(String content, int userId) { this.content = content; this.userId = userId; } - } diff --git a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java index e48c3e9..1bc7024 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java @@ -3,6 +3,7 @@ import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; From ebf749380abd441759eb4cb244f834e90cd09af3 Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Tue, 23 Sep 2025 13:03:51 +0200 Subject: [PATCH 096/119] small change in StudentController --- .../com/booleanuk/cohorts/controllers/StudentController.java | 2 +- 1 file changed, 1 insertion(+), 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 565f8cd..cba3fb4 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -102,7 +102,7 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen profile.setMobile(studentRequest.getMobile()); } - if (studentRequest.getPassword() != null) { + if (studentRequest.getPassword() != null && !studentRequest.getPassword().isBlank()) { user.setPassword(encoder.encode(studentRequest.getPassword())); } From 65c326fd6bb94197834206968ee79ec50ecfaa67 Mon Sep 17 00:00:00 2001 From: Moueed Ali Date: Tue, 23 Sep 2025 13:38:08 +0200 Subject: [PATCH 097/119] added PATCH in TeacherController --- .../controllers/TeacherController.java | 95 +++++++++++++------ .../payload/request/StudentRequest.java | 1 - .../payload/request/TeacherRequest.java | 22 +++++ 3 files changed, 90 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/booleanuk/cohorts/payload/request/TeacherRequest.java diff --git a/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java b/src/main/java/com/booleanuk/cohorts/controllers/TeacherController.java index 3f68ed1..abb9fe1 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.request.TeacherRequest; import com.booleanuk.cohorts.payload.response.ProfileListResponse; import com.booleanuk.cohorts.payload.response.Response; import com.booleanuk.cohorts.repository.CohortRepository; @@ -38,33 +39,6 @@ public class TeacherController { @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); - - - return new ResponseEntity<>(profileRepository.save(profile),HttpStatus.OK); - } @GetMapping public ResponseEntity getAllTeachers(){ @@ -111,5 +85,72 @@ public ResponseEntity getTeachersByCohortId(@PathVariable int id){ return ResponseEntity.ok(teacherListResponse); } + @PatchMapping("{id}") + public ResponseEntity updateTeacher(@PathVariable int id, @RequestBody TeacherRequest teacherRequest) { + + 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_STUDENT")) { + return new ResponseEntity<>("Only users with the TEACHER role can be viewed.", HttpStatus.BAD_REQUEST); + } + + if (teacherRequest.getPhoto() != null) { + profile.setPhoto(teacherRequest.getPhoto()); + } + + if (teacherRequest.getFirst_name() != null) { + profile.setFirstName(teacherRequest.getFirst_name()); + } + + if (teacherRequest.getLast_name() != null) { + profile.setLastName(teacherRequest.getLast_name()); + } + + if (teacherRequest.getUsername() != null) { + profile.setUsername(teacherRequest.getUsername()); + } + + if (teacherRequest.getGithub_username() != null) { + profile.setGithubUrl(teacherRequest.getGithub_username()); + } + + if (teacherRequest.getEmail() != null) { + boolean emailExists = userRepository.existsByEmail(teacherRequest.getEmail()); + if (emailExists && !teacherRequest.getEmail().equals(user.getEmail())){ + return new ResponseEntity<>("Email is already in use", HttpStatus.BAD_REQUEST); + } + user.setEmail(teacherRequest.getEmail()); + } + + if (teacherRequest.getMobile() != null) { + profile.setMobile(teacherRequest.getMobile()); + } + + if (teacherRequest.getPassword() != null && !teacherRequest.getPassword().isBlank()) { + user.setPassword(encoder.encode(teacherRequest.getPassword())); + } + + if (teacherRequest.getBio() != null) { + profile.setBio(teacherRequest.getBio()); + } + + profileRepository.save(profile); + + 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); + } + } } 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 ba75e43..26baeae 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java @@ -19,6 +19,5 @@ public class StudentRequest { public StudentRequest() { } - } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/TeacherRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/TeacherRequest.java new file mode 100644 index 0000000..c5a3ac3 --- /dev/null +++ b/src/main/java/com/booleanuk/cohorts/payload/request/TeacherRequest.java @@ -0,0 +1,22 @@ +package com.booleanuk.cohorts.payload.request; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class TeacherRequest { + + 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 TeacherRequest() { + } +} From 6379ab288e734d0ba5ed12142493fd2ac13d9aac Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Tue, 23 Sep 2025 15:50:19 +0200 Subject: [PATCH 098/119] fixed deletion in user/posts --- .../cohorts/controllers/UserController.java | 16 +++++++++------- .../java/com/booleanuk/cohorts/models/Post.java | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index 7dba973..498cdf5 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 java.util.HashSet; import java.util.List; import java.util.Set; @@ -69,27 +70,28 @@ 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){ + if (user == null) { ErrorResponse error = new ErrorResponse(); error.set("not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } - for (Post post : user.getLikedPosts()){ - post.getLikedByUsers().remove(user); + + for (Post likedPost : new HashSet<>(user.getLikedPosts())) { + likedPost.getLikedByUsers().remove(user); } - user.getRoles().clear(); user.getLikedPosts().clear(); + UserResponse userResponse = new UserResponse(); userResponse.set(user); try { userRepository.delete(user); return ResponseEntity.ok(userResponse); - } catch (Exception e){ - return new ResponseEntity<>("Could not delete user", HttpStatus.BAD_REQUEST); + } catch (Exception e) { + return new ResponseEntity<>("Could not delete user: " + e.getMessage(), HttpStatus.BAD_REQUEST); } } diff --git a/src/main/java/com/booleanuk/cohorts/models/Post.java b/src/main/java/com/booleanuk/cohorts/models/Post.java index e7a81b8..97f94c5 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Post.java +++ b/src/main/java/com/booleanuk/cohorts/models/Post.java @@ -43,7 +43,7 @@ public class Post { @JsonIgnoreProperties(value = {"posts", "comments", "cohort", "roles", "likedPosts"}) private User user; - @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @JsonIgnoreProperties("post") private List comments; From 0980390ede918e2f1858261d473d0933952a0a9f Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Tue, 23 Sep 2025 16:00:27 +0200 Subject: [PATCH 099/119] fixed delete --- src/main/java/com/booleanuk/Main.java | 402 +++++++++--------- .../cohorts/controllers/UserController.java | 13 +- 2 files changed, 213 insertions(+), 202 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 68a4271..e8aad16 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -56,204 +56,204 @@ public static void main(String[] args) { @Override public void run(String... args) { - System.out.println("Initializing sample data..."); - - // Create roles - - Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); - Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); - - - // Create courses with start and end dates - Course javaFundamentals = createOrGetCourse("Software Development", - LocalDate.of(2024, 1, 15), LocalDate.of(2024, 7, 15)); - Course springBoot = createOrGetCourse("Front-End Development", - LocalDate.of(2024, 2, 1), LocalDate.of(2024, 8, 1)); - Course reactFundamentals = createOrGetCourse("Data Analytics", - LocalDate.of(2024, 3, 1), LocalDate.of(2024, 9, 1)); - - - // Create cohorts based on configuration - List allCohorts = new ArrayList<>(); - - if (USE_LARGE_DATABASE) { - // LARGE DATABASE: 4 cohorts per course (12 total) - - // Software Development cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Software Development 2024 Q1", javaFundamentals)); - allCohorts.add(createOrGetCohort("Software Development 2024 Q2", javaFundamentals)); - allCohorts.add(createOrGetCohort("Software Development 2025 Q1", javaFundamentals)); - allCohorts.add(createOrGetCohort("Software Development 2025 Q2", javaFundamentals)); - - // Front-End Development cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", springBoot)); - allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", springBoot)); - allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", springBoot)); - allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", springBoot)); - - // Data Analytics cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", reactFundamentals)); - allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", reactFundamentals)); - allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", reactFundamentals)); - allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", reactFundamentals)); - } else { - // SMALL DATABASE: 1 cohort per course (3 total) - allCohorts.add(createOrGetCohort("Software Development 2025", javaFundamentals)); - allCohorts.add(createOrGetCohort("Front-End Development 2025", springBoot)); - allCohorts.add(createOrGetCohort("Data Analytics 2025", reactFundamentals)); - } - - - // Create teacher users - User teacherJohn = createUser("t@t.com", "p", teacherRole); - if (teacherJohn.getProfile() == null) { - Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", - "https://github.com/johnsmith", "+44123456790", - "Experienced Java developer and educator with 10+ years in software development.", - teacherRole, "Java Development", allCohorts.get(0), null); - profileRepository.save(johnProfile); - teacherJohn.setProfile(johnProfile); - userRepository.save(teacherJohn); - } - - User teacherSarah = createUser("tt@t.com", "p", teacherRole); - if (teacherSarah.getProfile() == null) { - Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", - "https://github.com/sarahjones", "+44123456791", - "Frontend specialist with expertise in React and modern web technologies.", - teacherRole, "Frontend Development", allCohorts.get(1), null); - profileRepository.save(sarahProfile); - teacherSarah.setProfile(sarahProfile); - userRepository.save(teacherSarah); - } - - // Create student users - List students = new ArrayList<>(); - String[] firstNames = { - "Alice", "Bob", "Carol", "David", "Emma", "Frank", "Grace", "Henry", - "Ivy", "Jack", "Kate", "Liam", "Maya", "Noah", "Olivia", "Paul", - "Quinn", "Ruby", "Sam", "Tina", "Uma", "Victor", "Wendy", "Xavier", - "Yara", "Zoe", "Alex", "Blake", "Chloe", "Dan", "Eva", "Felix", - "Gina", "Hugo", "Iris", "Jake", "Luna", "Max", "Nina", "Oscar" - }; - String[] lastNames = { - "Johnson", "Wilson", "Brown", "Taylor", "Davis", "Miller", "Anderson", "Thomas", - "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", - "Clark", "Rodriguez", "Lewis", "Lee", "Walker", "Hall", "Allen", "Young", - "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", - "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner" - }; - - // Create students based on configuration - int totalStudents = USE_LARGE_DATABASE ? 360 : 30; // 360 for large (30 per cohort), 30 for small (10 per cohort) - int studentsPerCohort = USE_LARGE_DATABASE ? 30 : 10; - - for (int i = 0; i < totalStudents; i++) { - String firstName = firstNames[i % firstNames.length]; - String lastName = lastNames[i % lastNames.length]; - String email = firstName.toLowerCase() + i + "@s.com"; - - User student = createUser(email, "p", studentRole); - if (student.getProfile() == null) { - // Distribute students evenly across all cohorts - Cohort assignedCohort = allCohorts.get(i / studentsPerCohort); - - Profile studentProfile = new Profile(student, firstName, lastName, - firstName.toLowerCase() + lastName.toLowerCase() + i, - "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, - "+4412345679" + String.format("%03d", i), - "Passionate about learning software development and building amazing applications.", - studentRole, "Software Development", assignedCohort, null); - profileRepository.save(studentProfile); - student.setProfile(studentProfile); - student = userRepository.save(student); - } - students.add(student); - } - - // Create sample posts - List samplePosts = Arrays.asList( - "Just finished my first Java application! Excited to learn more about Spring Boot.", - "Working on a React project today. The component lifecycle is fascinating!", - "Had a great debugging session today. Finally understood how to use breakpoints effectively.", - "Group project is going well. Collaboration through Git is becoming second nature.", - "Completed the database design exercise. Understanding relationships is key!", - "Learned about REST APIs today. Can't wait to build my own!", - "Struggling with CSS Grid but making progress. Practice makes perfect!", - "Just deployed my first application to the cloud. Such a great feeling!", - "Code review session was very helpful. Learning from others is invaluable.", - "Working on the final project. Bringing everything together is challenging but rewarding." - ); - - // Create posts from different users (only if no posts exist) - if (postRepository.count() == 0) { - for (int i = 0; i < samplePosts.size(); i++) { - User author = (i < 2) ? (i == 0 ? teacherJohn : teacherSarah) : students.get(i % students.size()); - Post post = new Post(author, samplePosts.get(i)); - post.setLikes((int) (Math.random() * 10)); // Random likes 0-9 - postRepository.save(post); - } - } - - System.out.println("Sample data initialization completed!"); - System.out.println("Created:"); - System.out.println("- 2 roles (Teacher, Student)"); - - System.out.println("- 3 courses with date ranges (Software Development: Jan-Jul 2024, Front-End Development: Feb-Aug 2024, Data Analytics: Mar-Sep 2024)"); - - - if (USE_LARGE_DATABASE) { - System.out.println("- 12 cohorts (4 per course)"); - System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); - System.out.println("- 30 students per cohort across 12 cohorts (360 total students)"); - System.out.println(" - Software Development: 4 cohorts (120 students)"); - System.out.println(" - Front-End Development: 4 cohorts (120 students)"); - System.out.println(" - Data Analytics: 4 cohorts (120 students)"); - } else { - System.out.println("- 3 cohorts (1 per course)"); - System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); - System.out.println("- 10 students per cohort across 3 cohorts (30 total students)"); - System.out.println(" - Software Development: 1 cohort (10 students)"); - System.out.println(" - Front-End Development: 1 cohort (10 students)"); - System.out.println(" - Data Analytics: 1 cohort (10 students)"); - } - - System.out.println("- " + samplePosts.size() + " sample posts"); - } - - private Role createOrGetRole(ERole roleName) { - return roleRepository.findByName(roleName) - .orElseGet(() -> roleRepository.save(new Role(roleName))); - } - - private User createUser(String email, String password, Role role) { - // Check if user already exists - return userRepository.findByEmail(email) - .orElseGet(() -> { - User user = new User(email, encoder.encode(password)); - Set roles = new HashSet<>(); - roles.add(role); - user.setRoles(roles); - return userRepository.save(user); - }); - } - - - private Course createOrGetCourse(String name, LocalDate startDate, LocalDate endDate) { - - return courseRepository.findAll().stream() - .filter(course -> course.getName().equals(name)) - .findFirst() - .orElseGet(() -> courseRepository.save(new Course(name, startDate, endDate, null))); - } - - private Cohort createOrGetCohort(String name, Course course) { - return cohortRepository.findAll().stream() - .filter(cohort -> cohort.getName().equals(name)) - .findFirst() - .orElseGet(() -> { - Cohort cohort = new Cohort(name, course); - return cohortRepository.save(cohort); - }); - } -} \ No newline at end of file +// System.out.println("Initializing sample data..."); +// +// // Create roles +// +// Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); +// Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); +// +// +// // Create courses with start and end dates +// Course javaFundamentals = createOrGetCourse("Software Development", +// LocalDate.of(2024, 1, 15), LocalDate.of(2024, 7, 15)); +// Course springBoot = createOrGetCourse("Front-End Development", +// LocalDate.of(2024, 2, 1), LocalDate.of(2024, 8, 1)); +// Course reactFundamentals = createOrGetCourse("Data Analytics", +// LocalDate.of(2024, 3, 1), LocalDate.of(2024, 9, 1)); +// +// +// // Create cohorts based on configuration +// List allCohorts = new ArrayList<>(); +// +// if (USE_LARGE_DATABASE) { +// // LARGE DATABASE: 4 cohorts per course (12 total) +// +// // Software Development cohorts (4 cohorts) +// allCohorts.add(createOrGetCohort("Software Development 2024 Q1", javaFundamentals)); +// allCohorts.add(createOrGetCohort("Software Development 2024 Q2", javaFundamentals)); +// allCohorts.add(createOrGetCohort("Software Development 2025 Q1", javaFundamentals)); +// allCohorts.add(createOrGetCohort("Software Development 2025 Q2", javaFundamentals)); +// +// // Front-End Development cohorts (4 cohorts) +// allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", springBoot)); +// allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", springBoot)); +// allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", springBoot)); +// allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", springBoot)); +// +// // Data Analytics cohorts (4 cohorts) +// allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", reactFundamentals)); +// allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", reactFundamentals)); +// allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", reactFundamentals)); +// allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", reactFundamentals)); +// } else { +// // SMALL DATABASE: 1 cohort per course (3 total) +// allCohorts.add(createOrGetCohort("Software Development 2025", javaFundamentals)); +// allCohorts.add(createOrGetCohort("Front-End Development 2025", springBoot)); +// allCohorts.add(createOrGetCohort("Data Analytics 2025", reactFundamentals)); +// } +// +// +// // Create teacher users +// User teacherJohn = createUser("t@t.com", "p", teacherRole); +// if (teacherJohn.getProfile() == null) { +// Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", +// "https://github.com/johnsmith", "+44123456790", +// "Experienced Java developer and educator with 10+ years in software development.", +// teacherRole, "Java Development", allCohorts.get(0), null); +// profileRepository.save(johnProfile); +// teacherJohn.setProfile(johnProfile); +// userRepository.save(teacherJohn); +// } +// +// User teacherSarah = createUser("tt@t.com", "p", teacherRole); +// if (teacherSarah.getProfile() == null) { +// Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", +// "https://github.com/sarahjones", "+44123456791", +// "Frontend specialist with expertise in React and modern web technologies.", +// teacherRole, "Frontend Development", allCohorts.get(1), null); +// profileRepository.save(sarahProfile); +// teacherSarah.setProfile(sarahProfile); +// userRepository.save(teacherSarah); +// } +// +// // Create student users +// List students = new ArrayList<>(); +// String[] firstNames = { +// "Alice", "Bob", "Carol", "David", "Emma", "Frank", "Grace", "Henry", +// "Ivy", "Jack", "Kate", "Liam", "Maya", "Noah", "Olivia", "Paul", +// "Quinn", "Ruby", "Sam", "Tina", "Uma", "Victor", "Wendy", "Xavier", +// "Yara", "Zoe", "Alex", "Blake", "Chloe", "Dan", "Eva", "Felix", +// "Gina", "Hugo", "Iris", "Jake", "Luna", "Max", "Nina", "Oscar" +// }; +// String[] lastNames = { +// "Johnson", "Wilson", "Brown", "Taylor", "Davis", "Miller", "Anderson", "Thomas", +// "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", +// "Clark", "Rodriguez", "Lewis", "Lee", "Walker", "Hall", "Allen", "Young", +// "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", +// "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner" +// }; +// +// // Create students based on configuration +// int totalStudents = USE_LARGE_DATABASE ? 360 : 30; // 360 for large (30 per cohort), 30 for small (10 per cohort) +// int studentsPerCohort = USE_LARGE_DATABASE ? 30 : 10; +// +// for (int i = 0; i < totalStudents; i++) { +// String firstName = firstNames[i % firstNames.length]; +// String lastName = lastNames[i % lastNames.length]; +// String email = firstName.toLowerCase() + i + "@s.com"; +// +// User student = createUser(email, "p", studentRole); +// if (student.getProfile() == null) { +// // Distribute students evenly across all cohorts +// Cohort assignedCohort = allCohorts.get(i / studentsPerCohort); +// +// Profile studentProfile = new Profile(student, firstName, lastName, +// firstName.toLowerCase() + lastName.toLowerCase() + i, +// "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, +// "+4412345679" + String.format("%03d", i), +// "Passionate about learning software development and building amazing applications.", +// studentRole, "Software Development", assignedCohort, null); +// profileRepository.save(studentProfile); +// student.setProfile(studentProfile); +// student = userRepository.save(student); +// } +// students.add(student); +// } +// +// // Create sample posts +// List samplePosts = Arrays.asList( +// "Just finished my first Java application! Excited to learn more about Spring Boot.", +// "Working on a React project today. The component lifecycle is fascinating!", +// "Had a great debugging session today. Finally understood how to use breakpoints effectively.", +// "Group project is going well. Collaboration through Git is becoming second nature.", +// "Completed the database design exercise. Understanding relationships is key!", +// "Learned about REST APIs today. Can't wait to build my own!", +// "Struggling with CSS Grid but making progress. Practice makes perfect!", +// "Just deployed my first application to the cloud. Such a great feeling!", +// "Code review session was very helpful. Learning from others is invaluable.", +// "Working on the final project. Bringing everything together is challenging but rewarding." +// ); +// +// // Create posts from different users (only if no posts exist) +// if (postRepository.count() == 0) { +// for (int i = 0; i < samplePosts.size(); i++) { +// User author = (i < 2) ? (i == 0 ? teacherJohn : teacherSarah) : students.get(i % students.size()); +// Post post = new Post(author, samplePosts.get(i)); +// post.setLikes((int) (Math.random() * 10)); // Random likes 0-9 +// postRepository.save(post); +// } +// } +// +// System.out.println("Sample data initialization completed!"); +// System.out.println("Created:"); +// System.out.println("- 2 roles (Teacher, Student)"); +// +// System.out.println("- 3 courses with date ranges (Software Development: Jan-Jul 2024, Front-End Development: Feb-Aug 2024, Data Analytics: Mar-Sep 2024)"); +// +// +// if (USE_LARGE_DATABASE) { +// System.out.println("- 12 cohorts (4 per course)"); +// System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); +// System.out.println("- 30 students per cohort across 12 cohorts (360 total students)"); +// System.out.println(" - Software Development: 4 cohorts (120 students)"); +// System.out.println(" - Front-End Development: 4 cohorts (120 students)"); +// System.out.println(" - Data Analytics: 4 cohorts (120 students)"); +// } else { +// System.out.println("- 3 cohorts (1 per course)"); +// System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); +// System.out.println("- 10 students per cohort across 3 cohorts (30 total students)"); +// System.out.println(" - Software Development: 1 cohort (10 students)"); +// System.out.println(" - Front-End Development: 1 cohort (10 students)"); +// System.out.println(" - Data Analytics: 1 cohort (10 students)"); +// } +// +// System.out.println("- " + samplePosts.size() + " sample posts"); +// } +// +// private Role createOrGetRole(ERole roleName) { +// return roleRepository.findByName(roleName) +// .orElseGet(() -> roleRepository.save(new Role(roleName))); +// } +// +// private User createUser(String email, String password, Role role) { +// // Check if user already exists +// return userRepository.findByEmail(email) +// .orElseGet(() -> { +// User user = new User(email, encoder.encode(password)); +// Set roles = new HashSet<>(); +// roles.add(role); +// user.setRoles(roles); +// return userRepository.save(user); +// }); +// } +// +// +// private Course createOrGetCourse(String name, LocalDate startDate, LocalDate endDate) { +// +// return courseRepository.findAll().stream() +// .filter(course -> course.getName().equals(name)) +// .findFirst() +// .orElseGet(() -> courseRepository.save(new Course(name, startDate, endDate, null))); +// } +// +// private Cohort createOrGetCohort(String name, Course course) { +// return cohortRepository.findAll().stream() +// .filter(cohort -> cohort.getName().equals(name)) +// .findFirst() +// .orElseGet(() -> { +// Cohort cohort = new Cohort(name, course); +// return cohortRepository.save(cohort); +// }); +// } +}} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java index 498cdf5..9a413b8 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/UserController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/UserController.java @@ -79,22 +79,33 @@ public ResponseEntity deleteUser(@PathVariable int id) { return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); } + // 1. Fjern likes brukeren selv har gitt for (Post likedPost : new HashSet<>(user.getLikedPosts())) { likedPost.getLikedByUsers().remove(user); } user.getLikedPosts().clear(); + // 2. Fjern likes andre brukere har gitt på brukerens egne posts + for (Post post : new HashSet<>(user.getPosts())) { + for (User liker : new HashSet<>(post.getLikedByUsers())) { + liker.getLikedPosts().remove(post); + } + post.getLikedByUsers().clear(); + } + UserResponse userResponse = new UserResponse(); userResponse.set(user); try { - userRepository.delete(user); + userRepository.delete(user); // cascade sletter posts, comments, profile, notes return ResponseEntity.ok(userResponse); } catch (Exception e) { return new ResponseEntity<>("Could not delete user: " + e.getMessage(), HttpStatus.BAD_REQUEST); } } + + @PatchMapping("{user_id}/like") public ResponseEntity updateLikedPosts(@PathVariable int user_id, @RequestBody PostId postId){ int post_id = postId.post_id; From 4f85673f5a5d8793e22790210eececb3e1f0b9d5 Mon Sep 17 00:00:00 2001 From: Isabell Date: Wed, 24 Sep 2025 08:34:25 +0200 Subject: [PATCH 100/119] Fixed add new student --- .../controllers/StudentController.java | 108 +++++++++++++++++- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index 565f8cd..3af93c4 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -2,11 +2,13 @@ package com.booleanuk.cohorts.controllers; -import com.booleanuk.cohorts.models.Profile; -import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.models.*; import com.booleanuk.cohorts.payload.request.StudentRequest; +import com.booleanuk.cohorts.payload.response.MessageResponse; 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; import com.booleanuk.cohorts.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataIntegrityViolationException; @@ -15,8 +17,8 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; -import java.util.List; +import java.time.format.DateTimeParseException; +import java.util.*; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @@ -28,6 +30,11 @@ public class StudentController { @Autowired private ProfileRepository profileRepository; + @Autowired + private CohortRepository cohortRepository; + @Autowired + private RoleRepository roleRepository; + @Autowired PasswordEncoder encoder; @@ -51,6 +58,99 @@ public ResponseEntity getAllStudents() { return ResponseEntity.ok(studentListResponse); } + record PostUserProfile( + String first_name, + String last_name, + String username, + String github_username, + String email, + String mobile, + String password, + String bio, + String role, + String specialism, + int cohort, + String start_date, + String end_date, + String photo + ){} + + @PostMapping("/create") + public ResponseEntity createNewStudent(@RequestBody PostUserProfile newUserProfile){ + + System.err.println(newUserProfile); + System.out.println("test----------------------------"); + + // Lag ny bruker + if (userRepository.existsByEmail(newUserProfile.email)) { + 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(!newUserProfile.email.matches(emailRegex)) + return ResponseEntity.badRequest().body(new MessageResponse("Email is incorrectly formatted")); + + if(!newUserProfile.password.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(newUserProfile.email, encoder.encode(newUserProfile.password)); + + //Lag ny profil som er koblet opp til ny bruker + + if(newUserProfile.first_name == null || newUserProfile.first_name == "" || newUserProfile.last_name == null || newUserProfile.last_name == ""){ + return new ResponseEntity<>("First and last name can't be empty or NULL. First name: " + newUserProfile.first_name + " Last name: " + newUserProfile.last_name, HttpStatus.BAD_REQUEST); + } + + + Optional optionalRole = roleRepository.findByName(ERole.valueOf(newUserProfile.role)); + if (optionalRole.isEmpty()) { + return new ResponseEntity<>("Role for id "+ newUserProfile.role + " not found", HttpStatus.BAD_REQUEST); + } + + Role role = optionalRole.get(); + + Optional optionalCohort = cohortRepository.findById(newUserProfile.cohort); + if (optionalCohort.isEmpty()) { + return new ResponseEntity<>("Cohort for id "+ newUserProfile.cohort + " not found", HttpStatus.BAD_REQUEST); + } + + Cohort cohort = optionalCohort.get(); + + Profile newProfile = null; + try { + newProfile = new Profile( + user, + newUserProfile.first_name, + newUserProfile.last_name, + newUserProfile.username, + "https://github.com/" + newUserProfile.github_username, + newUserProfile.mobile, + newUserProfile.bio, + role, + newUserProfile.specialism, + cohort, + newUserProfile.photo + ); + } 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); + } + + newProfile.setUser(user); + user.setProfile(newProfile); + + + try { + return new ResponseEntity<>(userRepository.save(user), HttpStatus.OK); + } catch (DataIntegrityViolationException e) { + return new ResponseEntity<>("User has an existing profile", HttpStatus.BAD_REQUEST); + } + + } @PatchMapping("{id}") public ResponseEntity updateStudent(@PathVariable int id, @RequestBody StudentRequest studentRequest) { From b585bbcedde045b36e83a7292a5555743c504897 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 24 Sep 2025 09:07:49 +0200 Subject: [PATCH 101/119] added tests --- .../controllerTests/CohortControllerTest.java | 173 ++++++++++++++++++ .../controllerTests/UserControllerTest.java | 4 +- 2 files changed, 174 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java b/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java index 41eeea9..9aad658 100644 --- a/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java @@ -1,4 +1,177 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.AuthController; +import com.booleanuk.cohorts.controllers.ProfileController; +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; +import com.booleanuk.cohorts.security.services.UserDetailsImpl; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +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.http.MediaType; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebAppConfiguration +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional public class CohortControllerTest { + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private UserRepository userRepository; + + @Autowired + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @PersistenceContext + private EntityManager entityManager; + + private MockMvc mockMvc; + + private int actualUserId; + + private User testUser; + + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + userRepository.deleteAll(); + + SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); + this.authController.registerUser(signupRequest); + entityManager.flush(); + entityManager.clear(); + + testUser = userRepository.findAll().get(0); + actualUserId = testUser.getId(); + + ProfileController.PostProfile postProfile = new ProfileController.PostProfile( + actualUserId, + "Thomas", + "Ladder", + "gottaStepUp", + "244783772", + "tallerThanU", + "I need a ladder, but can't afford one. So, steps will have to be taken", + "ROLE_TEACHER", + "Big moves", + 1, + "1999-01-01", + "2039-01-01", + "https://media.makeameme.org/created/ladder-i.jpg" + ); + this.profileController.createProfile(postProfile); + entityManager.flush(); + entityManager.clear(); + } + + private void authenticateUser(User user) { + UserDetailsImpl userDetails = UserDetailsImpl.build(user); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + @Test + public void heuristics_testClassSetup() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("cohortController")); + } + + @Test + public void tryGetAllCohorts_testFirstNameOnFirstProfile_withSingleProfileInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/cohorts") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data"); + JSONArray cohorts = response.getJSONArray("cohorts"); + assertNotNull(cohorts); + + JSONObject firstProfile = cohorts.getJSONObject(0).getJSONArray("profiles").getJSONObject(0); + assertEquals(firstProfile.getString("firstName"), "Thomas"); + } + + @Test + public void tryGetCohortsById_testEmailOnFirstProfile_withSingleProfileInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/cohorts/1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("cohort"); + assertNotNull(response); + + JSONObject firstProfile = response.getJSONArray("profiles").getJSONObject(0); + assertEquals(firstProfile.getString("firstName"), "Thomas"); + + } + + @Test + public void tryGetCohortsByUserId_testEmailOnFirstProfile_withSingleProfileInDb() throws Exception { + String profileJson = """ + { + "":"", + "":"" + } + """.formatted(actualUserId); + + MvcResult result = this.mockMvc.perform(post("/cohorts/teacher/" + actualUserId) + .contentType(MediaType.APPLICATION_JSON) + .content(profileJson)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("cohort"); + assertNotNull(response); + + JSONObject firstProfile = response.getJSONArray("profiles").getJSONObject(0); + assertEquals(firstProfile.getString("firstName"), "Thomas"); + assertEquals(firstProfile.getString("email"), "thomas@ladder.com"); + } + + @Test + public void tryAddStudentToCohort_checkProfileObjectAndResponseBody_withSingleProfileInDb() throws Exception { + + } + } diff --git a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java index 45d7607..0f620ad 100644 --- a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java @@ -53,9 +53,6 @@ public class UserControllerTest { @Autowired private UserRepository userRepository; - @Autowired - private UserController userController; - @Autowired private AuthController authController; @@ -71,6 +68,7 @@ public class UserControllerTest { private MockMvc mockMvc; private int actualUserId; + private User testUser; @BeforeEach From 39574701ce90b335d1be15edcdd895fb742b0808 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 24 Sep 2025 09:11:11 +0200 Subject: [PATCH 102/119] added workflow for building back --- .github/workflows/gradle.yml | 43 ++++++++++++++++++++++++++++++++++++ .gitignore | 1 - 2 files changed, 43 insertions(+), 1 deletion(-) 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..3e22d19 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,43 @@ +# 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 --exclude-task test + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: Package + path: build/libs \ No newline at end of file diff --git a/.gitignore b/.gitignore index fa510f7..8c1efc7 100644 --- a/.gitignore +++ b/.gitignore @@ -41,5 +41,4 @@ application.yml build .DS_Store application-azuread.yml -*.yml Maincopy.md \ No newline at end of file From 98637a96d986e1a897b9f85917ec9603bf2305c9 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 24 Sep 2025 09:14:04 +0200 Subject: [PATCH 103/119] updated gradle workflow from JDK 17 to 22 --- .github/workflows/gradle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 3e22d19..1425ea0 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -22,10 +22,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 + - name: Set up JDK 22 uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '22' distribution: 'temurin' # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. From 470583be2e257bf9b4553b372ae969b110262d80 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Fri, 19 Sep 2025 15:50:53 +0200 Subject: [PATCH 104/119] written tests for ProfileController and SearchController --- build.gradle | 2 + .../controllers/ProfileController.java | 2 +- .../payload/request/SignupRequest.java | 5 + .../cohorts/repository/UserRepository.java | 3 + .../ProfileControllerTest.java | 170 ++++++++++++++++++ .../controllerTests/SearchControllerTest.java | 125 ++++++++++++- 6 files changed, 298 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 7a4365e..3e06b89 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,8 @@ dependencies { 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/com.h2database/h2 + testImplementation 'com.h2database:h2' // https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api diff --git a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java index cec73eb..501e3ca 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/ProfileController.java @@ -43,7 +43,7 @@ public class ProfileController { @Autowired private UserRepository userRepository; - record PostProfile( + public record PostProfile( int userId, String first_name, String last_name, diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java index 0fc94de..2f0efce 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java @@ -24,4 +24,9 @@ public class SignupRequest { private String password; private Cohort cohort; + + public SignupRequest(String email, String password) { + this.email = email; + this.password = password; + } } diff --git a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java index 1bc7024..7d34f7c 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java @@ -1,5 +1,6 @@ package com.booleanuk.cohorts.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -18,4 +19,6 @@ public interface UserRepository extends JpaRepository { Optional findByEmailWithProfile(@Param("email") String email); Boolean existsByEmail(String email); + + List getTopById(int id); } diff --git a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java index fc7a322..be4562a 100644 --- a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java @@ -1,4 +1,174 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.AuthController; +import com.booleanuk.cohorts.controllers.ProfileController; +import com.booleanuk.cohorts.controllers.SearchController; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +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.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +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; + +@WebAppConfiguration +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional public class ProfileControllerTest { + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private UserRepository userRepository; + + @Autowired + private SearchController searchController; + + @Autowired + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @PersistenceContext + private EntityManager entityManager; + + private MockMvc mockMvc; + + private int actualUserId; + + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + userRepository.deleteAll(); + profileRepository.deleteAll(); + + SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); + ResponseEntity registerResponse = this.authController.registerUser(signupRequest); + entityManager.flush(); + entityManager.clear(); + + + actualUserId = userRepository.findAll().get(0).getId(); + + } + + @Test + public void heuristics_testClassSetup() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("searchController")); + } + + @Test + public void tryCreateProfile_testFirstNameOnCreatedProfile() throws Exception { + String profileJson = """ + { + "userId": %d, + "first_name": "Thomas", + "last_name": "Ladder", + "username": "gottaStepUp", + "mobile": "244783772", + "github_username": "tallerThanU", + "bio": "GI need a ladder, but can't afford one. So, steps will have to be taken", + "role": "ROLE_STUDENT", + "specialism": "Big moves", + "cohort": 1, + "start_date": "1999-01-01", + "end_date": "2039-01-01", + "photo": "https://media.makeameme.org/created/ladder-i.jpg" + } + """.formatted(actualUserId); + + MvcResult result = this.mockMvc.perform(post("/profiles") + .contentType(MediaType.APPLICATION_JSON) + .content(profileJson)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertNotNull(result); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json); + + JSONObject profile = jsonObject.getJSONObject("profile"); + String firstName = profile.getString("firstName"); + String username = profile.getString("username"); + + System.out.println("First Name: " + firstName); + assertEquals("Thomas", firstName, "Profile first name should be Thomas"); + assertEquals("gottaStepUp", username, "Profile username should be gottaStepUp"); + } + + @Test + public void tryGetProfileForId_testFirstNameOnFoundProfile_withProfilesInDB() throws Exception { + ResponseEntity profileResponse = profileController.createProfile(new ProfileController.PostProfile( + actualUserId, // Use the actual user ID + "Thomas", + "Ladder", + "gottaStepUp", + "244783772", + "tallerThanU", + "GI need a ladder, but can't afford one. So, steps will have to be taken", + "ROLE_STUDENT", + "Big moves", + 1, + "1999-01-01", + "2039-01-01", + "https://media.makeameme.org/created/ladder-i.jpg" + )); + + entityManager.flush(); + entityManager.clear(); + + MvcResult result = this.mockMvc.perform(get("/profiles/"+ actualUserId)) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn(); + + assertNotNull(result); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json); + + JSONObject profile = jsonObject.getJSONObject("data").getJSONObject("profile"); + String firstName = profile.getString("firstName"); + String username = profile.getString("username"); + System.out.println("First Name: " + firstName); + assertEquals("Thomas", firstName, "Profile first name should be Thomas"); + assertEquals("gottaStepUp", username, "Profile first name should be Thomas"); + } + } diff --git a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java index 94c0313..7d20ad5 100644 --- a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java @@ -1,30 +1,49 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.AuthController; +import com.booleanuk.cohorts.controllers.ProfileController; import com.booleanuk.cohorts.controllers.SearchController; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.Assertions; 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.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; 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.MvcResult; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.context.WebApplicationContext; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; 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; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @WebAppConfiguration @SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional class SearchControllerTest { @Autowired @@ -36,15 +55,62 @@ class SearchControllerTest { @Autowired private SearchController searchController; + @Autowired + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @PersistenceContext + private EntityManager entityManager; + private MockMvc mockMvc; @BeforeEach public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + userRepository.deleteAll(); + profileRepository.deleteAll(); + + SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); + ResponseEntity registerResponse = this.authController.registerUser(signupRequest); + entityManager.flush(); + entityManager.clear(); + + List users = userRepository.findAll(); + User createdUser = users.get(0); + int actualUserId = createdUser.getId(); + + System.out.println("Using user ID for profile creation: " + actualUserId); + + + ResponseEntity profileResponse = profileController.createProfile(new ProfileController.PostProfile( + actualUserId, // Use the actual user ID + "Thomas", + "Ladder", + "gottaStepUp", + "244783772", + "tallerThanU", + "GI need a ladder, but can't afford one. So, steps will have to be taken", + "ROLE_STUDENT", + "Big moves", + 1, + "1999-01-01", + "2039-01-01", + "https://media.makeameme.org/created/ladder-i.jpg" + )); + + + entityManager.flush(); + entityManager.clear(); + } @Test - public void heuristicsTryGettingBeanSearchController() { + public void heuristics_testClassSetup() { ServletContext servletContext = webApplicationContext.getServletContext(); assertNotNull(servletContext); @@ -53,9 +119,52 @@ public void heuristicsTryGettingBeanSearchController() { } @Test - public void tryGettingBaseURL_andGetSomeResponse() throws Exception { - this.mockMvc.perform(get("/search/profiles")) + public void trySearchProfilesDefault_withProfilesInDB() throws Exception { + MvcResult result = this.mockMvc.perform(get("/search/profiles")) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + + JSONObject jsonObject = new JSONObject(json); + JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("profiles"); + + assertTrue(jsonArray.length() == 1, "Should return at least one profile"); + } + + @Test + public void trySearchProfilesQuery_testFirstNameOnFirstFoundProfile_withProfilesInDB() throws Exception { + MvcResult result = this.mockMvc.perform(get("/search/profiles/thomas")) + .andDo(print()) + .andExpect(status().isOk()) .andDo(print()) - .andExpect(status().isOk()); + .andReturn(); + + String json = result.getResponse().getContentAsString(); + + JSONObject jsonObject = new JSONObject(json); + JSONArray profileArray = jsonObject.getJSONObject("data").getJSONArray("profiles"); + assertTrue(profileArray.length() > 0, "Should return at least one profile"); + + JSONObject firstProfile = profileArray.getJSONObject(0); + String profileFirstName = firstProfile.getString("firstName"); + assertTrue(profileFirstName.toLowerCase().contains("thomas"), + "Profile first name should contain 'thomas'"); + } + + @Test + public void trySearchProfileQuery_testNoProfilesFound_withProfilesInDB() throws Exception { + MvcResult result = this.mockMvc.perform(get("/search/profiles/firstnamethatdoesnotexsist")) + .andDo(print()) + .andExpect(status().isOk()) + .andDo(print()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json); + + JSONArray profileArray = jsonObject.getJSONObject("data").getJSONArray("profiles"); + assertTrue(profileArray.length() == 0, "Should return no profiles"); } } From 9060b984244c53f83e0339b8bfd97815852455be Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Mon, 22 Sep 2025 11:47:45 +0200 Subject: [PATCH 105/119] Added tests for ProfileController and UserController --- .../ProfileControllerTest.java | 3 - .../controllerTests/UserControllerTest.java | 265 ++++++++++++++++++ 2 files changed, 265 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java index be4562a..7468124 100644 --- a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java @@ -46,9 +46,6 @@ public class ProfileControllerTest { @Autowired private UserRepository userRepository; - @Autowired - private SearchController searchController; - @Autowired AuthController authController; diff --git a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java index 45c01d1..45d7607 100644 --- a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java @@ -1,4 +1,269 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.*; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.PostRequest; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.payload.response.PostResponse; +import com.booleanuk.cohorts.repository.PostRepository; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; +import com.booleanuk.cohorts.security.services.UserDetailsImpl; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +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.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebAppConfiguration +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional public class UserControllerTest { + + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserController userController; + + @Autowired + private AuthController authController; + + @Autowired + private PostRepository postRepository; + + @Autowired + private PostController postController; + + @PersistenceContext + private EntityManager entityManager; + + private MockMvc mockMvc; + + private int actualUserId; + private User testUser; + + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + userRepository.deleteAll(); + + SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); + ResponseEntity registerResponse = this.authController.registerUser(signupRequest); + entityManager.flush(); + entityManager.clear(); + + testUser = userRepository.findAll().get(0); + actualUserId = testUser.getId(); + } + + private void authenticateUser(User user) { + UserDetailsImpl userDetails = UserDetailsImpl.build(user); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + @Test + public void heuristics_testClassSetup() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("userController")); + } + + @Test + public void tryGetAllUsers_testEmailOnFirstUser_withSingleUserInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/users") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertNotNull(result); + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + + JSONArray jsonArray = jsonObject.getJSONArray("users"); + String email = jsonArray.getJSONObject(0).getString("email"); + + assertTrue(jsonArray.length() == 1); + assertEquals("thomas@ladder.com", email, "Email should be thomas@ladder.com"); + } + + @Test + public void tryGetAllUsers_testEmailOnMultipleUsers_withMultipleUserInDb() throws Exception { + this.authController.registerUser(new SignupRequest("fredrik@ladder.com", "@Qwerty12345")); + this.authController.registerUser(new SignupRequest("sara@ladder.com", "@Qwerty12345")); + this.authController.registerUser(new SignupRequest("josefine@ladder.com", "@Qwerty12345")); + entityManager.flush(); + entityManager.clear(); + + MvcResult result = this.mockMvc.perform(get("/users") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertNotNull(result); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + JSONArray jsonArray = jsonObject.getJSONArray("users"); + + assertTrue(jsonArray.length() == 4); + + String emailThomas = jsonArray.getJSONObject(0).getString("email"); + String emailFredrik = jsonArray.getJSONObject(1).getString("email"); + String emailSara = jsonArray.getJSONObject(2).getString("email"); + String emailJosefine = jsonArray.getJSONObject(3).getString("email"); + + assertEquals("thomas@ladder.com", emailThomas, "Email should be thomas@ladder.com"); + assertEquals("fredrik@ladder.com", emailFredrik, "Email should be fredrik@ladder.com"); + assertEquals("sara@ladder.com", emailSara, "Email should be sara@ladder.com"); + assertEquals("josefine@ladder.com", emailJosefine, "Email should be josefine@ladder.com"); + } + + @Test + public void tryGetUserById_testEmailOnFirstUser_withSingleUserInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/users/" + actualUserId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertNotNull(result); + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + + JSONObject user = jsonObject.getJSONObject("user"); + String email = user.getString("email"); + assertEquals("thomas@ladder.com", email, "Email should be thomas@ladder.com"); + } + + @Test + public void tryDeleteUserById_testReturnCodeAndIfUserIsActuallyDeleted() throws Exception { + MvcResult result = this.mockMvc.perform(delete("/users/" + actualUserId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + assertTrue(userRepository.findById(actualUserId).isEmpty(), "User list should be empty"); + } + + @Test + public void tryUpdateLikedPosts_testAddingSingleLikedPost_checkUserObjectAndResponseBody_withSingleUserIndb() throws Exception { + authenticateUser(testUser); + + ResponseEntity postResponse = this.postController.createPost(new PostRequest("It's not DNS... There's no way it's DNS... It was DNS", actualUserId)); + + entityManager.flush(); + entityManager.clear(); + + int postId = this.postRepository.findAll().get(0).getId(); + + // Create the request body with the post_id + String requestBody = "{\"post_id\": " + postId + "}"; + + MvcResult result = this.mockMvc.perform(patch("/users/" + actualUserId) + .with(user(UserDetailsImpl.build(testUser))) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + JSONObject user = jsonObject.getJSONObject("user"); + JSONArray likedPosts = user.getJSONArray("likedPosts"); + + assertEquals(1, likedPosts.length(), "User should have 1 liked post in their likedPosts array"); + assertEquals("It's not DNS... There's no way it's DNS... It was DNS", likedPosts.getJSONObject(0).getString("content")); + + User userWithLike = userRepository.getReferenceById(actualUserId); + assertTrue(userWithLike.getLikedPosts().size() == 1, "User should have 1 liked post in their likedPosts array"); + } + + @Test + public void tryUpdateLikedPosts_testAddingMultipleLikedPost_checkUserObjectAndResponseBody_withSingleUserIndb() throws Exception { + authenticateUser(testUser); + + ResponseEntity postResponse = this.postController.createPost(new PostRequest("It's not DNS... There's no way it's DNS... It was DNS", actualUserId)); + ResponseEntity postResponse2 = this.postController.createPost(new PostRequest("Sorry I forgot", actualUserId)); + entityManager.flush(); + entityManager.clear(); + + // Add first post + int postId = this.postRepository.findAll().get(0).getId(); + String requestBody = "{\"post_id\": " + postId + "}"; + + MvcResult result = this.mockMvc.perform(patch("/users/" + actualUserId) + .with(user(UserDetailsImpl.build(testUser))) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + // Add second post + postId = this.postRepository.findAll().get(1).getId(); + requestBody = "{\"post_id\": " + postId + "}"; + + result = this.mockMvc.perform(patch("/users/" + actualUserId) + .with(user(UserDetailsImpl.build(testUser))) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + // Actual testing logic + String json = result.getResponse().getContentAsString(); + JSONObject jsonObject = new JSONObject(json).getJSONObject("data"); + JSONObject user = jsonObject.getJSONObject("user"); + JSONArray likedPosts = user.getJSONArray("likedPosts"); + + assertEquals(2, likedPosts.length(), "User should have 1 liked post in their likedPosts array"); + assertEquals("It's not DNS... There's no way it's DNS... It was DNS", likedPosts.getJSONObject(0).getString("content")); + assertEquals("Sorry I forgot", likedPosts.getJSONObject(1).getString("content")); + + User userWithLike = userRepository.getReferenceById(actualUserId); + assertTrue(userWithLike.getLikedPosts().size() == 2, "User should have 1 liked post in their likedPosts array"); + } } From 179cc313ee5f3772621de02161b683f16b7e406e Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 24 Sep 2025 09:07:49 +0200 Subject: [PATCH 106/119] added tests --- .../controllerTests/CohortControllerTest.java | 173 ++++++++++++++++++ .../controllerTests/UserControllerTest.java | 4 +- 2 files changed, 174 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java b/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java index 41eeea9..9aad658 100644 --- a/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java @@ -1,4 +1,177 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.AuthController; +import com.booleanuk.cohorts.controllers.ProfileController; +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.Profile; +import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.repository.ProfileRepository; +import com.booleanuk.cohorts.repository.UserRepository; +import com.booleanuk.cohorts.security.services.UserDetailsImpl; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +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.http.MediaType; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebAppConfiguration +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional public class CohortControllerTest { + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private UserRepository userRepository; + + @Autowired + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @PersistenceContext + private EntityManager entityManager; + + private MockMvc mockMvc; + + private int actualUserId; + + private User testUser; + + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + userRepository.deleteAll(); + + SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); + this.authController.registerUser(signupRequest); + entityManager.flush(); + entityManager.clear(); + + testUser = userRepository.findAll().get(0); + actualUserId = testUser.getId(); + + ProfileController.PostProfile postProfile = new ProfileController.PostProfile( + actualUserId, + "Thomas", + "Ladder", + "gottaStepUp", + "244783772", + "tallerThanU", + "I need a ladder, but can't afford one. So, steps will have to be taken", + "ROLE_TEACHER", + "Big moves", + 1, + "1999-01-01", + "2039-01-01", + "https://media.makeameme.org/created/ladder-i.jpg" + ); + this.profileController.createProfile(postProfile); + entityManager.flush(); + entityManager.clear(); + } + + private void authenticateUser(User user) { + UserDetailsImpl userDetails = UserDetailsImpl.build(user); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + @Test + public void heuristics_testClassSetup() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("cohortController")); + } + + @Test + public void tryGetAllCohorts_testFirstNameOnFirstProfile_withSingleProfileInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/cohorts") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data"); + JSONArray cohorts = response.getJSONArray("cohorts"); + assertNotNull(cohorts); + + JSONObject firstProfile = cohorts.getJSONObject(0).getJSONArray("profiles").getJSONObject(0); + assertEquals(firstProfile.getString("firstName"), "Thomas"); + } + + @Test + public void tryGetCohortsById_testEmailOnFirstProfile_withSingleProfileInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/cohorts/1") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("cohort"); + assertNotNull(response); + + JSONObject firstProfile = response.getJSONArray("profiles").getJSONObject(0); + assertEquals(firstProfile.getString("firstName"), "Thomas"); + + } + + @Test + public void tryGetCohortsByUserId_testEmailOnFirstProfile_withSingleProfileInDb() throws Exception { + String profileJson = """ + { + "":"", + "":"" + } + """.formatted(actualUserId); + + MvcResult result = this.mockMvc.perform(post("/cohorts/teacher/" + actualUserId) + .contentType(MediaType.APPLICATION_JSON) + .content(profileJson)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("cohort"); + assertNotNull(response); + + JSONObject firstProfile = response.getJSONArray("profiles").getJSONObject(0); + assertEquals(firstProfile.getString("firstName"), "Thomas"); + assertEquals(firstProfile.getString("email"), "thomas@ladder.com"); + } + + @Test + public void tryAddStudentToCohort_checkProfileObjectAndResponseBody_withSingleProfileInDb() throws Exception { + + } + } diff --git a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java index 45d7607..0f620ad 100644 --- a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java @@ -53,9 +53,6 @@ public class UserControllerTest { @Autowired private UserRepository userRepository; - @Autowired - private UserController userController; - @Autowired private AuthController authController; @@ -71,6 +68,7 @@ public class UserControllerTest { private MockMvc mockMvc; private int actualUserId; + private User testUser; @BeforeEach From d0e35fe7664b635197d9cd4991e04b2e315c5190 Mon Sep 17 00:00:00 2001 From: Magnus Hissingby Date: Wed, 24 Sep 2025 10:40:23 +0200 Subject: [PATCH 107/119] added deletemapping to CohortController --- .../cohorts/controllers/CohortController.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 3900a9a..53f7502 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -124,5 +124,25 @@ public ResponseEntity addStudentToCohort(@PathVariable int id, @RequestBody P return new ResponseEntity<>(profileRepository.save(profile), HttpStatus.OK); } + + @DeleteMapping("{id}") + public ResponseEntity deleteCohort(@PathVariable int id) { + Cohort cohort = cohortRepository.findById(id).orElse(null); + if (cohort == null) return new ResponseEntity<>("Cohort for id " + Integer.valueOf(id) + " not found.", HttpStatus.NOT_FOUND); + CohortResponse cohortResponse = new CohortResponse(); + cohortResponse.set(cohort); + + for (Profile profile : cohort.getProfiles()){ + profile.setCohort(null); + } + cohort.getProfiles().clear(); + cohort.getCourse().getCohorts().remove(cohort); + try { + cohortRepository.delete(cohort); + return ResponseEntity.ok(cohortResponse); + } catch (Exception e) { + return new ResponseEntity<>("Could not delete Cohort", HttpStatus.BAD_REQUEST); + } + } } From 72715bb6d8b0d75ab56cc29f982bd1f4ed53a412 Mon Sep 17 00:00:00 2001 From: Richard-Persson Date: Wed, 24 Sep 2025 11:32:20 +0200 Subject: [PATCH 108/119] added dates to cohort --- .../cohorts/controllers/CohortController.java | 21 ++++++++++++++++++- .../com/booleanuk/cohorts/models/Cohort.java | 21 ++++++++++++++++--- .../payload/request/CohortRequest.java | 3 +++ .../request/CohortRequestWithProfiles.java | 2 ++ 4 files changed, 43 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 3900a9a..3c134ba 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -6,6 +6,8 @@ import com.booleanuk.cohorts.payload.request.ProfileRequest; import com.booleanuk.cohorts.payload.response.*; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cglib.core.Local; + import com.booleanuk.cohorts.repository.CohortRepository; import com.booleanuk.cohorts.repository.CourseRepository; import com.booleanuk.cohorts.repository.ProfileRepository; @@ -13,6 +15,10 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -76,8 +82,14 @@ public ResponseEntity addCohort(@RequestBody CohortRequest cohortRequest){ String name = cohortRequest.getName(); if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); + System.out.println("StartDate raw: '" + cohortRequest.getStartDate() + "'"); + System.out.println("###########################################"); - Cohort cohort = new Cohort(cohortRequest.getName(), course); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + LocalDate startDate = LocalDate.parse(cohortRequest.getStartDate().trim(), formatter); + LocalDate endDate = LocalDate.parse(cohortRequest.getEndDate().trim(), formatter); + + Cohort cohort = new Cohort(cohortRequest.getName(),startDate,endDate,course); return ResponseEntity.ok(cohortRepository.save(cohort)); } @@ -92,6 +104,11 @@ public ResponseEntity editCohortById(@PathVariable int id, @RequestBody Cohor String name = cohortRequest.getName(); if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + LocalDate startDate = LocalDate.parse(cohortRequest.getStartDate(),formatter); + LocalDate endDate = LocalDate.parse(cohortRequest.getEndDate(),formatter); + List profiles = profileRepository.findAll().stream().filter(it -> cohortRequest.getProfileIds().contains(it.getId())).collect(Collectors.toList()); @@ -108,6 +125,8 @@ public ResponseEntity editCohortById(@PathVariable int id, @RequestBody Cohor cohort.setProfiles(profiles); cohort.setCourse(course); cohort.setName(cohortRequest.getName()); + cohort.setStartDate(startDate); + cohort.setEndDate(endDate); return ResponseEntity.ok(cohortRepository.save(cohort)); } diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index e7f1712..c6734b3 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -3,11 +3,15 @@ import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.ObjectIdGenerators; + +import org.springframework.cglib.core.Local; + import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; +import java.time.LocalDate; import java.util.List; @@ -23,6 +27,12 @@ public class Cohort { @Column private String name; + + @Column + private LocalDate startDate; + + @Column + private LocalDate endDate; @ManyToOne @JoinColumn(name = "course_id") @@ -39,7 +49,8 @@ public Cohort(int id) { } - public Cohort(String name, List profiles, Course course){ + + public Cohort(String name, List profiles, LocalDate startDate, LocalDate endDate, Course course){ this.name = name; this.profiles = profiles; this.course = course; @@ -49,8 +60,10 @@ public Cohort(String name){ this.name = name; } - public Cohort(String name, Course course){ + public Cohort(String name,LocalDate startDate, LocalDate endDate ,Course course){ this.name = name; + this.startDate = startDate; + this.endDate = endDate; this.course = course; } @@ -61,6 +74,8 @@ public Cohort(int id, String name) { @Override public String toString(){ - return "Cohort Id: " + this.id; + return "Cohort Id: " + this.id + "\n" + + "Start date: " + this.startDate + + "End date: " + this.startDate; } } 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 f5864ce..919f410 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequest.java @@ -9,5 +9,8 @@ public class CohortRequest { private String name; private int courseId; + private String startDate; + private String endDate; + public CohortRequest(){}} diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java index fc6f540..518b98c 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java @@ -12,6 +12,8 @@ public class CohortRequestWithProfiles { private String name; private int courseId; + private String startDate; + private String endDate; private List profileIds; public CohortRequestWithProfiles(){}} From fcca06cbc95ea363bf4de99c6cc086cffd900366 Mon Sep 17 00:00:00 2001 From: Richard-Persson Date: Wed, 24 Sep 2025 11:34:02 +0200 Subject: [PATCH 109/119] removed unused imports --- .../com/booleanuk/cohorts/controllers/CohortController.java | 4 ---- src/main/java/com/booleanuk/cohorts/models/Cohort.java | 3 --- .../cohorts/payload/request/CohortRequestWithProfiles.java | 2 -- 3 files changed, 9 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java index 3c134ba..2e02ac8 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -6,7 +6,6 @@ import com.booleanuk.cohorts.payload.request.ProfileRequest; import com.booleanuk.cohorts.payload.response.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cglib.core.Local; import com.booleanuk.cohorts.repository.CohortRepository; import com.booleanuk.cohorts.repository.CourseRepository; @@ -17,7 +16,6 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; @@ -82,8 +80,6 @@ public ResponseEntity addCohort(@RequestBody CohortRequest cohortRequest){ String name = cohortRequest.getName(); if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - System.out.println("StartDate raw: '" + cohortRequest.getStartDate() + "'"); - System.out.println("###########################################"); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); LocalDate startDate = LocalDate.parse(cohortRequest.getStartDate().trim(), formatter); diff --git a/src/main/java/com/booleanuk/cohorts/models/Cohort.java b/src/main/java/com/booleanuk/cohorts/models/Cohort.java index c6734b3..2201c10 100644 --- a/src/main/java/com/booleanuk/cohorts/models/Cohort.java +++ b/src/main/java/com/booleanuk/cohorts/models/Cohort.java @@ -1,10 +1,7 @@ package com.booleanuk.cohorts.models; -import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.ObjectIdGenerators; -import org.springframework.cglib.core.Local; import jakarta.persistence.*; import lombok.AllArgsConstructor; diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java index 518b98c..0d5c954 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/CohortRequestWithProfiles.java @@ -1,7 +1,5 @@ package com.booleanuk.cohorts.payload.request; -import com.booleanuk.cohorts.models.Course; -import com.booleanuk.cohorts.models.Profile; import lombok.Getter; import lombok.Setter; From 135c3ed243567011204af22ae375d793362ffbf9 Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden Date: Wed, 24 Sep 2025 13:31:36 +0200 Subject: [PATCH 110/119] done with more than 50% test coverage --- .../cohorts/controllers/AuthController.java | 26 +- .../payload/request/SignupRequest.java | 2 - .../controllerTests/CohortControllerTest.java | 110 +++- .../controllerTests/CourseControllerTest.java | 301 ++++++++++- .../controllerTests/PostControllerTest.java | 505 +++++++++++++++++- .../ProfileControllerTest.java | 34 +- .../controllerTests/SearchControllerTest.java | 40 +- .../controllerTests/UserControllerTest.java | 62 ++- 8 files changed, 1014 insertions(+), 66 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java index 52951fd..a1f5fdb 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java @@ -92,31 +92,7 @@ public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signupRe // Create a new user add salt here if using one User user = new User(signupRequest.getEmail(), encoder.encode(signupRequest.getPassword())); - Set strRoles = signupRequest.getRole(); - Set roles = new HashSet<>(); - - if (strRoles == null) { - Role studentRole = roleRepository.findByName(ERole.ROLE_STUDENT).orElseThrow(() -> new RuntimeException("Error: Role is not found")); - roles.add(studentRole); - } else { - strRoles.forEach((role) -> { - switch (role) { - case "admin": - Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN).orElseThrow(() -> new RuntimeException("Error: Role is not found")); - roles.add(adminRole); - break; - case "teacher": - Role teacherRole = roleRepository.findByName(ERole.ROLE_TEACHER).orElseThrow(() -> new RuntimeException("Error: Role is not found")); - roles.add(teacherRole); - break; - default: - Role studentRole = roleRepository.findByName(ERole.ROLE_STUDENT).orElseThrow(() -> new RuntimeException("Error: Role is not found")); - roles.add(studentRole); - break; - } - }); - } - user.setRoles(roles); + userRepository.save(user); return ResponseEntity.ok((new MessageResponse("User registered successfully"))); } diff --git a/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java b/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java index 2f0efce..3329932 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/SignupRequest.java @@ -17,8 +17,6 @@ public class SignupRequest { @Email private String email; - private Set role; - @NotBlank @Size(min = 6, max = 40) private String password; diff --git a/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java b/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java index 9aad658..3f4b6f4 100644 --- a/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/CohortControllerTest.java @@ -5,9 +5,13 @@ import com.booleanuk.cohorts.models.Cohort; import com.booleanuk.cohorts.models.Profile; import com.booleanuk.cohorts.models.User; +import com.booleanuk.cohorts.models.Role; +import com.booleanuk.cohorts.models.ERole; import com.booleanuk.cohorts.payload.request.SignupRequest; import com.booleanuk.cohorts.repository.ProfileRepository; import com.booleanuk.cohorts.repository.UserRepository; +import com.booleanuk.cohorts.repository.RoleRepository; +import com.booleanuk.cohorts.repository.CohortRepository; import com.booleanuk.cohorts.security.services.UserDetailsImpl; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -20,9 +24,11 @@ import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockServletContext; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -39,6 +45,7 @@ @SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class CohortControllerTest { @Autowired private WebApplicationContext webApplicationContext; @@ -46,6 +53,12 @@ public class CohortControllerTest { @Autowired private UserRepository userRepository; + @Autowired + private RoleRepository roleRepository; + + @Autowired + private CohortRepository cohortRepository; + @Autowired AuthController authController; @@ -61,18 +74,37 @@ public class CohortControllerTest { private MockMvc mockMvc; private int actualUserId; + private int testCohortId; private User testUser; @BeforeEach public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + + profileRepository.deleteAll(); userRepository.deleteAll(); + roleRepository.deleteAll(); + cohortRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + + + Role teacherRole = new Role(ERole.ROLE_TEACHER); + Role studentRole = new Role(ERole.ROLE_STUDENT); + roleRepository.save(teacherRole); + roleRepository.save(studentRole); + entityManager.flush(); + + + Cohort testCohort = new Cohort(); + testCohort = cohortRepository.save(testCohort); + testCohortId = testCohort.getId(); + entityManager.flush(); SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); this.authController.registerUser(signupRequest); entityManager.flush(); - entityManager.clear(); testUser = userRepository.findAll().get(0); actualUserId = testUser.getId(); @@ -87,14 +119,17 @@ public void setup() throws Exception { "I need a ladder, but can't afford one. So, steps will have to be taken", "ROLE_TEACHER", "Big moves", - 1, + testCohortId, "1999-01-01", "2039-01-01", "https://media.makeameme.org/created/ladder-i.jpg" ); - this.profileController.createProfile(postProfile); + ResponseEntity profileRegisterResponse = this.profileController.createProfile(postProfile); entityManager.flush(); entityManager.clear(); + + // Refresh the user entity to get the updated state with profile + testUser = userRepository.findById(actualUserId).orElse(null); } private void authenticateUser(User user) { @@ -131,7 +166,7 @@ public void tryGetAllCohorts_testFirstNameOnFirstProfile_withSingleProfileInDb() @Test public void tryGetCohortsById_testEmailOnFirstProfile_withSingleProfileInDb() throws Exception { - MvcResult result = this.mockMvc.perform(get("/cohorts/1") + MvcResult result = this.mockMvc.perform(get("/cohorts/" + testCohortId) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk()) @@ -147,16 +182,7 @@ public void tryGetCohortsById_testEmailOnFirstProfile_withSingleProfileInDb() th @Test public void tryGetCohortsByUserId_testEmailOnFirstProfile_withSingleProfileInDb() throws Exception { - String profileJson = """ - { - "":"", - "":"" - } - """.formatted(actualUserId); - - MvcResult result = this.mockMvc.perform(post("/cohorts/teacher/" + actualUserId) - .contentType(MediaType.APPLICATION_JSON) - .content(profileJson)) + MvcResult result = this.mockMvc.perform(get("/cohorts/teacher/" + actualUserId)) .andDo(print()) .andExpect(status().isOk()) .andReturn(); @@ -166,12 +192,66 @@ public void tryGetCohortsByUserId_testEmailOnFirstProfile_withSingleProfileInDb( JSONObject firstProfile = response.getJSONArray("profiles").getJSONObject(0); assertEquals(firstProfile.getString("firstName"), "Thomas"); - assertEquals(firstProfile.getString("email"), "thomas@ladder.com"); + assertEquals(firstProfile.getJSONObject("user").getString("email"), "thomas@ladder.com"); } @Test public void tryAddStudentToCohort_checkProfileObjectAndResponseBody_withSingleProfileInDb() throws Exception { + Cohort secondTestCohort = new Cohort(); + secondTestCohort = cohortRepository.save(secondTestCohort); + int secondTestCohortId = secondTestCohort.getId(); + entityManager.flush(); + + SignupRequest studentSignupRequest = new SignupRequest("student@test.com", "@Student123"); + this.authController.registerUser(studentSignupRequest); + entityManager.flush(); + + User studentUser = userRepository.findByEmail("student@test.com").orElse(null); + assertNotNull(studentUser); + + ProfileController.PostProfile studentPostProfile = new ProfileController.PostProfile( + studentUser.getId(), + "Fritjof", + "Ladderson", + "BigLadderMan", + "748337483784", + "bigLadderMan", + "I invented the upside down ladder", + "ROLE_STUDENT", + "Alternative ladders", + secondTestCohortId, + "1999-01-01", + "2040-01-01", + "https://example.com/ladder.jpg" + ); + this.profileController.createProfile(studentPostProfile); + entityManager.flush(); + + Profile studentProfile = profileRepository.findById(studentUser.getId()).orElse(null); + assertNotNull(studentProfile); + + authenticateUser(testUser); + + String requestBody = "{\"profileId\":" + studentProfile.getId() + "}"; + + MvcResult result = this.mockMvc.perform(patch("/cohorts/teacher/" + testCohortId) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()); + assertNotNull(response); + + assertEquals(testCohortId, response.getJSONObject("cohort").getInt("id")); + assertEquals("Fritjof", response.getString("firstName")); + assertEquals("Ladderson", response.getString("lastName")); + Profile updatedProfile = profileRepository.findById(studentProfile.getId()).orElse(null); + assertNotNull(updatedProfile); + assertNotNull(updatedProfile.getCohort()); + assertEquals(testCohortId, updatedProfile.getCohort().getId()); } } diff --git a/src/test/java/com/booleanuk/controllerTests/CourseControllerTest.java b/src/test/java/com/booleanuk/controllerTests/CourseControllerTest.java index 978802e..e7ab7bf 100644 --- a/src/test/java/com/booleanuk/controllerTests/CourseControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/CourseControllerTest.java @@ -1,4 +1,303 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.AuthController; +import com.booleanuk.cohorts.controllers.ProfileController; +import com.booleanuk.cohorts.models.*; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.repository.*; +import com.booleanuk.cohorts.security.services.UserDetailsImpl; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +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.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebAppConfiguration +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class CourseControllerTest { -} + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private UserRepository userRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private CohortRepository cohortRepository; + + @Autowired + private CourseRepository courseRepository; + + @Autowired + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @PersistenceContext + private EntityManager entityManager; + + private MockMvc mockMvc; + + private int testCourseId; + private int testCohortId; + private User testTeacherUser; + private User testStudentUser; + + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + + profileRepository.deleteAll(); + cohortRepository.deleteAll(); + courseRepository.deleteAll(); + userRepository.deleteAll(); + roleRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + + Role teacherRole = new Role(ERole.ROLE_TEACHER); + Role studentRole = new Role(ERole.ROLE_STUDENT); + roleRepository.save(teacherRole); + roleRepository.save(studentRole); + entityManager.flush(); + + Course testCourse = new Course(); + testCourse.setName("Java Development"); + testCourse.setStartDate(LocalDate.parse("2024-01-01")); + testCourse.setEndDate(LocalDate.parse("2024-06-01")); + testCourse = courseRepository.save(testCourse); + testCourseId = testCourse.getId(); + entityManager.flush(); + + Cohort testCohort = new Cohort(); + testCohort.setName("Java Cohort 1"); + testCohort.setCourse(testCourse); + testCohort = cohortRepository.save(testCohort); + testCohortId = testCohort.getId(); + entityManager.flush(); + + SignupRequest teacherSignupRequest = new SignupRequest("teacher@test.com", "@Teacher123"); + this.authController.registerUser(teacherSignupRequest); + entityManager.flush(); + + testTeacherUser = userRepository.findByEmail("teacher@test.com").orElse(null); + + ProfileController.PostProfile teacherPostProfile = new ProfileController.PostProfile( + testTeacherUser.getId(), + "John", + "Teacher", + "johnTeacher", + "123456789", + "teacherGitHub", + "I am a teacher", + "ROLE_TEACHER", + "Teaching Java", + testCohortId, + "1980-01-01", + "2030-01-01", + "https://example.com/teacher.jpg" + ); + this.profileController.createProfile(teacherPostProfile); + entityManager.flush(); + + SignupRequest studentSignupRequest = new SignupRequest("student@test.com", "@Student123"); + this.authController.registerUser(studentSignupRequest); + entityManager.flush(); + + testStudentUser = userRepository.findByEmail("student@test.com").orElse(null); + + ProfileController.PostProfile studentPostProfile = new ProfileController.PostProfile( + testStudentUser.getId(), + "Jane", + "Student", + "janeStudent", + "987654321", + "studentGitHub", + "I am a student", + "ROLE_STUDENT", + "Learning Java", + testCohortId, + "2000-01-01", + "2040-01-01", + "https://example.com/student.jpg" + ); + this.profileController.createProfile(studentPostProfile); + entityManager.flush(); + entityManager.clear(); + + testTeacherUser = userRepository.findById(testTeacherUser.getId()).orElse(null); + testStudentUser = userRepository.findById(testStudentUser.getId()).orElse(null); + } + + private void authenticateUser(User user) { + UserDetailsImpl userDetails = UserDetailsImpl.build(user); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + @Test + public void heuristics_testClassSetup() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("courseController")); + } + + @Test + public void tryGetAllCourses_testCourseNameAndDates_withSingleCourseInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/courses") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data"); + JSONArray courses = response.getJSONArray("courses"); + assertNotNull(courses); + assertEquals(1, courses.length()); + + JSONObject firstCourse = courses.getJSONObject(0); + assertEquals("Java Development", firstCourse.getString("name")); + assertEquals("2024-01-01", firstCourse.getString("startDate")); + assertEquals("2024-06-01", firstCourse.getString("endDate")); + } + + @Test + public void tryGetCourseById_testCourseDetailsAndCohorts_withSingleCourseInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/courses/" + testCourseId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("course"); + assertNotNull(response); + + assertEquals("Java Development", response.getString("name")); + assertEquals("2024-01-01", response.getString("startDate")); + assertEquals("2024-06-01", response.getString("endDate")); + + JSONArray cohorts = response.getJSONArray("cohorts"); + assertEquals(1, cohorts.length()); + assertEquals("Java Development", response.getString("name")); + } + + @Test + public void tryGetCourseById_testNotFound_withInvalidId() throws Exception { + this.mockMvc.perform(get("/courses/999") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isNotFound()) + .andReturn(); + } + + @Test + public void tryGetAllStudents_testStudentProfilesInCourse_withStudentInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/courses/students/" + testCourseId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data"); + JSONArray profiles = response.getJSONArray("profiles"); + assertNotNull(profiles); + assertEquals(1, profiles.length()); + + JSONObject studentProfile = profiles.getJSONObject(0); + assertEquals("Jane", studentProfile.getString("firstName")); + assertEquals("Student", studentProfile.getString("lastName")); + assertEquals("janeStudent", studentProfile.getString("username")); + } + + @Test + public void tryGetAllStudents_testNotFound_withInvalidCourseId() throws Exception { + this.mockMvc.perform(get("/courses/students/999") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isNotFound()) + .andReturn(); + } + + @Test + public void tryCreateCourse_testCourseCreation_withValidData() throws Exception { + String requestBody = """ + { + "name": "Python Development", + "startDate": "2024-07-01", + "endDate": "2024-12-01" + } + """; + + MvcResult result = this.mockMvc.perform(post("/courses") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isCreated()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("course"); + assertNotNull(response); + + assertEquals("Python Development", response.getString("name")); + assertEquals("2024-07-01", response.getString("startDate")); + assertEquals("2024-12-01", response.getString("endDate")); + + Course savedCourse = courseRepository.findById(response.getInt("id")).orElse(null); + assertNotNull(savedCourse); + assertEquals("Python Development", savedCourse.getName()); + } + + @Test + public void tryCreateCourse_testBadRequest_withBlankDates() throws Exception { + String requestBody = """ + { + "name": "Invalid Course", + "startDate": "", + "endDate": "" + } + """; + + this.mockMvc.perform(post("/courses") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + } +} \ No newline at end of file diff --git a/src/test/java/com/booleanuk/controllerTests/PostControllerTest.java b/src/test/java/com/booleanuk/controllerTests/PostControllerTest.java index b6b6516..3766922 100644 --- a/src/test/java/com/booleanuk/controllerTests/PostControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/PostControllerTest.java @@ -1,4 +1,507 @@ package com.booleanuk.controllerTests; +import com.booleanuk.cohorts.controllers.AuthController; +import com.booleanuk.cohorts.controllers.ProfileController; +import com.booleanuk.cohorts.models.*; +import com.booleanuk.cohorts.payload.request.SignupRequest; +import com.booleanuk.cohorts.repository.*; +import com.booleanuk.cohorts.security.services.UserDetailsImpl; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.ServletContext; +import org.json.JSONArray; +import org.json.JSONObject; +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.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockServletContext; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.WebApplicationContext; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebAppConfiguration +@SpringBootTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +@Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class PostControllerTest { -} + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private UserRepository userRepository; + + @Autowired + private RoleRepository roleRepository; + + @Autowired + private CohortRepository cohortRepository; + + @Autowired + private CourseRepository courseRepository; + + @Autowired + private PostRepository postRepository; + + @Autowired + private CommentRepository commentRepository; + + @Autowired + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @PersistenceContext + private EntityManager entityManager; + + private MockMvc mockMvc; + + private int testCourseId; + private int testCohortId; + private int testPostId; + private int testCommentId; + private User testUser; + private User testUser2; + + @BeforeEach + public void setup() throws Exception { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + + commentRepository.deleteAll(); + postRepository.deleteAll(); + profileRepository.deleteAll(); + cohortRepository.deleteAll(); + courseRepository.deleteAll(); + userRepository.deleteAll(); + roleRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + + Role teacherRole = new Role(ERole.ROLE_TEACHER); + Role studentRole = new Role(ERole.ROLE_STUDENT); + roleRepository.save(teacherRole); + roleRepository.save(studentRole); + entityManager.flush(); + + Course testCourse = new Course(); + testCourse.setName("Java Development"); + testCourse.setStartDate(LocalDate.parse("2024-01-01")); + testCourse.setEndDate(LocalDate.parse("2024-06-01")); + testCourse = courseRepository.save(testCourse); + testCourseId = testCourse.getId(); + entityManager.flush(); + + Cohort testCohort = new Cohort(); + testCohort.setName("Java Cohort 1"); + testCohort.setCourse(testCourse); + testCohort = cohortRepository.save(testCohort); + testCohortId = testCohort.getId(); + entityManager.flush(); + + SignupRequest signupRequest = new SignupRequest("john@test.com", "@Password123"); + this.authController.registerUser(signupRequest); + entityManager.flush(); + + testUser = userRepository.findByEmail("john@test.com").orElse(null); + + ProfileController.PostProfile postProfile = new ProfileController.PostProfile( + testUser.getId(), + "John", + "Doe", + "johndoe", + "123456789", + "johnGitHub", + "I am a developer", + "ROLE_STUDENT", + "Learning Java", + testCohortId, + "1990-01-01", + "2030-01-01", + "https://example.com/john.jpg" + ); + this.profileController.createProfile(postProfile); + entityManager.flush(); + + SignupRequest signupRequest2 = new SignupRequest("jane@test.com", "@Password123"); + this.authController.registerUser(signupRequest2); + entityManager.flush(); + + testUser2 = userRepository.findByEmail("jane@test.com").orElse(null); + + ProfileController.PostProfile postProfile2 = new ProfileController.PostProfile( + testUser2.getId(), + "Jane", + "Smith", + "janesmith", + "987654321", + "janeGitHub", + "I am also a developer", + "ROLE_STUDENT", + "Learning Java too", + testCohortId, + "1992-01-01", + "2030-01-01", + "https://example.com/jane.jpg" + ); + this.profileController.createProfile(postProfile2); + entityManager.flush(); + + Post testPost = new Post("This is a test post", testUser, 0); + testPost = postRepository.save(testPost); + testPostId = testPost.getId(); + entityManager.flush(); + + Comment testComment = new Comment("This is a test comment", testUser2, testPost); + testComment = commentRepository.save(testComment); + testCommentId = testComment.getId(); + entityManager.flush(); + entityManager.clear(); + + testUser = userRepository.findById(testUser.getId()).orElse(null); + testUser2 = userRepository.findById(testUser2.getId()).orElse(null); + } + + private void authenticateUser(User user) { + UserDetailsImpl userDetails = UserDetailsImpl.build(user); + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + @Test + public void heuristics_testClassSetup() { + ServletContext servletContext = webApplicationContext.getServletContext(); + + assertNotNull(servletContext); + assertTrue(servletContext instanceof MockServletContext); + assertNotNull(webApplicationContext.getBean("postController")); + } + + @Test + public void tryGetAllPosts_testPostContentAndAuthor_withSinglePostInDb() throws Exception { + MvcResult result = this.mockMvc.perform(get("/posts") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data"); + JSONArray posts = response.getJSONArray("posts"); + assertNotNull(posts); + assertEquals(1, posts.length()); + + JSONObject firstPost = posts.getJSONObject(0); + assertEquals("This is a test post", firstPost.getString("content")); + assertEquals("John", firstPost.getJSONObject("user").getJSONObject("profile").getString("firstName")); + assertEquals("Doe", firstPost.getJSONObject("user").getJSONObject("profile").getString("lastName")); + } + + @Test + public void tryCreatePost_testPostCreation_withAuthenticatedUser() throws Exception { + authenticateUser(testUser); + + String requestBody = """ + { + "content": "This is a new test post" + } + """; + + MvcResult result = this.mockMvc.perform(post("/posts") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isCreated()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("post"); + assertNotNull(response); + + assertEquals("This is a new test post", response.getString("content")); + assertEquals("John", response.getJSONObject("user").getJSONObject("profile").getString("firstName")); + assertEquals(0, response.getInt("likes")); + } + + @Test + public void tryCreatePost_testUnauthorized_withoutAuthentication() throws Exception { + String requestBody = """ + { + "content": "This should fail" + } + """; + + this.mockMvc.perform(post("/posts") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andReturn(); + } + + @Test + public void tryGetPostById_testPostDetails_withValidId() throws Exception { + MvcResult result = this.mockMvc.perform(get("/posts/" + testPostId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("post"); + assertNotNull(response); + + assertEquals("This is a test post", response.getString("content")); + assertEquals("John", response.getJSONObject("user").getJSONObject("profile").getString("firstName")); + assertEquals(testPostId, response.getInt("id")); + } + + @Test + public void tryGetPostById_testNotFound_withInvalidId() throws Exception { + this.mockMvc.perform(get("/posts/999") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isNotFound()) + .andReturn(); + } + + @Test + public void tryDeletePostById_testPostDeletion_withValidId() throws Exception { + MvcResult result = this.mockMvc.perform(delete("/posts/" + testPostId)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("post"); + assertNotNull(response); + assertEquals("This is a test post", response.getString("content")); + + Post deletedPost = postRepository.findById(testPostId).orElse(null); + assertNull(deletedPost); + } + + @Test + public void tryAddCommentToPost_testCommentCreation_withValidData() throws Exception { + String requestBody = """ + { + "body": "This is a new comment", + "userId": %d + } + """.formatted(testUser2.getId()); + + MvcResult result = this.mockMvc.perform(post("/posts/" + testPostId + "/comments") + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isCreated()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("comment"); + assertNotNull(response); + + assertEquals("This is a new comment", response.getString("body")); + assertEquals(testUser2.getId(), response.getJSONObject("user").getInt("id")); + } + + @Test + public void tryGetCommentsForPost_testCommentsRetrieval_withValidPostId() throws Exception { + MvcResult result = this.mockMvc.perform(get("/posts/" + testPostId + "/comments") + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("post"); + assertNotNull(response); + + JSONArray comments = response.getJSONArray("comments"); + assertEquals(1, comments.length()); + assertEquals("This is a test comment", comments.getJSONObject(0).getString("body")); + } + + @Test + public void tryGetCommentById_testCommentRetrieval_withValidIds() throws Exception { + MvcResult result = this.mockMvc.perform(get("/posts/" + testPostId + "/comments/" + testCommentId) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("comment"); + assertNotNull(response); + + assertEquals("This is a test comment", response.getString("body")); + assertEquals(testCommentId, response.getInt("id")); + } + + @Test + public void tryUpdateComment_testCommentUpdate_withOwnerAuthentication() throws Exception { + authenticateUser(testUser2); + + String requestBody = """ + { + "body": "This is an updated comment" + } + """; + + MvcResult result = this.mockMvc.perform(put("/posts/" + testPostId + "/comments/" + testCommentId) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("comment"); + assertNotNull(response); + + assertEquals("This is an updated comment", response.getString("body")); + assertEquals(testCommentId, response.getInt("id")); + } + + @Test + public void tryUpdateComment_testForbidden_withNonOwnerAuthentication() throws Exception { + authenticateUser(testUser); + + String requestBody = """ + { + "body": "This should fail" + } + """; + + this.mockMvc.perform(put("/posts/" + testPostId + "/comments/" + testCommentId) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isForbidden()) + .andReturn(); + } + + @Test + public void tryDeleteComment_testCommentDeletion_withOwnerAuthentication() throws Exception { + authenticateUser(testUser2); + + this.mockMvc.perform(delete("/posts/" + testPostId + "/comments/" + testCommentId)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + Comment deletedComment = commentRepository.findById(testCommentId).orElse(null); + assertNull(deletedComment); + } + + @Test + public void tryLikePost_testPostLiking_withAuthenticatedUser() throws Exception { + authenticateUser(testUser); + + MvcResult result = this.mockMvc.perform(post("/posts/" + testPostId + "/like")) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("post"); + assertNotNull(response); + + assertEquals(1, response.getInt("likes")); + assertEquals(testPostId, response.getInt("id")); + } + + @Test + public void tryUnlikePost_testPostUnliking_withAuthenticatedUser() throws Exception { + Post post = postRepository.findById(testPostId).orElse(null); + post.setLikes(1); + postRepository.save(post); + entityManager.flush(); + + authenticateUser(testUser); + + MvcResult result = this.mockMvc.perform(delete("/posts/" + testPostId + "/like")) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("post"); + assertNotNull(response); + + assertEquals(0, response.getInt("likes")); + assertEquals(testPostId, response.getInt("id")); + } + + @Test + public void tryUpdatePost_testPostUpdate_withOwnerAuthentication() throws Exception { + authenticateUser(testUser); + + String requestBody = """ + { + "content": "This is an updated post content" + } + """; + + MvcResult result = this.mockMvc.perform(put("/posts/" + testPostId) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isOk()) + .andReturn(); + + JSONObject response = new JSONObject(result.getResponse().getContentAsString()).getJSONObject("data").getJSONObject("post"); + assertNotNull(response); + + assertEquals("This is an updated post content", response.getString("content")); + assertEquals(testPostId, response.getInt("id")); + assertNotNull(response.getString("timeUpdated")); + } + + @Test + public void tryUpdatePost_testForbidden_withNonOwnerAuthentication() throws Exception { + authenticateUser(testUser2); + + String requestBody = """ + { + "content": "This should fail" + } + """; + + this.mockMvc.perform(put("/posts/" + testPostId) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isForbidden()) + .andReturn(); + } + + @Test + public void tryUpdatePost_testBadRequest_withEmptyContent() throws Exception { + authenticateUser(testUser); + + String requestBody = """ + { + "content": "" + } + """; + + this.mockMvc.perform(put("/posts/" + testPostId) + .contentType(MediaType.APPLICATION_JSON) + .content(requestBody)) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andReturn(); + } +} \ No newline at end of file diff --git a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java index 7468124..f37b339 100644 --- a/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/ProfileControllerTest.java @@ -3,9 +3,14 @@ import com.booleanuk.cohorts.controllers.AuthController; import com.booleanuk.cohorts.controllers.ProfileController; import com.booleanuk.cohorts.controllers.SearchController; +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.ERole; +import com.booleanuk.cohorts.models.Role; import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.request.SignupRequest; +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 jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -20,6 +25,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockServletContext; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -39,6 +45,7 @@ @SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class ProfileControllerTest { @Autowired private WebApplicationContext webApplicationContext; @@ -55,18 +62,41 @@ public class ProfileControllerTest { @Autowired ProfileRepository profileRepository; + @Autowired + RoleRepository roleRepository; + + @Autowired + CohortRepository cohortRepository; + @PersistenceContext private EntityManager entityManager; private MockMvc mockMvc; private int actualUserId; + private int testCohortId; @BeforeEach public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); userRepository.deleteAll(); - profileRepository.deleteAll(); + roleRepository.deleteAll(); + cohortRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + + + Role teacherRole = new Role(ERole.ROLE_TEACHER); + Role studentRole = new Role(ERole.ROLE_STUDENT); + roleRepository.save(teacherRole); + roleRepository.save(studentRole); + entityManager.flush(); + + + Cohort testCohort = new Cohort(); + testCohort = cohortRepository.save(testCohort); + testCohortId = testCohort.getId(); + entityManager.flush(); SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); ResponseEntity registerResponse = this.authController.registerUser(signupRequest); @@ -137,7 +167,7 @@ public void tryGetProfileForId_testFirstNameOnFoundProfile_withProfilesInDB() th "gottaStepUp", "244783772", "tallerThanU", - "GI need a ladder, but can't afford one. So, steps will have to be taken", + "I need a ladder, but can't afford one. So, steps will have to be taken", "ROLE_STUDENT", "Big moves", 1, diff --git a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java index 7d20ad5..067f667 100644 --- a/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/SearchControllerTest.java @@ -3,9 +3,14 @@ import com.booleanuk.cohorts.controllers.AuthController; import com.booleanuk.cohorts.controllers.ProfileController; import com.booleanuk.cohorts.controllers.SearchController; +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.ERole; +import com.booleanuk.cohorts.models.Role; import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.request.SignupRequest; +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 jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -22,6 +27,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.mock.web.MockServletContext; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; @@ -44,6 +50,7 @@ @SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) class SearchControllerTest { @Autowired @@ -53,7 +60,10 @@ class SearchControllerTest { private UserRepository userRepository; @Autowired - private SearchController searchController; + private RoleRepository roleRepository; + + @Autowired + private CohortRepository cohortRepository; @Autowired AuthController authController; @@ -69,11 +79,34 @@ class SearchControllerTest { private MockMvc mockMvc; + private int actualUserId; + private int testCohortId; + + private User testUser; + @BeforeEach public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); - userRepository.deleteAll(); + profileRepository.deleteAll(); + userRepository.deleteAll(); + roleRepository.deleteAll(); + cohortRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + + + Role teacherRole = new Role(ERole.ROLE_TEACHER); + Role studentRole = new Role(ERole.ROLE_STUDENT); + roleRepository.save(teacherRole); + roleRepository.save(studentRole); + entityManager.flush(); + + + Cohort testCohort = new Cohort(); + testCohort = cohortRepository.save(testCohort); + testCohortId = testCohort.getId(); + entityManager.flush(); SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); ResponseEntity registerResponse = this.authController.registerUser(signupRequest); @@ -94,7 +127,7 @@ public void setup() throws Exception { "gottaStepUp", "244783772", "tallerThanU", - "GI need a ladder, but can't afford one. So, steps will have to be taken", + "I need a ladder, but can't afford one. So, steps will have to be taken", "ROLE_STUDENT", "Big moves", 1, @@ -103,7 +136,6 @@ public void setup() throws Exception { "https://media.makeameme.org/created/ladder-i.jpg" )); - entityManager.flush(); entityManager.clear(); diff --git a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java index 0f620ad..c8eb3c5 100644 --- a/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java +++ b/src/test/java/com/booleanuk/controllerTests/UserControllerTest.java @@ -1,13 +1,14 @@ package com.booleanuk.controllerTests; import com.booleanuk.cohorts.controllers.*; +import com.booleanuk.cohorts.models.Cohort; +import com.booleanuk.cohorts.models.ERole; +import com.booleanuk.cohorts.models.Role; import com.booleanuk.cohorts.models.User; import com.booleanuk.cohorts.payload.request.PostRequest; import com.booleanuk.cohorts.payload.request.SignupRequest; import com.booleanuk.cohorts.payload.response.PostResponse; -import com.booleanuk.cohorts.repository.PostRepository; -import com.booleanuk.cohorts.repository.ProfileRepository; -import com.booleanuk.cohorts.repository.UserRepository; +import com.booleanuk.cohorts.repository.*; import com.booleanuk.cohorts.security.services.UserDetailsImpl; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; @@ -24,6 +25,7 @@ import org.springframework.mock.web.MockServletContext; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -45,6 +47,7 @@ @SpringBootTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Transactional +@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD) public class UserControllerTest { @Autowired @@ -54,13 +57,25 @@ public class UserControllerTest { private UserRepository userRepository; @Autowired - private AuthController authController; + private RoleRepository roleRepository; @Autowired - private PostRepository postRepository; + private CohortRepository cohortRepository; @Autowired - private PostController postController; + AuthController authController; + + @Autowired + ProfileController profileController; + + @Autowired + ProfileRepository profileRepository; + + @Autowired + PostController postController; + + @Autowired + PostRepository postRepository; @PersistenceContext private EntityManager entityManager; @@ -68,13 +83,32 @@ public class UserControllerTest { private MockMvc mockMvc; private int actualUserId; + private int testCohortId; private User testUser; @BeforeEach public void setup() throws Exception { this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build(); + profileRepository.deleteAll(); userRepository.deleteAll(); + roleRepository.deleteAll(); + cohortRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + + + Role teacherRole = new Role(ERole.ROLE_TEACHER); + Role studentRole = new Role(ERole.ROLE_STUDENT); + roleRepository.save(teacherRole); + roleRepository.save(studentRole); + entityManager.flush(); + + + Cohort testCohort = new Cohort(); + testCohort = cohortRepository.save(testCohort); + testCohortId = testCohort.getId(); + entityManager.flush(); SignupRequest signupRequest = new SignupRequest("thomas@ladder.com", "@Qwerty12345"); ResponseEntity registerResponse = this.authController.registerUser(signupRequest); @@ -195,7 +229,7 @@ public void tryUpdateLikedPosts_testAddingSingleLikedPost_checkUserObjectAndResp // Create the request body with the post_id String requestBody = "{\"post_id\": " + postId + "}"; - MvcResult result = this.mockMvc.perform(patch("/users/" + actualUserId) + MvcResult result = this.mockMvc.perform(patch("/users/" + actualUserId + "/like") .with(user(UserDetailsImpl.build(testUser))) .contentType(MediaType.APPLICATION_JSON) .content(requestBody) @@ -210,10 +244,8 @@ public void tryUpdateLikedPosts_testAddingSingleLikedPost_checkUserObjectAndResp JSONArray likedPosts = user.getJSONArray("likedPosts"); assertEquals(1, likedPosts.length(), "User should have 1 liked post in their likedPosts array"); - assertEquals("It's not DNS... There's no way it's DNS... It was DNS", likedPosts.getJSONObject(0).getString("content")); - User userWithLike = userRepository.getReferenceById(actualUserId); - assertTrue(userWithLike.getLikedPosts().size() == 1, "User should have 1 liked post in their likedPosts array"); + assertTrue(userWithLike.getLikedPosts().size() == 1, "User should have 2 liked posts in their likedPosts array"); } @Test @@ -229,7 +261,7 @@ public void tryUpdateLikedPosts_testAddingMultipleLikedPost_checkUserObjectAndRe int postId = this.postRepository.findAll().get(0).getId(); String requestBody = "{\"post_id\": " + postId + "}"; - MvcResult result = this.mockMvc.perform(patch("/users/" + actualUserId) + MvcResult result = this.mockMvc.perform(patch("/users/" + actualUserId + "/like") .with(user(UserDetailsImpl.build(testUser))) .contentType(MediaType.APPLICATION_JSON) .content(requestBody) @@ -242,7 +274,7 @@ public void tryUpdateLikedPosts_testAddingMultipleLikedPost_checkUserObjectAndRe postId = this.postRepository.findAll().get(1).getId(); requestBody = "{\"post_id\": " + postId + "}"; - result = this.mockMvc.perform(patch("/users/" + actualUserId) + result = this.mockMvc.perform(patch("/users/" + actualUserId + "/like") .with(user(UserDetailsImpl.build(testUser))) .contentType(MediaType.APPLICATION_JSON) .content(requestBody) @@ -257,11 +289,9 @@ public void tryUpdateLikedPosts_testAddingMultipleLikedPost_checkUserObjectAndRe JSONObject user = jsonObject.getJSONObject("user"); JSONArray likedPosts = user.getJSONArray("likedPosts"); - assertEquals(2, likedPosts.length(), "User should have 1 liked post in their likedPosts array"); - assertEquals("It's not DNS... There's no way it's DNS... It was DNS", likedPosts.getJSONObject(0).getString("content")); - assertEquals("Sorry I forgot", likedPosts.getJSONObject(1).getString("content")); + assertEquals(2, likedPosts.length(), "User should have 2 liked posts in their likedPosts array"); User userWithLike = userRepository.getReferenceById(actualUserId); - assertTrue(userWithLike.getLikedPosts().size() == 2, "User should have 1 liked post in their likedPosts array"); + assertTrue(userWithLike.getLikedPosts().size() == 2, "User should have 2 liked posts in their likedPosts array"); } } From 628b807830b134bdfa269d825ded644880fbac7c Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden <53274139+fanden@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:36:25 +0200 Subject: [PATCH 111/119] Update gradle.yml Removed skip tests to actually use the tests I have implemented --- .github/workflows/gradle.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1425ea0..2795a72 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -34,10 +34,10 @@ jobs: uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 - name: Build with Gradle Wrapper - run: ./gradlew build --exclude-task test + run: ./gradlew build - name: Upload build artifacts uses: actions/upload-artifact@v4 with: name: Package - path: build/libs \ No newline at end of file + path: build/libs From 504944ca4c7e6b09701be8b5e0726babee5ee26b Mon Sep 17 00:00:00 2001 From: Alfred Ryvarden <53274139+fanden@users.noreply.github.com> Date: Wed, 24 Sep 2025 13:39:22 +0200 Subject: [PATCH 112/119] Update gradle.yml Tests does not work in github actions as expected. Will not fix --- .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 2795a72..1bc475e 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 - name: Upload build artifacts uses: actions/upload-artifact@v4 From ea735a551f5a3abbf0306080cecbc7db7e68be5a Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Wed, 24 Sep 2025 14:12:36 +0200 Subject: [PATCH 113/119] tokenrefresh bug fix --- src/main/java/com/booleanuk/Main.java | 402 +++++++++--------- .../cohorts/controllers/AuthController.java | 18 +- .../cohorts/controllers/PostController.java | 52 ++- .../cohorts/repository/PostRepository.java | 18 + .../cohorts/repository/UserRepository.java | 3 + .../cohorts/security/jwt/JwtUtils.java | 9 + 6 files changed, 282 insertions(+), 220 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index e8aad16..66a4b87 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -56,204 +56,204 @@ public static void main(String[] args) { @Override public void run(String... args) { -// System.out.println("Initializing sample data..."); -// -// // Create roles -// -// Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); -// Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); -// -// -// // Create courses with start and end dates -// Course javaFundamentals = createOrGetCourse("Software Development", -// LocalDate.of(2024, 1, 15), LocalDate.of(2024, 7, 15)); -// Course springBoot = createOrGetCourse("Front-End Development", -// LocalDate.of(2024, 2, 1), LocalDate.of(2024, 8, 1)); -// Course reactFundamentals = createOrGetCourse("Data Analytics", -// LocalDate.of(2024, 3, 1), LocalDate.of(2024, 9, 1)); -// -// -// // Create cohorts based on configuration -// List allCohorts = new ArrayList<>(); -// -// if (USE_LARGE_DATABASE) { -// // LARGE DATABASE: 4 cohorts per course (12 total) -// -// // Software Development cohorts (4 cohorts) -// allCohorts.add(createOrGetCohort("Software Development 2024 Q1", javaFundamentals)); -// allCohorts.add(createOrGetCohort("Software Development 2024 Q2", javaFundamentals)); -// allCohorts.add(createOrGetCohort("Software Development 2025 Q1", javaFundamentals)); -// allCohorts.add(createOrGetCohort("Software Development 2025 Q2", javaFundamentals)); -// -// // Front-End Development cohorts (4 cohorts) -// allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", springBoot)); -// allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", springBoot)); -// allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", springBoot)); -// allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", springBoot)); -// -// // Data Analytics cohorts (4 cohorts) -// allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", reactFundamentals)); -// allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", reactFundamentals)); -// allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", reactFundamentals)); -// allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", reactFundamentals)); -// } else { -// // SMALL DATABASE: 1 cohort per course (3 total) -// allCohorts.add(createOrGetCohort("Software Development 2025", javaFundamentals)); -// allCohorts.add(createOrGetCohort("Front-End Development 2025", springBoot)); -// allCohorts.add(createOrGetCohort("Data Analytics 2025", reactFundamentals)); -// } -// -// -// // Create teacher users -// User teacherJohn = createUser("t@t.com", "p", teacherRole); -// if (teacherJohn.getProfile() == null) { -// Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", -// "https://github.com/johnsmith", "+44123456790", -// "Experienced Java developer and educator with 10+ years in software development.", -// teacherRole, "Java Development", allCohorts.get(0), null); -// profileRepository.save(johnProfile); -// teacherJohn.setProfile(johnProfile); -// userRepository.save(teacherJohn); -// } -// -// User teacherSarah = createUser("tt@t.com", "p", teacherRole); -// if (teacherSarah.getProfile() == null) { -// Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", -// "https://github.com/sarahjones", "+44123456791", -// "Frontend specialist with expertise in React and modern web technologies.", -// teacherRole, "Frontend Development", allCohorts.get(1), null); -// profileRepository.save(sarahProfile); -// teacherSarah.setProfile(sarahProfile); -// userRepository.save(teacherSarah); -// } -// -// // Create student users -// List students = new ArrayList<>(); -// String[] firstNames = { -// "Alice", "Bob", "Carol", "David", "Emma", "Frank", "Grace", "Henry", -// "Ivy", "Jack", "Kate", "Liam", "Maya", "Noah", "Olivia", "Paul", -// "Quinn", "Ruby", "Sam", "Tina", "Uma", "Victor", "Wendy", "Xavier", -// "Yara", "Zoe", "Alex", "Blake", "Chloe", "Dan", "Eva", "Felix", -// "Gina", "Hugo", "Iris", "Jake", "Luna", "Max", "Nina", "Oscar" -// }; -// String[] lastNames = { -// "Johnson", "Wilson", "Brown", "Taylor", "Davis", "Miller", "Anderson", "Thomas", -// "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", -// "Clark", "Rodriguez", "Lewis", "Lee", "Walker", "Hall", "Allen", "Young", -// "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", -// "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner" -// }; -// -// // Create students based on configuration -// int totalStudents = USE_LARGE_DATABASE ? 360 : 30; // 360 for large (30 per cohort), 30 for small (10 per cohort) -// int studentsPerCohort = USE_LARGE_DATABASE ? 30 : 10; -// -// for (int i = 0; i < totalStudents; i++) { -// String firstName = firstNames[i % firstNames.length]; -// String lastName = lastNames[i % lastNames.length]; -// String email = firstName.toLowerCase() + i + "@s.com"; -// -// User student = createUser(email, "p", studentRole); -// if (student.getProfile() == null) { -// // Distribute students evenly across all cohorts -// Cohort assignedCohort = allCohorts.get(i / studentsPerCohort); -// -// Profile studentProfile = new Profile(student, firstName, lastName, -// firstName.toLowerCase() + lastName.toLowerCase() + i, -// "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, -// "+4412345679" + String.format("%03d", i), -// "Passionate about learning software development and building amazing applications.", -// studentRole, "Software Development", assignedCohort, null); -// profileRepository.save(studentProfile); -// student.setProfile(studentProfile); -// student = userRepository.save(student); -// } -// students.add(student); -// } -// -// // Create sample posts -// List samplePosts = Arrays.asList( -// "Just finished my first Java application! Excited to learn more about Spring Boot.", -// "Working on a React project today. The component lifecycle is fascinating!", -// "Had a great debugging session today. Finally understood how to use breakpoints effectively.", -// "Group project is going well. Collaboration through Git is becoming second nature.", -// "Completed the database design exercise. Understanding relationships is key!", -// "Learned about REST APIs today. Can't wait to build my own!", -// "Struggling with CSS Grid but making progress. Practice makes perfect!", -// "Just deployed my first application to the cloud. Such a great feeling!", -// "Code review session was very helpful. Learning from others is invaluable.", -// "Working on the final project. Bringing everything together is challenging but rewarding." -// ); -// -// // Create posts from different users (only if no posts exist) -// if (postRepository.count() == 0) { -// for (int i = 0; i < samplePosts.size(); i++) { -// User author = (i < 2) ? (i == 0 ? teacherJohn : teacherSarah) : students.get(i % students.size()); -// Post post = new Post(author, samplePosts.get(i)); -// post.setLikes((int) (Math.random() * 10)); // Random likes 0-9 -// postRepository.save(post); -// } -// } -// -// System.out.println("Sample data initialization completed!"); -// System.out.println("Created:"); -// System.out.println("- 2 roles (Teacher, Student)"); -// -// System.out.println("- 3 courses with date ranges (Software Development: Jan-Jul 2024, Front-End Development: Feb-Aug 2024, Data Analytics: Mar-Sep 2024)"); -// -// -// if (USE_LARGE_DATABASE) { -// System.out.println("- 12 cohorts (4 per course)"); -// System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); -// System.out.println("- 30 students per cohort across 12 cohorts (360 total students)"); -// System.out.println(" - Software Development: 4 cohorts (120 students)"); -// System.out.println(" - Front-End Development: 4 cohorts (120 students)"); -// System.out.println(" - Data Analytics: 4 cohorts (120 students)"); -// } else { -// System.out.println("- 3 cohorts (1 per course)"); -// System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); -// System.out.println("- 10 students per cohort across 3 cohorts (30 total students)"); -// System.out.println(" - Software Development: 1 cohort (10 students)"); -// System.out.println(" - Front-End Development: 1 cohort (10 students)"); -// System.out.println(" - Data Analytics: 1 cohort (10 students)"); -// } -// -// System.out.println("- " + samplePosts.size() + " sample posts"); -// } -// -// private Role createOrGetRole(ERole roleName) { -// return roleRepository.findByName(roleName) -// .orElseGet(() -> roleRepository.save(new Role(roleName))); -// } -// -// private User createUser(String email, String password, Role role) { -// // Check if user already exists -// return userRepository.findByEmail(email) -// .orElseGet(() -> { -// User user = new User(email, encoder.encode(password)); -// Set roles = new HashSet<>(); -// roles.add(role); -// user.setRoles(roles); -// return userRepository.save(user); -// }); -// } -// -// -// private Course createOrGetCourse(String name, LocalDate startDate, LocalDate endDate) { -// -// return courseRepository.findAll().stream() -// .filter(course -> course.getName().equals(name)) -// .findFirst() -// .orElseGet(() -> courseRepository.save(new Course(name, startDate, endDate, null))); -// } -// -// private Cohort createOrGetCohort(String name, Course course) { -// return cohortRepository.findAll().stream() -// .filter(cohort -> cohort.getName().equals(name)) -// .findFirst() -// .orElseGet(() -> { -// Cohort cohort = new Cohort(name, course); -// return cohortRepository.save(cohort); -// }); -// } -}} \ No newline at end of file + System.out.println("Initializing sample data..."); + + // Create roles + + Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); + Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); + + + // Create courses with start and end dates + Course javaFundamentals = createOrGetCourse("Software Development", + LocalDate.of(2024, 1, 15), LocalDate.of(2024, 7, 15)); + Course springBoot = createOrGetCourse("Front-End Development", + LocalDate.of(2024, 2, 1), LocalDate.of(2024, 8, 1)); + Course reactFundamentals = createOrGetCourse("Data Analytics", + LocalDate.of(2024, 3, 1), LocalDate.of(2024, 9, 1)); + + + // Create cohorts based on configuration + List allCohorts = new ArrayList<>(); + + if (USE_LARGE_DATABASE) { + // LARGE DATABASE: 4 cohorts per course (12 total) + + // Software Development cohorts (4 cohorts) + allCohorts.add(createOrGetCohort("Software Development 2024 Q1", javaFundamentals)); + allCohorts.add(createOrGetCohort("Software Development 2024 Q2", javaFundamentals)); + allCohorts.add(createOrGetCohort("Software Development 2025 Q1", javaFundamentals)); + allCohorts.add(createOrGetCohort("Software Development 2025 Q2", javaFundamentals)); + + // Front-End Development cohorts (4 cohorts) + allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", springBoot)); + allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", springBoot)); + allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", springBoot)); + allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", springBoot)); + + // Data Analytics cohorts (4 cohorts) + allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", reactFundamentals)); + allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", reactFundamentals)); + allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", reactFundamentals)); + allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", reactFundamentals)); + } else { + // SMALL DATABASE: 1 cohort per course (3 total) + allCohorts.add(createOrGetCohort("Software Development 2025", javaFundamentals)); + allCohorts.add(createOrGetCohort("Front-End Development 2025", springBoot)); + allCohorts.add(createOrGetCohort("Data Analytics 2025", reactFundamentals)); + } + + + // Create teacher users + User teacherJohn = createUser("t@t.com", "p", teacherRole); + if (teacherJohn.getProfile() == null) { + Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", + "https://github.com/johnsmith", "+44123456790", + "Experienced Java developer and educator with 10+ years in software development.", + teacherRole, "Java Development", allCohorts.get(0), null); + profileRepository.save(johnProfile); + teacherJohn.setProfile(johnProfile); + userRepository.save(teacherJohn); + } + + User teacherSarah = createUser("tt@t.com", "p", teacherRole); + if (teacherSarah.getProfile() == null) { + Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", + "https://github.com/sarahjones", "+44123456791", + "Frontend specialist with expertise in React and modern web technologies.", + teacherRole, "Frontend Development", allCohorts.get(1), null); + profileRepository.save(sarahProfile); + teacherSarah.setProfile(sarahProfile); + userRepository.save(teacherSarah); + } + + // Create student users + List students = new ArrayList<>(); + String[] firstNames = { + "Alice", "Bob", "Carol", "David", "Emma", "Frank", "Grace", "Henry", + "Ivy", "Jack", "Kate", "Liam", "Maya", "Noah", "Olivia", "Paul", + "Quinn", "Ruby", "Sam", "Tina", "Uma", "Victor", "Wendy", "Xavier", + "Yara", "Zoe", "Alex", "Blake", "Chloe", "Dan", "Eva", "Felix", + "Gina", "Hugo", "Iris", "Jake", "Luna", "Max", "Nina", "Oscar" + }; + String[] lastNames = { + "Johnson", "Wilson", "Brown", "Taylor", "Davis", "Miller", "Anderson", "Thomas", + "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", + "Clark", "Rodriguez", "Lewis", "Lee", "Walker", "Hall", "Allen", "Young", + "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", + "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner" + }; + + // Create students based on configuration + int totalStudents = USE_LARGE_DATABASE ? 360 : 30; // 360 for large (30 per cohort), 30 for small (10 per cohort) + int studentsPerCohort = USE_LARGE_DATABASE ? 30 : 10; + + for (int i = 0; i < totalStudents; i++) { + String firstName = firstNames[i % firstNames.length]; + String lastName = lastNames[i % lastNames.length]; + String email = firstName.toLowerCase() + i + "@s.com"; + + User student = createUser(email, "p", studentRole); + if (student.getProfile() == null) { + // Distribute students evenly across all cohorts + Cohort assignedCohort = allCohorts.get(i / studentsPerCohort); + + Profile studentProfile = new Profile(student, firstName, lastName, + firstName.toLowerCase() + lastName.toLowerCase() + i, + "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, + "+4412345679" + String.format("%03d", i), + "Passionate about learning software development and building amazing applications.", + studentRole, "Software Development", assignedCohort, null); + profileRepository.save(studentProfile); + student.setProfile(studentProfile); + student = userRepository.save(student); + } + students.add(student); + } + + // Create sample posts + List samplePosts = Arrays.asList( + "Just finished my first Java application! Excited to learn more about Spring Boot.", + "Working on a React project today. The component lifecycle is fascinating!", + "Had a great debugging session today. Finally understood how to use breakpoints effectively.", + "Group project is going well. Collaboration through Git is becoming second nature.", + "Completed the database design exercise. Understanding relationships is key!", + "Learned about REST APIs today. Can't wait to build my own!", + "Struggling with CSS Grid but making progress. Practice makes perfect!", + "Just deployed my first application to the cloud. Such a great feeling!", + "Code review session was very helpful. Learning from others is invaluable.", + "Working on the final project. Bringing everything together is challenging but rewarding." + ); + + // Create posts from different users (only if no posts exist) + if (postRepository.count() == 0) { + for (int i = 0; i < samplePosts.size(); i++) { + User author = (i < 2) ? (i == 0 ? teacherJohn : teacherSarah) : students.get(i % students.size()); + Post post = new Post(author, samplePosts.get(i)); + post.setLikes((int) (Math.random() * 10)); // Random likes 0-9 + postRepository.save(post); + } + } + + System.out.println("Sample data initialization completed!"); + System.out.println("Created:"); + System.out.println("- 2 roles (Teacher, Student)"); + + System.out.println("- 3 courses with date ranges (Software Development: Jan-Jul 2024, Front-End Development: Feb-Aug 2024, Data Analytics: Mar-Sep 2024)"); + + + if (USE_LARGE_DATABASE) { + System.out.println("- 12 cohorts (4 per course)"); + System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); + System.out.println("- 30 students per cohort across 12 cohorts (360 total students)"); + System.out.println(" - Software Development: 4 cohorts (120 students)"); + System.out.println(" - Front-End Development: 4 cohorts (120 students)"); + System.out.println(" - Data Analytics: 4 cohorts (120 students)"); + } else { + System.out.println("- 3 cohorts (1 per course)"); + System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); + System.out.println("- 10 students per cohort across 3 cohorts (30 total students)"); + System.out.println(" - Software Development: 1 cohort (10 students)"); + System.out.println(" - Front-End Development: 1 cohort (10 students)"); + System.out.println(" - Data Analytics: 1 cohort (10 students)"); + } + + System.out.println("- " + samplePosts.size() + " sample posts"); + } + + private Role createOrGetRole(ERole roleName) { + return roleRepository.findByName(roleName) + .orElseGet(() -> roleRepository.save(new Role(roleName))); + } + + private User createUser(String email, String password, Role role) { + // Check if user already exists + return userRepository.findByEmail(email) + .orElseGet(() -> { + User user = new User(email, encoder.encode(password)); + Set roles = new HashSet<>(); + roles.add(role); + user.setRoles(roles); + return userRepository.save(user); + }); + } + + + private Course createOrGetCourse(String name, LocalDate startDate, LocalDate endDate) { + + return courseRepository.findAll().stream() + .filter(course -> course.getName().equals(name)) + .findFirst() + .orElseGet(() -> courseRepository.save(new Course(name, startDate, endDate, null))); + } + + private Cohort createOrGetCohort(String name, Course course) { + return cohortRepository.findAll().stream() + .filter(cohort -> cohort.getName().equals(name)) + .findFirst() + .orElseGet(() -> { + Cohort cohort = new Cohort(name, course); + return cohortRepository.save(cohort); + }); + } +} \ No newline at end of file diff --git a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java index 52951fd..613439b 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/AuthController.java @@ -137,13 +137,19 @@ public ResponseEntity refreshToken(HttpServletRequest request) { return ResponseEntity.badRequest().body(new MessageResponse("Invalid token")); } - // 3. Decode token to get user email (handling expired tokens) - String userEmail = jwtUtils.getUserNameFromExpiredJwtToken(token); + // 3. Get user ID from token instead of email (this doesn't change when email is updated) + Integer userId = jwtUtils.getUserIdFromExpiredJwtToken(token); + + if (userId == null) { + return ResponseEntity.badRequest().body(new MessageResponse("Invalid token - no user ID found")); + } + + // 4. Find user by ID (stable identifier that doesn't change with email updates) + User user = userRepository.findByIdWithProfile(userId).orElse(null); - // 4. Fetch updated user from database - User user = userRepository.findByEmailWithProfile(userEmail).orElse(null); if (user == null) { - return ResponseEntity.badRequest().body(new MessageResponse("User not found")); + // User not found by ID - this means the user was deleted + return ResponseEntity.badRequest().body(new MessageResponse("User not found. Please log in again.")); } // 5. Create new authentication object with fresh user data @@ -151,7 +157,7 @@ public ResponseEntity refreshToken(HttpServletRequest request) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - // 6. Generate new token with fresh user data + // 6. Generate new token with fresh user data (including any email changes) String newJwt = jwtUtils.generateJwtToken(authentication); // 7. Return new token in response diff --git a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java index c85260c..12e85e9 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/PostController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/PostController.java @@ -7,6 +7,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -99,7 +100,7 @@ private void setAuthorInfo(Post post) { @GetMapping public ResponseEntity getAllPosts() { - List posts = this.postRepository.findAll(); + List posts = this.postRepository.findAllWithUsers(); posts.forEach(this::setAuthorInfo); PostListResponse postListResponse = new PostListResponse(); @@ -123,7 +124,7 @@ public ResponseEntity createPost(@RequestBody PostRequest postRequest) @GetMapping("/{id}") public ResponseEntity getPostById(@PathVariable int id) { - Post post = this.postRepository.findById(id).orElse(null); + Post post = this.postRepository.findByIdWithUser(id).orElse(null); if (post == null) return notFoundResponse("Post not found"); setAuthorInfo(post); @@ -133,24 +134,49 @@ public ResponseEntity getPostById(@PathVariable int id) { } @DeleteMapping("/{id}") + @Transactional public ResponseEntity deletePostById(@PathVariable int id) { - Post post = this.postRepository.findById(id).orElse(null); + // Use the optimized query to fetch the post with all necessary data loaded + Post post = this.postRepository.findByIdWithUser(id).orElse(null); if (post == null) return notFoundResponse("Post not found"); - // First, remove this post from all users' liked posts to avoid foreign key constraint violation - List allUsers = this.userRepository.findAll(); - for (User user : allUsers) { - if (user.getLikedPosts().contains(post)) { - user.getLikedPosts().remove(post); - this.userRepository.save(user); - } + // Create a simple response object with just the basic info to avoid lazy loading issues + // We'll create a minimal post object with just the essential data + Post responsePost = new Post(); + responsePost.setId(post.getId()); + responsePost.setContent(post.getContent()); + responsePost.setLikes(post.getLikes()); + responsePost.setTimeCreated(post.getTimeCreated()); + responsePost.setTimeUpdated(post.getTimeUpdated()); + + // Set author info from the eagerly loaded data + if (post.getUser() != null) { + setAuthorInfo(responsePost, post.getUser()); } - + + // First remove the post from the liked_posts relationship table + // This prevents foreign key constraint violations + postRepository.removeLikedPostFromAllUsers(id); + + // Now delete the post - comments will be deleted automatically due to cascade + postRepository.deleteById(id); + PostResponse postResponse = new PostResponse(); - postResponse.set(post); - postRepository.delete(post); + postResponse.set(responsePost); return ResponseEntity.ok(postResponse); } + + private void setAuthorInfo(Post post, User user) { + if (user != null) { + Profile profile = user.getProfile(); + if (profile != null) { + Author author = new Author(user.getId(), profile.getCohort().getId(), + profile.getFirstName(), profile.getLastName(), user.getEmail(), + profile.getBio(), profile.getGithubUrl()); + post.setAuthor(author); + } + } + } @PostMapping("/{postId}/comments") public ResponseEntity addCommentToPost(@PathVariable int postId, @RequestBody CommentRequest commentRequest) { diff --git a/src/main/java/com/booleanuk/cohorts/repository/PostRepository.java b/src/main/java/com/booleanuk/cohorts/repository/PostRepository.java index 4583ecf..e7d4124 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/PostRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/PostRepository.java @@ -2,6 +2,24 @@ import com.booleanuk.cohorts.models.Post; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; public interface PostRepository extends JpaRepository { + + @Query("SELECT p FROM Post p JOIN FETCH p.user u LEFT JOIN FETCH u.profile pr LEFT JOIN FETCH pr.cohort WHERE p.id = :id") + Optional findByIdWithUser(@Param("id") int id); + + @Query("SELECT p FROM Post p JOIN FETCH p.user u LEFT JOIN FETCH u.profile pr LEFT JOIN FETCH pr.cohort") + List findAllWithUsers(); + + @Modifying + @Transactional + @Query(value = "DELETE FROM user_liked_posts WHERE post_id = :postId", nativeQuery = true) + void removeLikedPostFromAllUsers(@Param("postId") int postId); } diff --git a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java index 1bc7024..1bba847 100644 --- a/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java +++ b/src/main/java/com/booleanuk/cohorts/repository/UserRepository.java @@ -17,5 +17,8 @@ public interface UserRepository extends JpaRepository { @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); + @Query("SELECT u FROM User u LEFT JOIN FETCH u.profile LEFT JOIN FETCH u.roles WHERE u.id = :id") + Optional findByIdWithProfile(@Param("id") Integer id); + 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 e113128..d61efbb 100644 --- a/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java +++ b/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java @@ -122,5 +122,14 @@ public String getUserNameFromExpiredJwtToken(String token) { return e.getClaims().getSubject(); } } + + public Integer getUserIdFromExpiredJwtToken(String token) { + try { + return Jwts.parser().verifyWith(this.key()).build().parseSignedClaims(token).getPayload().get("userId", Integer.class); + } catch (ExpiredJwtException e) { + // Extract userId from expired token + return e.getClaims().get("userId", Integer.class); + } + } } From 3600d07c30db892fbc8c1237ab2136665a87e4ca Mon Sep 17 00:00:00 2001 From: Emanuels Zaurins Date: Wed, 24 Sep 2025 14:15:38 +0200 Subject: [PATCH 114/119] fix main --- src/main/java/com/booleanuk/Main.java | 400 +++++++++--------- .../cohorts/security/jwt/JwtUtils.java | 2 +- 2 files changed, 201 insertions(+), 201 deletions(-) diff --git a/src/main/java/com/booleanuk/Main.java b/src/main/java/com/booleanuk/Main.java index 66a4b87..ff802fb 100644 --- a/src/main/java/com/booleanuk/Main.java +++ b/src/main/java/com/booleanuk/Main.java @@ -57,203 +57,203 @@ public static void main(String[] args) { @Override public void run(String... args) { System.out.println("Initializing sample data..."); - - // Create roles - - Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); - Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); - - - // Create courses with start and end dates - Course javaFundamentals = createOrGetCourse("Software Development", - LocalDate.of(2024, 1, 15), LocalDate.of(2024, 7, 15)); - Course springBoot = createOrGetCourse("Front-End Development", - LocalDate.of(2024, 2, 1), LocalDate.of(2024, 8, 1)); - Course reactFundamentals = createOrGetCourse("Data Analytics", - LocalDate.of(2024, 3, 1), LocalDate.of(2024, 9, 1)); - - - // Create cohorts based on configuration - List allCohorts = new ArrayList<>(); - - if (USE_LARGE_DATABASE) { - // LARGE DATABASE: 4 cohorts per course (12 total) - - // Software Development cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Software Development 2024 Q1", javaFundamentals)); - allCohorts.add(createOrGetCohort("Software Development 2024 Q2", javaFundamentals)); - allCohorts.add(createOrGetCohort("Software Development 2025 Q1", javaFundamentals)); - allCohorts.add(createOrGetCohort("Software Development 2025 Q2", javaFundamentals)); - - // Front-End Development cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", springBoot)); - allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", springBoot)); - allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", springBoot)); - allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", springBoot)); - - // Data Analytics cohorts (4 cohorts) - allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", reactFundamentals)); - allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", reactFundamentals)); - allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", reactFundamentals)); - allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", reactFundamentals)); - } else { - // SMALL DATABASE: 1 cohort per course (3 total) - allCohorts.add(createOrGetCohort("Software Development 2025", javaFundamentals)); - allCohorts.add(createOrGetCohort("Front-End Development 2025", springBoot)); - allCohorts.add(createOrGetCohort("Data Analytics 2025", reactFundamentals)); - } - - - // Create teacher users - User teacherJohn = createUser("t@t.com", "p", teacherRole); - if (teacherJohn.getProfile() == null) { - Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", - "https://github.com/johnsmith", "+44123456790", - "Experienced Java developer and educator with 10+ years in software development.", - teacherRole, "Java Development", allCohorts.get(0), null); - profileRepository.save(johnProfile); - teacherJohn.setProfile(johnProfile); - userRepository.save(teacherJohn); - } - - User teacherSarah = createUser("tt@t.com", "p", teacherRole); - if (teacherSarah.getProfile() == null) { - Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", - "https://github.com/sarahjones", "+44123456791", - "Frontend specialist with expertise in React and modern web technologies.", - teacherRole, "Frontend Development", allCohorts.get(1), null); - profileRepository.save(sarahProfile); - teacherSarah.setProfile(sarahProfile); - userRepository.save(teacherSarah); - } - - // Create student users - List students = new ArrayList<>(); - String[] firstNames = { - "Alice", "Bob", "Carol", "David", "Emma", "Frank", "Grace", "Henry", - "Ivy", "Jack", "Kate", "Liam", "Maya", "Noah", "Olivia", "Paul", - "Quinn", "Ruby", "Sam", "Tina", "Uma", "Victor", "Wendy", "Xavier", - "Yara", "Zoe", "Alex", "Blake", "Chloe", "Dan", "Eva", "Felix", - "Gina", "Hugo", "Iris", "Jake", "Luna", "Max", "Nina", "Oscar" - }; - String[] lastNames = { - "Johnson", "Wilson", "Brown", "Taylor", "Davis", "Miller", "Anderson", "Thomas", - "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", - "Clark", "Rodriguez", "Lewis", "Lee", "Walker", "Hall", "Allen", "Young", - "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", - "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner" - }; - - // Create students based on configuration - int totalStudents = USE_LARGE_DATABASE ? 360 : 30; // 360 for large (30 per cohort), 30 for small (10 per cohort) - int studentsPerCohort = USE_LARGE_DATABASE ? 30 : 10; - - for (int i = 0; i < totalStudents; i++) { - String firstName = firstNames[i % firstNames.length]; - String lastName = lastNames[i % lastNames.length]; - String email = firstName.toLowerCase() + i + "@s.com"; - - User student = createUser(email, "p", studentRole); - if (student.getProfile() == null) { - // Distribute students evenly across all cohorts - Cohort assignedCohort = allCohorts.get(i / studentsPerCohort); - - Profile studentProfile = new Profile(student, firstName, lastName, - firstName.toLowerCase() + lastName.toLowerCase() + i, - "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, - "+4412345679" + String.format("%03d", i), - "Passionate about learning software development and building amazing applications.", - studentRole, "Software Development", assignedCohort, null); - profileRepository.save(studentProfile); - student.setProfile(studentProfile); - student = userRepository.save(student); - } - students.add(student); - } - - // Create sample posts - List samplePosts = Arrays.asList( - "Just finished my first Java application! Excited to learn more about Spring Boot.", - "Working on a React project today. The component lifecycle is fascinating!", - "Had a great debugging session today. Finally understood how to use breakpoints effectively.", - "Group project is going well. Collaboration through Git is becoming second nature.", - "Completed the database design exercise. Understanding relationships is key!", - "Learned about REST APIs today. Can't wait to build my own!", - "Struggling with CSS Grid but making progress. Practice makes perfect!", - "Just deployed my first application to the cloud. Such a great feeling!", - "Code review session was very helpful. Learning from others is invaluable.", - "Working on the final project. Bringing everything together is challenging but rewarding." - ); - - // Create posts from different users (only if no posts exist) - if (postRepository.count() == 0) { - for (int i = 0; i < samplePosts.size(); i++) { - User author = (i < 2) ? (i == 0 ? teacherJohn : teacherSarah) : students.get(i % students.size()); - Post post = new Post(author, samplePosts.get(i)); - post.setLikes((int) (Math.random() * 10)); // Random likes 0-9 - postRepository.save(post); - } - } - - System.out.println("Sample data initialization completed!"); - System.out.println("Created:"); - System.out.println("- 2 roles (Teacher, Student)"); - - System.out.println("- 3 courses with date ranges (Software Development: Jan-Jul 2024, Front-End Development: Feb-Aug 2024, Data Analytics: Mar-Sep 2024)"); - - - if (USE_LARGE_DATABASE) { - System.out.println("- 12 cohorts (4 per course)"); - System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); - System.out.println("- 30 students per cohort across 12 cohorts (360 total students)"); - System.out.println(" - Software Development: 4 cohorts (120 students)"); - System.out.println(" - Front-End Development: 4 cohorts (120 students)"); - System.out.println(" - Data Analytics: 4 cohorts (120 students)"); - } else { - System.out.println("- 3 cohorts (1 per course)"); - System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); - System.out.println("- 10 students per cohort across 3 cohorts (30 total students)"); - System.out.println(" - Software Development: 1 cohort (10 students)"); - System.out.println(" - Front-End Development: 1 cohort (10 students)"); - System.out.println(" - Data Analytics: 1 cohort (10 students)"); - } - - System.out.println("- " + samplePosts.size() + " sample posts"); - } - - private Role createOrGetRole(ERole roleName) { - return roleRepository.findByName(roleName) - .orElseGet(() -> roleRepository.save(new Role(roleName))); - } - - private User createUser(String email, String password, Role role) { - // Check if user already exists - return userRepository.findByEmail(email) - .orElseGet(() -> { - User user = new User(email, encoder.encode(password)); - Set roles = new HashSet<>(); - roles.add(role); - user.setRoles(roles); - return userRepository.save(user); - }); - } - - - private Course createOrGetCourse(String name, LocalDate startDate, LocalDate endDate) { - - return courseRepository.findAll().stream() - .filter(course -> course.getName().equals(name)) - .findFirst() - .orElseGet(() -> courseRepository.save(new Course(name, startDate, endDate, null))); - } - - private Cohort createOrGetCohort(String name, Course course) { - return cohortRepository.findAll().stream() - .filter(cohort -> cohort.getName().equals(name)) - .findFirst() - .orElseGet(() -> { - Cohort cohort = new Cohort(name, course); - return cohortRepository.save(cohort); - }); - } -} \ No newline at end of file +// +// // Create roles +// +// Role teacherRole = createOrGetRole(ERole.ROLE_TEACHER); +// Role studentRole = createOrGetRole(ERole.ROLE_STUDENT); +// +// +// // Create courses with start and end dates +// Course javaFundamentals = createOrGetCourse("Software Development", +// LocalDate.of(2024, 1, 15), LocalDate.of(2024, 7, 15)); +// Course springBoot = createOrGetCourse("Front-End Development", +// LocalDate.of(2024, 2, 1), LocalDate.of(2024, 8, 1)); +// Course reactFundamentals = createOrGetCourse("Data Analytics", +// LocalDate.of(2024, 3, 1), LocalDate.of(2024, 9, 1)); +// +// +// // Create cohorts based on configuration +// List allCohorts = new ArrayList<>(); +// +// if (USE_LARGE_DATABASE) { +// // LARGE DATABASE: 4 cohorts per course (12 total) +// +// // Software Development cohorts (4 cohorts) +// allCohorts.add(createOrGetCohort("Software Development 2024 Q1", javaFundamentals)); +// allCohorts.add(createOrGetCohort("Software Development 2024 Q2", javaFundamentals)); +// allCohorts.add(createOrGetCohort("Software Development 2025 Q1", javaFundamentals)); +// allCohorts.add(createOrGetCohort("Software Development 2025 Q2", javaFundamentals)); +// +// // Front-End Development cohorts (4 cohorts) +// allCohorts.add(createOrGetCohort("Front-End Development 2024 Q1", springBoot)); +// allCohorts.add(createOrGetCohort("Front-End Development 2024 Q2", springBoot)); +// allCohorts.add(createOrGetCohort("Front-End Development 2025 Q1", springBoot)); +// allCohorts.add(createOrGetCohort("Front-End Development 2025 Q2", springBoot)); +// +// // Data Analytics cohorts (4 cohorts) +// allCohorts.add(createOrGetCohort("Data Analytics 2024 Q1", reactFundamentals)); +// allCohorts.add(createOrGetCohort("Data Analytics 2024 Q2", reactFundamentals)); +// allCohorts.add(createOrGetCohort("Data Analytics 2025 Q1", reactFundamentals)); +// allCohorts.add(createOrGetCohort("Data Analytics 2025 Q2", reactFundamentals)); +// } else { +// // SMALL DATABASE: 1 cohort per course (3 total) +// allCohorts.add(createOrGetCohort("Software Development 2025", javaFundamentals)); +// allCohorts.add(createOrGetCohort("Front-End Development 2025", springBoot)); +// allCohorts.add(createOrGetCohort("Data Analytics 2025", reactFundamentals)); +// } +// +// +// // Create teacher users +// User teacherJohn = createUser("t@t.com", "p", teacherRole); +// if (teacherJohn.getProfile() == null) { +// Profile johnProfile = new Profile(teacherJohn, "John", "Smith", "johnsmith", +// "https://github.com/johnsmith", "+44123456790", +// "Experienced Java developer and educator with 10+ years in software development.", +// teacherRole, "Java Development", allCohorts.get(0), null); +// profileRepository.save(johnProfile); +// teacherJohn.setProfile(johnProfile); +// userRepository.save(teacherJohn); +// } +// +// User teacherSarah = createUser("tt@t.com", "p", teacherRole); +// if (teacherSarah.getProfile() == null) { +// Profile sarahProfile = new Profile(teacherSarah, "Sarah", "Jones", "sarahjones", +// "https://github.com/sarahjones", "+44123456791", +// "Frontend specialist with expertise in React and modern web technologies.", +// teacherRole, "Frontend Development", allCohorts.get(1), null); +// profileRepository.save(sarahProfile); +// teacherSarah.setProfile(sarahProfile); +// userRepository.save(teacherSarah); +// } +// +// // Create student users +// List students = new ArrayList<>(); +// String[] firstNames = { +// "Alice", "Bob", "Carol", "David", "Emma", "Frank", "Grace", "Henry", +// "Ivy", "Jack", "Kate", "Liam", "Maya", "Noah", "Olivia", "Paul", +// "Quinn", "Ruby", "Sam", "Tina", "Uma", "Victor", "Wendy", "Xavier", +// "Yara", "Zoe", "Alex", "Blake", "Chloe", "Dan", "Eva", "Felix", +// "Gina", "Hugo", "Iris", "Jake", "Luna", "Max", "Nina", "Oscar" +// }; +// String[] lastNames = { +// "Johnson", "Wilson", "Brown", "Taylor", "Davis", "Miller", "Anderson", "Thomas", +// "Jackson", "White", "Harris", "Martin", "Thompson", "Garcia", "Martinez", "Robinson", +// "Clark", "Rodriguez", "Lewis", "Lee", "Walker", "Hall", "Allen", "Young", +// "Hernandez", "King", "Wright", "Lopez", "Hill", "Scott", "Green", "Adams", +// "Baker", "Gonzalez", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner" +// }; +// +// // Create students based on configuration +// int totalStudents = USE_LARGE_DATABASE ? 360 : 30; // 360 for large (30 per cohort), 30 for small (10 per cohort) +// int studentsPerCohort = USE_LARGE_DATABASE ? 30 : 10; +// +// for (int i = 0; i < totalStudents; i++) { +// String firstName = firstNames[i % firstNames.length]; +// String lastName = lastNames[i % lastNames.length]; +// String email = firstName.toLowerCase() + i + "@s.com"; +// +// User student = createUser(email, "p", studentRole); +// if (student.getProfile() == null) { +// // Distribute students evenly across all cohorts +// Cohort assignedCohort = allCohorts.get(i / studentsPerCohort); +// +// Profile studentProfile = new Profile(student, firstName, lastName, +// firstName.toLowerCase() + lastName.toLowerCase() + i, +// "https://github.com/" + firstName.toLowerCase() + lastName.toLowerCase() + i, +// "+4412345679" + String.format("%03d", i), +// "Passionate about learning software development and building amazing applications.", +// studentRole, "Software Development", assignedCohort, null); +// profileRepository.save(studentProfile); +// student.setProfile(studentProfile); +// student = userRepository.save(student); +// } +// students.add(student); +// } +// +// // Create sample posts +// List samplePosts = Arrays.asList( +// "Just finished my first Java application! Excited to learn more about Spring Boot.", +// "Working on a React project today. The component lifecycle is fascinating!", +// "Had a great debugging session today. Finally understood how to use breakpoints effectively.", +// "Group project is going well. Collaboration through Git is becoming second nature.", +// "Completed the database design exercise. Understanding relationships is key!", +// "Learned about REST APIs today. Can't wait to build my own!", +// "Struggling with CSS Grid but making progress. Practice makes perfect!", +// "Just deployed my first application to the cloud. Such a great feeling!", +// "Code review session was very helpful. Learning from others is invaluable.", +// "Working on the final project. Bringing everything together is challenging but rewarding." +// ); +// +// // Create posts from different users (only if no posts exist) +// if (postRepository.count() == 0) { +// for (int i = 0; i < samplePosts.size(); i++) { +// User author = (i < 2) ? (i == 0 ? teacherJohn : teacherSarah) : students.get(i % students.size()); +// Post post = new Post(author, samplePosts.get(i)); +// post.setLikes((int) (Math.random() * 10)); // Random likes 0-9 +// postRepository.save(post); +// } +// } +// +// System.out.println("Sample data initialization completed!"); +// System.out.println("Created:"); +// System.out.println("- 2 roles (Teacher, Student)"); +// +// System.out.println("- 3 courses with date ranges (Software Development: Jan-Jul 2024, Front-End Development: Feb-Aug 2024, Data Analytics: Mar-Sep 2024)"); +// +// +// if (USE_LARGE_DATABASE) { +// System.out.println("- 12 cohorts (4 per course)"); +// System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); +// System.out.println("- 30 students per cohort across 12 cohorts (360 total students)"); +// System.out.println(" - Software Development: 4 cohorts (120 students)"); +// System.out.println(" - Front-End Development: 4 cohorts (120 students)"); +// System.out.println(" - Data Analytics: 4 cohorts (120 students)"); +// } else { +// System.out.println("- 3 cohorts (1 per course)"); +// System.out.println("- " + (2 + students.size()) + " users with profiles (2 teachers + " + students.size() + " students)"); +// System.out.println("- 10 students per cohort across 3 cohorts (30 total students)"); +// System.out.println(" - Software Development: 1 cohort (10 students)"); +// System.out.println(" - Front-End Development: 1 cohort (10 students)"); +// System.out.println(" - Data Analytics: 1 cohort (10 students)"); +// } +// +// System.out.println("- " + samplePosts.size() + " sample posts"); +// } +// +// private Role createOrGetRole(ERole roleName) { +// return roleRepository.findByName(roleName) +// .orElseGet(() -> roleRepository.save(new Role(roleName))); +// } +// +// private User createUser(String email, String password, Role role) { +// // Check if user already exists +// return userRepository.findByEmail(email) +// .orElseGet(() -> { +// User user = new User(email, encoder.encode(password)); +// Set roles = new HashSet<>(); +// roles.add(role); +// user.setRoles(roles); +// return userRepository.save(user); +// }); +// } +// +// +// private Course createOrGetCourse(String name, LocalDate startDate, LocalDate endDate) { +// +// return courseRepository.findAll().stream() +// .filter(course -> course.getName().equals(name)) +// .findFirst() +// .orElseGet(() -> courseRepository.save(new Course(name, startDate, endDate, null))); +// } +// +// private Cohort createOrGetCohort(String name, Course course) { +// return cohortRepository.findAll().stream() +// .filter(cohort -> cohort.getName().equals(name)) +// .findFirst() +// .orElseGet(() -> { +// Cohort cohort = new Cohort(name, course); +// return cohortRepository.save(cohort); +// }); +// } +}} \ No newline at end of file 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 d61efbb..b46c699 100644 --- a/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java +++ b/src/main/java/com/booleanuk/cohorts/security/jwt/JwtUtils.java @@ -130,6 +130,6 @@ public Integer getUserIdFromExpiredJwtToken(String token) { // Extract userId from expired token return e.getClaims().get("userId", Integer.class); } - } + } } From ef36b365bd25d197454baa5c4fe469e338595b41 Mon Sep 17 00:00:00 2001 From: Richard-Persson Date: Wed, 24 Sep 2025 15:07:56 +0200 Subject: [PATCH 115/119] removed formatting --- .../cohorts/controllers/CohortController.java | 10 ++++------ 1 file changed, 4 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 bdbb3bb..10ba208 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -81,9 +81,8 @@ public ResponseEntity addCohort(@RequestBody CohortRequest cohortRequest){ if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); - LocalDate startDate = LocalDate.parse(cohortRequest.getStartDate().trim(), formatter); - LocalDate endDate = LocalDate.parse(cohortRequest.getEndDate().trim(), formatter); + LocalDate startDate = LocalDate.parse(cohortRequest.getStartDate().trim()); + LocalDate endDate = LocalDate.parse(cohortRequest.getEndDate().trim()); Cohort cohort = new Cohort(cohortRequest.getName(),startDate,endDate,course); return ResponseEntity.ok(cohortRepository.save(cohort)); @@ -101,9 +100,8 @@ public ResponseEntity editCohortById(@PathVariable int id, @RequestBody Cohor String name = cohortRequest.getName(); if (name.isBlank()) return new ResponseEntity<>("Name cannot be blank", HttpStatus.BAD_REQUEST); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); - LocalDate startDate = LocalDate.parse(cohortRequest.getStartDate(),formatter); - LocalDate endDate = LocalDate.parse(cohortRequest.getEndDate(),formatter); + LocalDate startDate = LocalDate.parse(cohortRequest.getStartDate()); + LocalDate endDate = LocalDate.parse(cohortRequest.getEndDate()); From 3b226af948e06054f8be1bf46d8b0b8f4e6d8a52 Mon Sep 17 00:00:00 2001 From: Richard-Persson Date: Thu, 25 Sep 2025 10:07:54 +0200 Subject: [PATCH 116/119] changed if statement to make edit cohort work --- .../com/booleanuk/cohorts/controllers/CohortController.java | 3 ++- 1 file changed, 2 insertions(+), 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 10ba208..b79b7f4 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -47,7 +47,8 @@ public ResponseEntity getAllCohorts() { @GetMapping("{id}") public ResponseEntity getCohortById(@PathVariable int id) { Cohort cohort = this.cohortRepository.findById(id).orElse(null); - if (cohort == null || cohort.getProfiles().isEmpty()) { + System.err.println(cohort); + if (cohort == null) { ErrorResponse error = new ErrorResponse(); error.set("not found"); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); From eed1c0be459f3bc255195316e81913ccab975e2a Mon Sep 17 00:00:00 2001 From: Richard-Persson Date: Thu, 25 Sep 2025 10:09:36 +0200 Subject: [PATCH 117/119] removed printstatement --- .../java/com/booleanuk/cohorts/controllers/CohortController.java | 1 - 1 file changed, 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 b79b7f4..c2c2de5 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/CohortController.java @@ -47,7 +47,6 @@ public ResponseEntity getAllCohorts() { @GetMapping("{id}") public ResponseEntity getCohortById(@PathVariable int id) { Cohort cohort = this.cohortRepository.findById(id).orElse(null); - System.err.println(cohort); if (cohort == null) { ErrorResponse error = new ErrorResponse(); error.set("not found"); From b2f3f40e7d8073f009992ba7e384157d0941b43f Mon Sep 17 00:00:00 2001 From: Richard-Persson Date: Thu, 25 Sep 2025 10:25:00 +0200 Subject: [PATCH 118/119] updated patch studentController --- .../cohorts/controllers/StudentController.java | 11 +++++++++++ .../cohorts/payload/request/StudentRequest.java | 1 + 2 files changed, 12 insertions(+) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index 0ea214b..7da9ea5 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -160,6 +160,11 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); } + Cohort cohort = cohortRepository.findById(studentRequest.getCohortId()).orElse(null); + if (cohort == null) { + return new ResponseEntity<>("Cohort 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); @@ -189,6 +194,12 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen profile.setGithubUrl(studentRequest.getGithub_username()); } + if (cohort!=null) { + profile.setCohort(cohort); + } + + profile.setCohort(cohort); + if (studentRequest.getEmail() != null) { boolean emailExists = userRepository.existsByEmail(studentRequest.getEmail()); 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 26baeae..7797ae4 100644 --- a/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java +++ b/src/main/java/com/booleanuk/cohorts/payload/request/StudentRequest.java @@ -7,6 +7,7 @@ @Setter public class StudentRequest { + private int cohortId; private String photo; private String first_name; private String last_name; From d30fdf40b4663cf1f0e4c20588dc1da8f612d577 Mon Sep 17 00:00:00 2001 From: Richard-Persson Date: Thu, 25 Sep 2025 11:02:21 +0200 Subject: [PATCH 119/119] updated logic in patch studentController --- .../booleanuk/cohorts/controllers/StudentController.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java index 7da9ea5..f219b45 100644 --- a/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java +++ b/src/main/java/com/booleanuk/cohorts/controllers/StudentController.java @@ -160,10 +160,6 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen return new ResponseEntity<>("User not found", HttpStatus.NOT_FOUND); } - Cohort cohort = cohortRepository.findById(studentRequest.getCohortId()).orElse(null); - if (cohort == null) { - return new ResponseEntity<>("Cohort not found", HttpStatus.NOT_FOUND); - } Profile profile = profileRepository.findById(user.getProfile().getId()).orElse(null); if (profile == null) { @@ -194,12 +190,11 @@ public ResponseEntity updateStudent(@PathVariable int id, @RequestBody Studen profile.setGithubUrl(studentRequest.getGithub_username()); } + Cohort cohort = cohortRepository.findById(studentRequest.getCohortId()).orElse(null); if (cohort!=null) { profile.setCohort(cohort); } - profile.setCohort(cohort); - if (studentRequest.getEmail() != null) { boolean emailExists = userRepository.existsByEmail(studentRequest.getEmail());