From a39d066affc85dd06a5d55aafa964f657ec69d33 Mon Sep 17 00:00:00 2001 From: Guilherme Stark Date: Sun, 29 Jun 2025 13:58:36 +0200 Subject: [PATCH 01/26] feat: Update userdb to match api v2.1 --- server/database/userdb/init_userdb.sql | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/server/database/userdb/init_userdb.sql b/server/database/userdb/init_userdb.sql index bc2cf201..7a246c29 100644 --- a/server/database/userdb/init_userdb.sql +++ b/server/database/userdb/init_userdb.sql @@ -9,9 +9,11 @@ CREATE TABLE IF NOT EXISTS `user_data` ( user_id VARBINARY(16) PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, - name VARCHAR(255) NOT NULL, + firstname VARCHAR(255) NOT NULL, + lastname VARCHAR(255) NOT NULL, gender VARCHAR(255) NOT NULL, degree VARCHAR(255) NOT NULL, + degree_start INT NOT NULL, birthday DATE NOT NULL ); @@ -23,12 +25,4 @@ CREATE TABLE IF NOT EXISTS `user_interests` ( list_id VARBINARY(16) PRIMARY KEY, user_id VARBINARY(16) NOT NULL, interest VARCHAR(255) NOT NULL -); - --- user_credentials table - -- user_id: UUID of the given user - -- password_hash: user's hashed password -CREATE TABLE IF NOT EXISTS `user_credentials` ( - user_id VARBINARY(16) PRIMARY KEY, - password_hash VARCHAR(255) NOT NULL ); \ No newline at end of file From 828f0ad964199652c84d613d93f5afeaf98beb64 Mon Sep 17 00:00:00 2001 From: Guilherme Stark Date: Sun, 29 Jun 2025 14:01:48 +0200 Subject: [PATCH 02/26] feat: add bio field --- server/database/userdb/init_userdb.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/database/userdb/init_userdb.sql b/server/database/userdb/init_userdb.sql index 7a246c29..d43d89f7 100644 --- a/server/database/userdb/init_userdb.sql +++ b/server/database/userdb/init_userdb.sql @@ -11,10 +11,11 @@ CREATE TABLE IF NOT EXISTS `user_data` ( email VARCHAR(255) UNIQUE NOT NULL, firstname VARCHAR(255) NOT NULL, lastname VARCHAR(255) NOT NULL, + birthday DATE NOT NULL, gender VARCHAR(255) NOT NULL, degree VARCHAR(255) NOT NULL, degree_start INT NOT NULL, - birthday DATE NOT NULL + bio VARCHAR(1023) NOT NULL, ); -- user-interests table From 11252d32686bd2c1b33e68445a2c1448b5f38a61 Mon Sep 17 00:00:00 2001 From: Guilherme Stark Date: Sun, 29 Jun 2025 14:09:52 +0200 Subject: [PATCH 03/26] feat: update user entity object to match api v2.1 --- .../java/meet_at_mensa/user/model/User.java | 67 ++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/server/user/src/main/java/meet_at_mensa/user/model/User.java b/server/user/src/main/java/meet_at_mensa/user/model/User.java index 52742e6b..97ebf263 100644 --- a/server/user/src/main/java/meet_at_mensa/user/model/User.java +++ b/server/user/src/main/java/meet_at_mensa/user/model/User.java @@ -28,8 +28,14 @@ public class User { @Column(name = "email") private String email; - @Column(name = "name") - private String name; + @Column(name = "firstname") + private String firstname; + + @Column(name = "lastname") + private String lastname; + + @Column(name = "birthday") + private LocalDate birthday; @Column(name = "gender") private String gender; @@ -37,8 +43,12 @@ public class User { @Column(name = "degree") private String degree; - @Column(name = "birthday") - private LocalDate birthday; + @Column(name = "degree_start") + private Integer degreeStart; + + @Column(name = "bio") + private String bio; + // ------- // Getters @@ -52,8 +62,16 @@ public String getEmail() { return email; } - public String getName() { - return name; + public String getFirstname() { + return firstname; + } + + public String getLastname() { + return lastname; + } + + public LocalDate getBirthday() { + return birthday; } public String getGender() { @@ -64,8 +82,12 @@ public String getDegree() { return degree; } - public LocalDate getBirthday() { - return birthday; + public Integer getDegreeStart() { + return degreeStart; + } + + public String getBio() { + return bio; } // ------- @@ -78,8 +100,16 @@ public void setEmail(String email) { this.email = email; } - public void setName(String name) { - this.name = name; + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public void setBirthday(LocalDate birthday) { + this.birthday = birthday; } public void setGender(String gender) { @@ -90,8 +120,12 @@ public void setDegree(String degree) { this.degree = degree; } - public void setBirthday(LocalDate birthday) { - this.birthday = birthday; + public void setDegreeStart(Integer degreeStart) { + this.degreeStart = degreeStart; + } + + public void setBio(String bio) { + this.bio = bio; } // ------------ @@ -104,13 +138,16 @@ public User() { } - public User(String email, String name, String gender, String degree, LocalDate birthday) { + public User(String email, String firstname, String lastname, LocalDate birthday, String gender, String degree, Integer degreeStart, String bio) { this.email = email; - this.name = name; + this.firstname = firstname; + this.lastname = lastname; + this.birthday = birthday; this.gender = gender; this.degree = degree; - this.birthday = birthday; + this.degreeStart = degreeStart; + this.bio = bio; } } From 702dd305334a45784b723a694066a9f72c7d888f Mon Sep 17 00:00:00 2001 From: Guilherme Stark Date: Sun, 29 Jun 2025 14:10:44 +0200 Subject: [PATCH 04/26] cleanup: remove obsolete classes --- .../user/controller/InterestController.java | 45 ---------- .../user/controller/UserController.java | 82 ------------------- 2 files changed, 127 deletions(-) delete mode 100644 server/user/src/main/java/meet_at_mensa/user/controller/InterestController.java delete mode 100644 server/user/src/main/java/meet_at_mensa/user/controller/UserController.java diff --git a/server/user/src/main/java/meet_at_mensa/user/controller/InterestController.java b/server/user/src/main/java/meet_at_mensa/user/controller/InterestController.java deleted file mode 100644 index 7b8a0b02..00000000 --- a/server/user/src/main/java/meet_at_mensa/user/controller/InterestController.java +++ /dev/null @@ -1,45 +0,0 @@ -package meet_at_mensa.user.controller; - -import org.springframework.beans.factory.annotation.Autowired; - -import org.springframework.web.bind.annotation.RestController; - -import meet_at_mensa.user.model.Interest; -import meet_at_mensa.user.repository.InterestRepository; - -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import java.util.UUID; -import java.util.Map; - -@RestController -public class InterestController { - - // Object representing the userdb database - @Autowired // This is auto-implemented by spring - private InterestRepository interestRepository; - - // debug function to show entire database - @GetMapping(path="/debug/users/interests/all_entries") - public Iterable getAllInterests() { - return interestRepository.findAll(); - } - - // Get all interests for a given user - @PostMapping(path="/api/users/interests/by_user_id") - public Iterable getUserInterests(@RequestBody Map payload) { - UUID userID = UUID.fromString(payload.get("userID")); - return interestRepository.findByUserID(userID); - } - - // Awaits an interest .json {userID: UUID userID, interest: String interest} - @PostMapping(path="/api/users/interests/add") - public Interest addInterest(@RequestBody Interest interest) { - return interestRepository.save(interest); - } - // Example: - // curl -H "Content-Type: application/json" --request POST -d '{"userID": "946f3f46-28de-4b6a-9010-1c5c1ed56af3", "interest": "cats"}' 127.0.0.1:8083/api/users/interests/add - -} diff --git a/server/user/src/main/java/meet_at_mensa/user/controller/UserController.java b/server/user/src/main/java/meet_at_mensa/user/controller/UserController.java deleted file mode 100644 index 29c9d8be..00000000 --- a/server/user/src/main/java/meet_at_mensa/user/controller/UserController.java +++ /dev/null @@ -1,82 +0,0 @@ -package meet_at_mensa.user.controller; - -import org.springframework.beans.factory.annotation.Autowired; - -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.server.ResponseStatusException; - -import meet_at_mensa.user.model.User; -import meet_at_mensa.user.repository.UserRepository; - -import org.springframework.http.HttpStatus; - -import java.util.UUID; -import java.util.Map; - -@RestController -public class UserController { - - // Object representing the userdb database - @Autowired // This is auto-implemented by spring - private UserRepository userRepository; - - // Show all users in database - // TODO: Remove in production - @GetMapping(path="/debug/users/all") - public Iterable getAllUsers() { - return userRepository.findAll(); - } - - // Create a new user when provided with json formatted data - @PostMapping(path="/api/users/create") - public User createUser(@RequestBody User user) { - return userRepository.save(user); - } - - // Return user information - @GetMapping(path="/api/users/get/{userID}") - public User getUser(@PathVariable UUID userID) { - return userRepository.findById(userID) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")); - } - - // Update user information - @PostMapping(path="api/users/edit/{userID}") - public User editUser(@PathVariable UUID userID, @RequestBody Map update) { - - // find user - User user = userRepository.findById(userID) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")); - - // Update values if present in payload - if (update.containsKey("email")) { - user.setEmail(update.get("email")); - } - - if (update.containsKey("name")) { - user.setName(update.get("name")); - } - - if (update.containsKey("gender")) { - user.setGender(update.get("gender")); - } - - if (update.containsKey("degree")) { - user.setDegree(update.get("degree")); - } - - if (update.containsKey("birthday")) { - // implement later - } - - // Save to Database and Return - return userRepository.save(user); - - } - -} \ No newline at end of file From d2a384506a24440fdb762ba134e513ef7fecc282 Mon Sep 17 00:00:00 2001 From: Guilherme Stark Date: Sun, 29 Jun 2025 15:01:43 +0200 Subject: [PATCH 05/26] refactor: rename database entity classes to differentiate themselves from DTOs --- .../user/model/{Interest.java => InterestEntity.java} | 6 +++--- .../meet_at_mensa/user/model/{User.java => UserEntity.java} | 6 +++--- .../meet_at_mensa/user/repository/InterestRepository.java | 6 +++--- .../java/meet_at_mensa/user/repository/UserRepository.java | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) rename server/user/src/main/java/meet_at_mensa/user/model/{Interest.java => InterestEntity.java} (91%) rename server/user/src/main/java/meet_at_mensa/user/model/{User.java => UserEntity.java} (93%) diff --git a/server/user/src/main/java/meet_at_mensa/user/model/Interest.java b/server/user/src/main/java/meet_at_mensa/user/model/InterestEntity.java similarity index 91% rename from server/user/src/main/java/meet_at_mensa/user/model/Interest.java rename to server/user/src/main/java/meet_at_mensa/user/model/InterestEntity.java index bd833043..909b52de 100644 --- a/server/user/src/main/java/meet_at_mensa/user/model/Interest.java +++ b/server/user/src/main/java/meet_at_mensa/user/model/InterestEntity.java @@ -12,7 +12,7 @@ // Class User represents a single entry (row) in the userdb/users table @Entity @Table(name = "user_interests") -public class Interest { +public class InterestEntity { // ---------- // Attributes @@ -50,13 +50,13 @@ public String getInterest() { // Constructors // ------------ - public Interest() { + public InterestEntity() { // default constructor required by JPA } - public Interest(UUID userID, String interest) { + public InterestEntity(UUID userID, String interest) { this.userID = userID; this.interest = interest; diff --git a/server/user/src/main/java/meet_at_mensa/user/model/User.java b/server/user/src/main/java/meet_at_mensa/user/model/UserEntity.java similarity index 93% rename from server/user/src/main/java/meet_at_mensa/user/model/User.java rename to server/user/src/main/java/meet_at_mensa/user/model/UserEntity.java index 97ebf263..a2861a11 100644 --- a/server/user/src/main/java/meet_at_mensa/user/model/User.java +++ b/server/user/src/main/java/meet_at_mensa/user/model/UserEntity.java @@ -13,7 +13,7 @@ // Class User represents a single entry (row) in the userdb/users table @Entity @Table(name = "user_data") -public class User { +public class UserEntity { // ---------- // Attributes @@ -132,13 +132,13 @@ public void setBio(String bio) { // Constructors // ------------ - public User() { + public UserEntity() { // default constructor required by JPA } - public User(String email, String firstname, String lastname, LocalDate birthday, String gender, String degree, Integer degreeStart, String bio) { + public UserEntity(String email, String firstname, String lastname, LocalDate birthday, String gender, String degree, Integer degreeStart, String bio) { this.email = email; this.firstname = firstname; diff --git a/server/user/src/main/java/meet_at_mensa/user/repository/InterestRepository.java b/server/user/src/main/java/meet_at_mensa/user/repository/InterestRepository.java index fca51112..7e8d586c 100644 --- a/server/user/src/main/java/meet_at_mensa/user/repository/InterestRepository.java +++ b/server/user/src/main/java/meet_at_mensa/user/repository/InterestRepository.java @@ -3,17 +3,17 @@ // import CRUD repository (Create/Read/Update/Delete) import org.springframework.data.repository.CrudRepository; -import meet_at_mensa.user.model.Interest; +import meet_at_mensa.user.model.InterestEntity; import java.util.UUID; // Interface UserRepository represents the userdb table -public interface InterestRepository extends CrudRepository { +public interface InterestRepository extends CrudRepository { // This is auto-implemented by springboot into a userRepository Bean // In order to be able to get all values for a given user ID, we define this custom query // Custom Queries as described here: https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html - Iterable findByUserID(UUID userID); + Iterable findByUserID(UUID userID); } \ No newline at end of file diff --git a/server/user/src/main/java/meet_at_mensa/user/repository/UserRepository.java b/server/user/src/main/java/meet_at_mensa/user/repository/UserRepository.java index beaa9b4d..57331b66 100644 --- a/server/user/src/main/java/meet_at_mensa/user/repository/UserRepository.java +++ b/server/user/src/main/java/meet_at_mensa/user/repository/UserRepository.java @@ -3,12 +3,12 @@ // import CRUD repository (Create/Read/Update/Delete) import org.springframework.data.repository.CrudRepository; -import meet_at_mensa.user.model.User; +import meet_at_mensa.user.model.UserEntity; import java.util.UUID; // Interface UserRepository represents the userdb table -public interface UserRepository extends CrudRepository { +public interface UserRepository extends CrudRepository { // This is auto-implemented by springboot into a userRepository Bean From bc85ac2485edaeb46208c0e596614f7a160e082b Mon Sep 17 00:00:00 2001 From: Guilherme Stark Date: Sun, 29 Jun 2025 17:34:49 +0200 Subject: [PATCH 06/26] feat: implemented basic logic for UserService UserService handles the operations on the databases. --- .../user/exception/UserNotFoundException.java | 17 + .../user/service/UserService.java | 309 ++++++++++++++++++ 2 files changed, 326 insertions(+) create mode 100644 server/user/src/main/java/meet_at_mensa/user/exception/UserNotFoundException.java create mode 100644 server/user/src/main/java/meet_at_mensa/user/service/UserService.java diff --git a/server/user/src/main/java/meet_at_mensa/user/exception/UserNotFoundException.java b/server/user/src/main/java/meet_at_mensa/user/exception/UserNotFoundException.java new file mode 100644 index 00000000..58e2c9df --- /dev/null +++ b/server/user/src/main/java/meet_at_mensa/user/exception/UserNotFoundException.java @@ -0,0 +1,17 @@ +package meet_at_mensa.user.exception; + +public class UserNotFoundException extends RuntimeException { + + public UserNotFoundException() { + super("User not found"); + } + + public UserNotFoundException(String message) { + super(message); + } + + public UserNotFoundException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/server/user/src/main/java/meet_at_mensa/user/service/UserService.java b/server/user/src/main/java/meet_at_mensa/user/service/UserService.java new file mode 100644 index 00000000..abdf7e73 --- /dev/null +++ b/server/user/src/main/java/meet_at_mensa/user/service/UserService.java @@ -0,0 +1,309 @@ +package meet_at_mensa.user.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import org.openapitools.model.User; +import org.openapitools.model.UserNew; +import org.openapitools.model.UserUpdate; + +import meet_at_mensa.user.repository.*; +import meet_at_mensa.user.exception.UserNotFoundException; +import meet_at_mensa.user.model.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + + +@Service +public class UserService { + + // UserRepository manages the userdb database + @Autowired + private UserRepository userRepository; + + // IntererstRepository manages the interests database + @Autowired + InterestRepository interestRepository; + + + /** + * Searches the database for a user with id {userID} + * + * Generates a User object from the database entity abstractions + * + * @param userID UUID of the user being searched for + * @return User object if a user was found + * @throws UserNotFoundException if no user with id {userID} is found + */ + public User getUser(UUID userID) { + + // search the database for a user + // throws a UserNotFoundException if no user is found + UserEntity userEntity = userRepository.findById(userID) + .orElseThrow(() -> new UserNotFoundException(String.format("User with ID %s not found", userID))); + + // seach the database for a user's interests + List interests = getInterests(userID); + + // construct new User object + User user = new User( + userID, + userEntity.getEmail(), // email + userEntity.getFirstname(), // firstname + userEntity.getLastname(), // lastname + userEntity.getBirthday(), // birthday + userEntity.getGender(), // gender + userEntity.getDegree(), // degree + userEntity.getDegreeStart(), // degreeStart + interests, // interests + userEntity.getBio() // bio + ); + + // return + return user; + } + + /** + * Creates database entries for a new user based on a UserNew object + * + * Generates UUIDs + * + * @param userNew UserNew object containing information about the new user + * @return User object of the user that has been created + */ + public User registerUser(UserNew userNew) { + + // construct new UserEntity object + UserEntity userEntity = new UserEntity( + userNew.getEmail(), // email + userNew.getFirstname(), // firstname + userNew.getLastname(), // lastname + userNew.getBirthday(), // birthday + userNew.getGender(), // gender + userNew.getDegree(), // degree + userNew.getDegreeStart(), // degreeStart + userNew.getBio() // bio + ); + + // save user entity to database + userEntity = userRepository.save(userEntity); + + // register interests + registerInterests(userEntity.getUserID(), userNew.getInterests()); + + // fetch User object and return + return getUser(userEntity.getUserID()); + } + + /** + * Updates the database entries for a user with id {UserID} based on a UserUpdate object. + * + * Keeps old value if corresponding value in UserUpdate is null. + * Overwrites previously existing values otherwise. + * + * @param userID UUID of the user being updated + * @return User object of the user that has been updated + * @throws UserNotFoundException if no user with id {userID} is found + */ + public User updateUser(UUID userID, UserUpdate userUpdate) { + + // search the database for a user + // throws a UserNotFoundException if no user is found + UserEntity userEntity = userRepository.findById(userID) + .orElseThrow(() -> new UserNotFoundException(String.format("User with ID %s not found", userID))); + + // update Email + if (userUpdate.getEmail() != null){ + userEntity.setEmail(userUpdate.getEmail()); + } + + // update Firstname + if (userUpdate.getFirstname() != null){ + userEntity.setFirstname(userUpdate.getFirstname()); + } + + // update Lastname + if (userUpdate.getLastname() != null){ + userEntity.setLastname(userUpdate.getLastname()); + } + + // update Birthday + if (userUpdate.getBirthday() != null){ + userEntity.setBirthday(userUpdate.getBirthday()); + } + + // update Gender + if (userUpdate.getGender() != null){ + userEntity.setGender(userUpdate.getGender()); + } + + // update Degree + if (userUpdate.getDegree() != null){ + userEntity.setDegree(userUpdate.getDegree()); + } + + // update DegreeStart + if (userUpdate.getDegreeStart() != null){ + userEntity.setDegreeStart(userUpdate.getDegreeStart()); + } + + // update Bio + if (userUpdate.getBio() != null){ + userEntity.setBio(userUpdate.getBio()); + } + + // save changes to the database + userRepository.save(userEntity); + + // update Interests + if (userUpdate.getBio() != null){ + + // remove old interests + deleteInterests(userID); + + // add new interest list + registerInterests(userID, userUpdate.getInterests()); + + } + + // return User object fetched new from the database + return getUser(userID); + + } + + /** + * Removes all database entries for a user with id {UserID}. + * + * + * @param userID UUID of the user being deleted + * @throws UserNotFoundException if no user with id {userID} is found + */ + public void deleteUser(UUID userID) { + + // Check that user exists + // throws a UserNotFoundException if no user is found + userRepository.findById(userID) + .orElseThrow(() -> new UserNotFoundException(String.format("User with ID %s not found", userID))); + + // delete userEntity from database + userRepository.deleteById(userID); + + // delete corresponding interests + deleteInterests(userID); + } + + /** + * Searches the interests database for all interests corresponding to user with id {userID} + * + * Generates a list of Strings to return + * + * @param userID UUID of the user who's interests are being searched for + * @return List of interests corresponding to that user + */ + private List getInterests(UUID userID) { + + // search the database for interestEntity entries + Iterable interestEntities = interestRepository.findByUserID(userID); + + // create an empty list to store interests + List interests = new ArrayList<>(); + + // extract string values into new list + for (InterestEntity interestEntity : interestEntities) { + interests.add(interestEntity.getInterest()); + } + + // return + return interests; + } + + /** + * Deletes all entries from interests database for user with id {userID} + * + * @param userID UUID of the user who's interests are being deleted + */ + private void deleteInterests(UUID userID) { + + // search the database for interestEntity entries + Iterable interestEntities = interestRepository.findByUserID(userID); + + // delete all entries from list + for (InterestEntity interestEntity : interestEntities) { + interestRepository.deleteById(interestEntity.getListID()); + } + + } + + /** + * Creates database entries for interests corresponding to a user with id {userID} + * + * Deletes all previous entries for that user and generates new ones + * + * @param userID UUID of the user being searched for + * @param interests List String values of the user's interests + * @return The list of interests corresponding to the user after registering + */ + private List registerInterests(UUID userID, List interests) { + + // for each interest + for (String interest : interests) { + + // create an entity object and add it to the database + interestRepository.save(new InterestEntity(userID, interest)); + } + + // return the interests for this userID + return getInterests(userID); + + } + + /** + * DEBUG METHOD ONLY. DO NOT USE IN PRODUCTION + * + * Generate a list of all UserEntity objects managed by the databases + * + * @return List of all UserEntity objects in the database + */ + public Iterable debugGetAllUserEntities() { + return userRepository.findAll(); + } + + /** + * DEBUG METHOD ONLY. DO NOT USE IN PRODUCTION + * + * Generate a list of all InterestEntity objects managed by the databases + * + * @return List of all InterestEntity objects in the database + */ + public Iterable debugGetAllInterestEntities() { + return interestRepository.findAll(); + } + + /** + * DEBUG METHOD ONLY. DO NOT USE IN PRODUCTION + * + * Generate a list of all User Objects managed by the database + * + * @return List of all User objects in the database + */ + public Iterable debugGetAllUsers() { + + // get list of userentities + Iterable allUserEntities = userRepository.findAll(); + + List users = new ArrayList<>(); + + // generate User objects for each + for (UserEntity userEntity : allUserEntities) { + users.add( + getUser(userEntity.getUserID()) + ); + } + + return users; + + } + +} From 874b18b7ac699dc983c2b5e6331a79fd5d4f71a9 Mon Sep 17 00:00:00 2001 From: Guilherme Stark Date: Sun, 29 Jun 2025 18:05:51 +0200 Subject: [PATCH 07/26] feat: initial implementation of UserController class --- .../user/controller/UserController.java | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 server/user/src/main/java/meet_at_mensa/user/controller/UserController.java diff --git a/server/user/src/main/java/meet_at_mensa/user/controller/UserController.java b/server/user/src/main/java/meet_at_mensa/user/controller/UserController.java new file mode 100644 index 00000000..7c58139e --- /dev/null +++ b/server/user/src/main/java/meet_at_mensa/user/controller/UserController.java @@ -0,0 +1,109 @@ +package meet_at_mensa.user.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import meet_at_mensa.user.exception.UserNotFoundException; +import meet_at_mensa.user.service.UserService; + +import org.openapitools.api.UserApi; +import org.openapitools.model.User; +import org.openapitools.model.UserNew; +import org.openapitools.model.UserUpdate; + +import java.util.UUID; + + + +@RestController +public class UserController implements UserApi { + + // UserService handles database operations + @Autowired + private UserService userService; + + + // DELETE @ api/v2/user/{userID} + @Override + public ResponseEntity deleteApiV2UserUserID(UUID userID) { + + try { + + // attempt to delete user with given ID + userService.deleteUser(userID); + + // if delete operation succeeded, return 200 + return ResponseEntity.ok().build(); + + } catch (UserNotFoundException e) { + + // if user was not found, return 404 + return ResponseEntity.notFound().build(); + + } + } + + + // GET @ api/v2/user/{userID} + @Override + public ResponseEntity getApiV2UserUserID(UUID userID) { + + try { + + // attempt to get user with the given ID + User user = userService.getUser(userID); + + // return 200 with User + return ResponseEntity.ok(user); + + + } catch (UserNotFoundException e) { + + // if user was not found, return 404 + return ResponseEntity.notFound().build(); + + } + } + + + // POST @ api/v2/user/register + @Override + public ResponseEntity postApiV2UserRegister(UserNew userNew) { + + try { + + // attempt to register a new user + User newUser = userService.registerUser(userNew); + + return ResponseEntity.status(201).body(newUser); + + } catch (Exception e) { + + // TODO fix signature to match API, this should not return 503 + return ResponseEntity.internalServerError().build(); + + } + } + + // PUT @ api/v2/user/{userID} + @Override + public ResponseEntity putApiV2UserUserID(UUID userID, UserUpdate userUpdate) { + + try { + + // attempt to update User + User updatedUser = userService.updateUser(userID, userUpdate); + + // return the updated user + return ResponseEntity.ok(updatedUser); + + } catch (UserNotFoundException e) { + + // if user was not found, return 404 + return ResponseEntity.notFound().build(); + } + + } + +} \ No newline at end of file From ed661e9f109a6b386f9be168ba9ee7d783f21024 Mon Sep 17 00:00:00 2001 From: Guilherme Stark Date: Sun, 29 Jun 2025 18:18:20 +0200 Subject: [PATCH 08/26] spec: update api spec to v2.1.1 --- api/changelogs/changelog_v2_1_1.md | 24 ++++++++++++++++++++++++ api/openapi.yaml | 30 +++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 api/changelogs/changelog_v2_1_1.md diff --git a/api/changelogs/changelog_v2_1_1.md b/api/changelogs/changelog_v2_1_1.md new file mode 100644 index 00000000..3b2c152c --- /dev/null +++ b/api/changelogs/changelog_v2_1_1.md @@ -0,0 +1,24 @@ +## Info +### Version +v2.1 +### Date +2025-06-27 +### Autor +James Stark +## Changelog: + +### Paths +- Add 500 responses to all endpoints +- Change 201 response to 200 for register user + +``` diff + +@@ All @@ ++ insert code 500 response +# Formally handle errors + +@@ /api/v2/user/registr POST @@ +- remove code 201 response ++ insert code 200 response + +``` diff --git a/api/openapi.yaml b/api/openapi.yaml index 7b2e059c..5fa4588d 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -3,7 +3,7 @@ x-stoplight: id: ceylawji1yc2t info: title: MeetAtMensa - version: 1.0.0 + version: 2.1.1 description: |- This OpenAPI specification defines the endpoints, schemas, and security mechanisms for the Meet@Mensa User micro-service. @@ -701,6 +701,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' + '500': + description: Internal Server Error operationId: get-api-v2-user-userID x-stoplight: id: p1d67jb8y16q8 @@ -724,6 +726,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' + 5XX: + description: Server Error operationId: put-api-v2-user-userID x-stoplight: id: exmvsbw8301bi @@ -746,6 +750,8 @@ paths: $ref: '#/components/responses/BadRequestError' '404': $ref: '#/components/responses/NotFoundError' + '500': + description: Internal Server Error operationId: delete-api-v2-user-userID x-stoplight: id: 407z5o4zq5tvl @@ -757,8 +763,8 @@ paths: tags: - User responses: - '201': - description: Successfully created user + '200': + description: OK content: application/json: schema: @@ -769,6 +775,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '409': description: Conflict + '500': + description: Internal Server Error operationId: post-api-v2-user-register x-stoplight: id: laufntfpxgg42 @@ -792,6 +800,8 @@ paths: $ref: '#/components/responses/BadRequestError' '401': $ref: '#/components/responses/UnauthorizedError' + '500': + description: Internal Server Error operationId: post-api-v2-matching-request x-stoplight: id: 1x5ta8qguutzq @@ -823,6 +833,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' + '500': + description: Internal Server Error operationId: get-api-v2-matching-matches-userID description: 'Retrieve all matches for a user with {user-id} from the matching-service' '/api/v2/matching/requests/{user-id}': @@ -845,6 +857,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' + '500': + description: Internal Server Error operationId: get-api-v2-matching-requests-userID x-stoplight: id: wkc1ys6vkbv12 @@ -865,6 +879,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' + '500': + description: Internal Server Error operationId: delete-api-v2-matching-matches-request-id x-stoplight: id: shgx14ydoaa2f @@ -888,6 +904,8 @@ paths: $ref: '#/components/responses/NotFoundError' '406': description: MatchRequest cannot be updated since it has already been fulfilled! + '500': + description: Internal Server Error operationId: put-api-v2-matching-request-request-id x-stoplight: id: f6t3aeqcvup68 @@ -909,6 +927,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' + '500': + description: Internal Server Error operationId: get-api-v2-matching-rsvp-match-id-accept x-stoplight: id: am6b7xnyytanu @@ -933,6 +953,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' + '500': + description: Internal Server Error operationId: get-api-v2-matching-rsvp-match-id-reject description: Reject invitation to a given match x-stoplight: @@ -959,6 +981,8 @@ paths: $ref: '#/components/responses/UnauthorizedError' '404': $ref: '#/components/responses/NotFoundError' + '500': + description: Internal Server Error operationId: get-api-v2-genai-conversation-starter requestBody: content: From f5d1086ef2e7e076faa93c58f5c4802b123a87b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 29 Jun 2025 16:19:27 +0000 Subject: [PATCH 09/26] gen: generate code and documentation based on latest version of openapi.yaml --- client/src/api.ts | 88 ++++++++++++++++++- docs/api.html | 34 +++++-- server/gateway/generated/build.gradle | 2 +- server/gateway/generated/pom.xml | 2 +- .../java/org/openapitools/api/GenAiApi.java | 6 +- .../org/openapitools/api/MatchingApi.java | 30 +++++-- .../java/org/openapitools/api/UserApi.java | 22 +++-- .../model/ConversationStarter.java | 2 +- .../model/ConversationStarterCollection.java | 2 +- .../java/org/openapitools/model/Group.java | 2 +- .../org/openapitools/model/InviteStatus.java | 2 +- .../java/org/openapitools/model/Location.java | 2 +- .../java/org/openapitools/model/Match.java | 2 +- .../openapitools/model/MatchCollection.java | 2 +- .../openapitools/model/MatchPreferences.java | 2 +- .../org/openapitools/model/MatchRequest.java | 2 +- .../model/MatchRequestCollection.java | 2 +- .../openapitools/model/MatchRequestNew.java | 2 +- .../model/MatchRequestUpdate.java | 2 +- .../org/openapitools/model/MatchStatus.java | 2 +- .../org/openapitools/model/RequestStatus.java | 2 +- .../java/org/openapitools/model/User.java | 2 +- .../openapitools/model/UserCollection.java | 2 +- .../java/org/openapitools/model/UserNew.java | 2 +- .../org/openapitools/model/UserUpdate.java | 2 +- server/matching/generated/build.gradle | 2 +- server/matching/generated/pom.xml | 2 +- .../java/org/openapitools/api/GenAiApi.java | 6 +- .../org/openapitools/api/MatchingApi.java | 30 +++++-- .../java/org/openapitools/api/UserApi.java | 22 +++-- .../model/ConversationStarter.java | 2 +- .../model/ConversationStarterCollection.java | 2 +- .../java/org/openapitools/model/Group.java | 2 +- .../org/openapitools/model/InviteStatus.java | 2 +- .../java/org/openapitools/model/Location.java | 2 +- .../java/org/openapitools/model/Match.java | 2 +- .../openapitools/model/MatchCollection.java | 2 +- .../openapitools/model/MatchPreferences.java | 2 +- .../org/openapitools/model/MatchRequest.java | 2 +- .../model/MatchRequestCollection.java | 2 +- .../openapitools/model/MatchRequestNew.java | 2 +- .../model/MatchRequestUpdate.java | 2 +- .../org/openapitools/model/MatchStatus.java | 2 +- .../org/openapitools/model/RequestStatus.java | 2 +- .../java/org/openapitools/model/User.java | 2 +- .../openapitools/model/UserCollection.java | 2 +- .../java/org/openapitools/model/UserNew.java | 2 +- .../org/openapitools/model/UserUpdate.java | 2 +- server/user/generated/build.gradle | 2 +- server/user/generated/pom.xml | 2 +- .../java/org/openapitools/api/GenAiApi.java | 6 +- .../org/openapitools/api/MatchingApi.java | 30 +++++-- .../java/org/openapitools/api/UserApi.java | 22 +++-- .../model/ConversationStarter.java | 2 +- .../model/ConversationStarterCollection.java | 2 +- .../java/org/openapitools/model/Group.java | 2 +- .../org/openapitools/model/InviteStatus.java | 2 +- .../java/org/openapitools/model/Location.java | 2 +- .../java/org/openapitools/model/Match.java | 2 +- .../openapitools/model/MatchCollection.java | 2 +- .../openapitools/model/MatchPreferences.java | 2 +- .../org/openapitools/model/MatchRequest.java | 2 +- .../model/MatchRequestCollection.java | 2 +- .../openapitools/model/MatchRequestNew.java | 2 +- .../model/MatchRequestUpdate.java | 2 +- .../org/openapitools/model/MatchStatus.java | 2 +- .../org/openapitools/model/RequestStatus.java | 2 +- .../java/org/openapitools/model/User.java | 2 +- .../openapitools/model/UserCollection.java | 2 +- .../java/org/openapitools/model/UserNew.java | 2 +- .../org/openapitools/model/UserUpdate.java | 2 +- 71 files changed, 298 insertions(+), 118 deletions(-) diff --git a/client/src/api.ts b/client/src/api.ts index b9c5b31f..8ac5989a 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -646,6 +646,13 @@ export interface operations { 400: components["responses"]["BadRequestError"]; 401: components["responses"]["UnauthorizedError"]; 404: components["responses"]["NotFoundError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "put-api-v2-user-userID": { @@ -676,6 +683,13 @@ export interface operations { 400: components["responses"]["BadRequestError"]; 401: components["responses"]["UnauthorizedError"]; 404: components["responses"]["NotFoundError"]; + /** @description Server Error */ + "5XX": { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "delete-api-v2-user-userID": { @@ -700,6 +714,13 @@ export interface operations { 400: components["responses"]["BadRequestError"]; 401: components["responses"]["BadRequestError"]; 404: components["responses"]["NotFoundError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "post-api-v2-user-register": { @@ -715,8 +736,8 @@ export interface operations { }; }; responses: { - /** @description Successfully created user */ - 201: { + /** @description OK */ + 200: { headers: { [name: string]: unknown; }; @@ -733,6 +754,13 @@ export interface operations { }; content?: never; }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "post-api-v2-matching-request": { @@ -757,6 +785,13 @@ export interface operations { }; 400: components["responses"]["BadRequestError"]; 401: components["responses"]["UnauthorizedError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "get-api-v2-matching-matches-userID": { @@ -783,6 +818,13 @@ export interface operations { 400: components["responses"]["BadRequestError"]; 401: components["responses"]["UnauthorizedError"]; 404: components["responses"]["NotFoundError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "get-api-v2-matching-requests-userID": { @@ -809,6 +851,13 @@ export interface operations { 400: components["responses"]["BadRequestError"]; 401: components["responses"]["UnauthorizedError"]; 404: components["responses"]["NotFoundError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "put-api-v2-matching-request-request-id": { @@ -846,6 +895,13 @@ export interface operations { }; content?: never; }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "delete-api-v2-matching-matches-request-id": { @@ -870,6 +926,13 @@ export interface operations { 400: components["responses"]["BadRequestError"]; 401: components["responses"]["UnauthorizedError"]; 404: components["responses"]["NotFoundError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "get-api-v2-matching-rsvp-match-id-accept": { @@ -893,6 +956,13 @@ export interface operations { }; 401: components["responses"]["UnauthorizedError"]; 404: components["responses"]["NotFoundError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "get-api-v2-matching-rsvp-match-id-reject": { @@ -916,6 +986,13 @@ export interface operations { }; 401: components["responses"]["UnauthorizedError"]; 404: components["responses"]["NotFoundError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; "get-api-v2-genai-conversation-starter": { @@ -944,6 +1021,13 @@ export interface operations { 400: components["responses"]["BadRequestError"]; 401: components["responses"]["UnauthorizedError"]; 404: components["responses"]["NotFoundError"]; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; } diff --git a/docs/api.html b/docs/api.html index 9f795dda..b3cda8f2 100644 --- a/docs/api.html +++ b/docs/api.html @@ -424,7 +424,7 @@ 55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864 -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688, -104.0616 -231.873,-231.248 z - " fill="currentColor">

MeetAtMensa (1.0.0)

Download OpenAPI specification:

MeetAtMensa (2.1.1)

Download OpenAPI specification:

This OpenAPI specification defines the endpoints, schemas, and security mechanisms for the Meet@Mensa User micro-service.

@@ -444,6 +444,8 @@ " class="sc-eVqvcJ sc-fszimp sc-etsjJW kIppRw jnwENr ljKHqG">

Authentication failed due to missing or invalid OAuth2 token.

Request samples

Content type
application/json
{
  • "users": {
    }
}

Response samples

Content type
application/json
{
  • "conversationsStarters": [
    ]
}

Matching

Paths belonging to the Matching microservice

Submit matching Request

The request was malformed or contained invalid parameters.

Request samples

Content type
application/json
{
  • "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa",
  • "date": "2019-08-24",
  • "timeslot": [
    ],
  • "location": "GARCHING",
  • "preferences": {
    }
}

Retrieve all matches for a {user-id}

Retrieve all matches for a user with {user-id} from the matching-service

Authorizations:
auth0
path Parameters
user-id
required
string <uuid>

Authentication failed due to missing or invalid OAuth2 token.

Response samples

Content type
application/json
{
  • "matches": {
    }
}

Retrieve all MatchRequests for a {user-id}

Retrieve all MatchRequests for a user with {user-id} from the matching-service

Authorizations:
auth0
path Parameters
user-id
required
string <uuid>

Authentication failed due to missing or invalid OAuth2 token.

Response samples

Content type
application/json
{
  • "requests": [
    ]
}

Delete MatchRequest with {request-id}

Delete MatchRequest with ID {request-id} from the system

Authorizations:
auth0
path Parameters
request-id
required
string <uuid>

Authentication failed due to missing or invalid OAuth2 token.

Update MatchRequest with {request-id}

Update all information in the MatchRequest with ID {request-id}

Authorizations:
auth0
path Parameters
request-id
required
string <uuid>

The requested resource was not found.

Request samples

Content type
application/json
{
  • "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa",
  • "date": "2019-08-24",
  • "timeslot": [
    ],
  • "location": "GARCHING",
  • "preferences": {
    }
}

Response samples

Content type
application/json
{
  • "requestID": "e4619679-f5d9-4eff-9f79-bbded6130bb1",
  • "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa",
  • "date": "2019-08-24",
  • "timeslot": [
    ],
  • "location": "GARCHING",
  • "preferences": {
    },
  • "status": "PENDING"
}

Accept invitation to a given match

Accept invitation to a given match

Authorizations:
auth0
path Parameters
match-id
required
string <uuid>

Authentication failed due to missing or invalid OAuth2 token.

Reject invitation to a given match

Reject invitation to a given match

Authorizations:
auth0
path Parameters
match-id
required
string <uuid>

Authentication failed due to missing or invalid OAuth2 token.

User

Paths belonging to the User microservice

Retrieve User with {user-id}

Authentication failed due to missing or invalid OAuth2 token.

Response samples

Content type
application/json
{
  • "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa",
  • "email": "user@example.com",
  • "firstname": "Max",
  • "lastname": "Mustermann",
  • "birthday": "2019-08-24",
  • "gender": "other",
  • "degree": "msc_informatics",
  • "degreeStart": 2024,
  • "interests": [
    ],
  • "bio": "string"
}

Update User with {user-id}

Update all information about user with ID {user-id} from user-service

Authorizations:
auth0
path Parameters
user-id
required
string <uuid>

Authentication failed due to missing or invalid OAuth2 token.

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "firstname": "Max",
  • "lastname": "Mustermann",
  • "birthday": "2019-08-24",
  • "gender": "other",
  • "degree": "msc_informatics",
  • "degreeStart": 2024,
  • "interests": [
    ],
  • "bio": "string"
}

Response samples

Content type
application/json
{
  • "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa",
  • "email": "user@example.com",
  • "firstname": "Max",
  • "lastname": "Mustermann",
  • "birthday": "2019-08-24",
  • "gender": "other",
  • "degree": "msc_informatics",
  • "degreeStart": 2024,
  • "interests": [
    ],
  • "bio": "string"
}

Delete User with {user-id}

Remove all information about user with ID {user-id} from user-service

Authorizations:
auth0
path Parameters
user-id
required
string <uuid>

The request was malformed or contained invalid parameters.

Register new User

Register a new user and respond with it's {user-id}

Authorizations:
auth0
Request Body schema: application/json
email
required
string <email>

Array of a User's interests

bio
required
string

Short introduction text written by the user

-

Responses

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "firstname": "Max",
  • "lastname": "Mustermann",
  • "birthday": "2019-08-24",
  • "gender": "other",
  • "degree": "msc_informatics",
  • "degreeStart": 2024,
  • "interests": [
    ],
  • "bio": "string"
}

Response samples

Content type
application/json
{
  • "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa",
  • "email": "user@example.com",
  • "firstname": "Max",
  • "lastname": "Mustermann",
  • "birthday": "2019-08-24",
  • "gender": "other",
  • "degree": "msc_informatics",
  • "degreeStart": 2024,
  • "interests": [
    ],
  • "bio": "string"
}
+

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "firstname": "Max",
  • "lastname": "Mustermann",
  • "birthday": "2019-08-24",
  • "gender": "other",
  • "degree": "msc_informatics",
  • "degreeStart": 2024,
  • "interests": [
    ],
  • "bio": "string"
}

Response samples

Content type
application/json
{
  • "userID": "2c3821b8-1cdb-4b77-bcd8-a1da701e46aa",
  • "email": "user@example.com",
  • "firstname": "Max",
  • "lastname": "Mustermann",
  • "birthday": "2019-08-24",
  • "gender": "other",
  • "degree": "msc_informatics",
  • "degreeStart": 2024,
  • "interests": [
    ],
  • "bio": "string"
}