diff --git a/Backend/pom.xml b/Backend/pom.xml index c1c3d99eba866538229bf248aedd5ef73df88eb5..5f3ff16ab949c2424f6294241fab46c5aaede6e4 100644 --- a/Backend/pom.xml +++ b/Backend/pom.xml @@ -18,6 +18,7 @@ <java.version>17</java.version> </properties> + <!-- Springboot --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> @@ -42,12 +43,7 @@ <scope>test</scope> </dependency> - <dependency> - <groupId>com.h2database</groupId> - <artifactId>h2</artifactId> - <scope>runtime</scope> - </dependency> - + <!-- MySQL database --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> @@ -58,13 +54,20 @@ <artifactId>hibernate-validator</artifactId> <version>8.0.0.Final</version> </dependency> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>runtime</scope> + </dependency> + <!-- Servlet --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> </dependency> + <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> @@ -81,6 +84,7 @@ <version>0.11.5</version> </dependency> + <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> @@ -125,6 +129,12 @@ <version>2.0.4</version> </dependency> + <!-- Springboot websockets --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-websocket</artifactId> + </dependency> + </dependencies> diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationRequest.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/AuthenticationRequest.java similarity index 73% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationRequest.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/AuthenticationRequest.java index af473c57a41707ea9c53fbaaa8be1014d6bf7183..f0c9b5fe7bbd46df81388adb392f6c81d410de4d 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationRequest.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/AuthenticationRequest.java @@ -1,10 +1,13 @@ -package edu.ntnu.idatt2105.backend.security.authentication; +package edu.ntnu.idatt2105.backend.DTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +/** + * The request body for authenticating a user. + */ @Data @Builder @AllArgsConstructor diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationResponse.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/AuthenticationResponse.java similarity index 71% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationResponse.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/AuthenticationResponse.java index 40e19094718263a16dc5371dd23472ad7b63e6cc..1571aa5615903cd6afc9d7d730ab8d074749310f 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationResponse.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/AuthenticationResponse.java @@ -1,10 +1,13 @@ -package edu.ntnu.idatt2105.backend.security.authentication; +package edu.ntnu.idatt2105.backend.DTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +/** + * The request body for authenticating a user. + */ @Data @Builder @AllArgsConstructor diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/request/EditRequest.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/EditRequest.java similarity index 76% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/request/EditRequest.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/EditRequest.java index 41acd0f5e296da56930b117b53fc0dffc553e99f..4bdc28dfd64cc3e1d0668cd6a3183d75b159971b 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/request/EditRequest.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/EditRequest.java @@ -1,4 +1,4 @@ -package edu.ntnu.idatt2105.backend.request; +package edu.ntnu.idatt2105.backend.DTO; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/ListingDTO.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/ListingDTO.java index 24d95946e813415983b8f787a9ab442e61aef246..71ae7fe8f648b3b0ef56975f66d6176e42d81821 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/ListingDTO.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/ListingDTO.java @@ -5,8 +5,9 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; - +/** + * The request body for authenticating a listing. Also used for transferring user information. + */ @Data @Builder @NoArgsConstructor @@ -23,7 +24,9 @@ public class ListingDTO { private Boolean isSold; private double price; private int numberOfPictures; + // These two fields are to make frontend development easier. private Boolean isFavoriteToCurrentUser; + private Boolean isCurrentUserOwner; private Long ownerId; } \ No newline at end of file diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/PasswordEditRequest.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/PasswordEditRequest.java new file mode 100644 index 0000000000000000000000000000000000000000..4afab559b21200002933ac3272bb0abb61ff0e16 --- /dev/null +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/PasswordEditRequest.java @@ -0,0 +1,19 @@ +package edu.ntnu.idatt2105.backend.DTO; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * The request body for changing the password of a user. + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PasswordEditRequest { + + private String oldPassword; + private String newPassword; +} diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/RegisterRequest.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/RegisterRequest.java similarity index 80% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/RegisterRequest.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/RegisterRequest.java index a131d2b8e77b563791a1638dbebf172c0caf9eec..d1d89ff0b4ee0fbd64cc531679556d9326b17548 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/RegisterRequest.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/RegisterRequest.java @@ -1,10 +1,13 @@ -package edu.ntnu.idatt2105.backend.security.authentication; +package edu.ntnu.idatt2105.backend.DTO; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +/** + * The request body for registering a user. + */ @Data @Builder @AllArgsConstructor diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/UserDTO.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/UserDTO.java index c158aa75a920089d2d15f62bbd0c90f052109155..b1dd5cadba112841173c0af77ff6b1c1d4bf422a 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/UserDTO.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/DTO/UserDTO.java @@ -6,6 +6,9 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +/** + * The request body for editing a user. The user is identified by the id. Also used for transferring user data. + */ @Data @Builder @NoArgsConstructor diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/config/WebSocketConfig.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/config/WebSocketConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..f249cc76c603e81cc2bd26ee969ce0fbfff82642 --- /dev/null +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/config/WebSocketConfig.java @@ -0,0 +1,25 @@ +package edu.ntnu.idatt2105.backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +//@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { +/* + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.setApplicationDestinationPrefixes("/app"); + registry.enableSimpleBroker("/topic"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/websocket") + .setAllowedOrigins("*") + .withSockJS(); + }*/ +} \ No newline at end of file diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/AuthController.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/AuthController.java index 0f508f363b36783d7582f55586cebcbb52a6aebd..06ea7c4fa5b4319d3e14ce3049d61ac644465fc6 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/AuthController.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/AuthController.java @@ -1,14 +1,20 @@ package edu.ntnu.idatt2105.backend.controller; -import edu.ntnu.idatt2105.backend.security.authentication.AuthenticationRequest; -import edu.ntnu.idatt2105.backend.security.authentication.AuthenticationResponse; -import edu.ntnu.idatt2105.backend.security.authentication.AuthenticationService; -import edu.ntnu.idatt2105.backend.security.authentication.RegisterRequest; -import lombok.NoArgsConstructor; +import edu.ntnu.idatt2105.backend.DTO.AuthenticationRequest; +import edu.ntnu.idatt2105.backend.DTO.AuthenticationResponse; +import edu.ntnu.idatt2105.backend.security.AuthenticationService; +import edu.ntnu.idatt2105.backend.DTO.RegisterRequest; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +/** + * Controller for user authentication. The user can register and login. + * + * @author Brage H. Kvamme + * @version 1.0 + */ @RestController @CrossOrigin("*") @RequestMapping("/api/auth") @@ -17,6 +23,16 @@ public class AuthController { private final AuthenticationService authenticationService; + /** + * Register a new user. The user will be assigned the role USER. The user will be + * authenticated and a JWT token will be returned. The token will be valid for 60 + * minutes. + * + * @param request The request body containing the user information. + * @return A response containing the JWT token. + */ + @Operation(summary = "Register a new user", description = "The user will be assigned the role USER. " + + "The user will be authenticated and a JWT token will be returned. The token will be valid for 60 minutes.") @PostMapping("/register") public ResponseEntity<AuthenticationResponse> register( @RequestBody RegisterRequest request @@ -24,6 +40,15 @@ public class AuthController { return ResponseEntity.ok(authenticationService.register(request)); } + /** + * Authenticate a user. The user will be authenticated and a JWT token will be + * returned. The token will be valid for 60 minutes. + * + * @param request The request body containing the email and password. + * @return A response containing the JWT token. + */ + @Operation(summary = "Authenticate a user", description = "The user will be authenticated and a JWT token " + + "will be returned. The token will be valid for 60 minutes.") @PostMapping("/authenticate") public ResponseEntity<AuthenticationResponse> authenticate( @RequestBody AuthenticationRequest request diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/FileController.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/FileController.java index a292356b6d2f17c453d51e30643e7ba3f105a9d1..288eeb4f64e88d3680c8fea1de408849a2fa730a 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/FileController.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/FileController.java @@ -1,43 +1,34 @@ package edu.ntnu.idatt2105.backend.controller; +import edu.ntnu.idatt2105.backend.service.FileStorageService; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.file.Files; - +/** + * Controller for getting images from the backend. + * + * @author Brage H. Kvamme + * @version 1.0 + */ @RestController @CrossOrigin("*") @RequestMapping("/api/images") @RequiredArgsConstructor public class FileController { - @GetMapping("/{id}/{id2}") - public ResponseEntity<byte[]> getFile(@PathVariable Long id, @PathVariable Long id2) throws IOException { - String filePath = "src/main/resources/images/" + id + "/" + id2; - - File file = new File(filePath); - - if (!file.exists()) { - throw new FileNotFoundException("File not found: " + filePath); - } - - // Read the contents of the file into a byte array - byte[] fileContent = Files.readAllBytes(file.toPath()); - - // Set the content type and content disposition headers - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.IMAGE_JPEG); - headers.setContentDispositionFormData("attachment", Long.toString(id2)); - - // Return the file content as a ResponseEntity - return ResponseEntity.ok() - .headers(headers) - .body(fileContent); + private final FileStorageService fileStorageService; + /** + * Get image from server by listing ID and image ID. + * + * @param listingId id of the listing the image belongs to + * @param imageId id of the image. ID starts with 0 + * @return image as byte array or error message + */ + @Operation(summary = "Get image from server by id", description = "Get image from server by listing ID and image ID.") + @GetMapping("/{listingId}/{imageId}") + public ResponseEntity<byte[]> getFile(@PathVariable Long listingId, @PathVariable Long imageId) { + return fileStorageService.getImageInListing(listingId.toString(), imageId.toString()); } } \ No newline at end of file diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/ListingController.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/ListingController.java index 40d9f2be41d02d25e5131a8dbf2b04937c911cb7..c132848edea86c0add8aa0ccafb676ae2628ad59 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/ListingController.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/ListingController.java @@ -1,37 +1,37 @@ package edu.ntnu.idatt2105.backend.controller; -import com.fasterxml.jackson.core.JsonProcessingException; -import edu.ntnu.idatt2105.backend.Repository.ListingRepository; +import edu.ntnu.idatt2105.backend.DTO.ListingDTO; import edu.ntnu.idatt2105.backend.filter.SearchRequest; import edu.ntnu.idatt2105.backend.model.Listing; -import edu.ntnu.idatt2105.backend.model.User; -import edu.ntnu.idatt2105.backend.request.EditRequest; import edu.ntnu.idatt2105.backend.security.JWTService; import edu.ntnu.idatt2105.backend.service.ListingService; import edu.ntnu.idatt2105.backend.service.UserService; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.util.Iterator; import java.util.List; -import java.util.function.Function; +/** + * Controller for listing endpoints. + * + * @author Brage H. Kvamme + * @version 1.0 + */ @RestController @CrossOrigin("*") @RequestMapping("/api/listing") @RequiredArgsConstructor public class ListingController { - @Autowired - private final ListingRepository listingRepository; + Logger logger = org.slf4j.LoggerFactory.getLogger(ListingController.class); + @Autowired private final JWTService jwtService; @Autowired @@ -39,116 +39,186 @@ public class ListingController { @Autowired private final UserService userService; - // create listing + /** + * Create a new listing. The user must be logged in to create a listing. The listing is created + * from a JSON string and a list of images. The images are saved to the server and are connected to the listing. + * + * @param files List of images. + * @param listingJson JSON string of the listing. + * @return Status code 201 if listing is created, 400 if listing is not created, 401 if not authenticated. + */ + @Operation(summary = "Create a new listing", description = "The user must be logged in to create a listing." + + " The listing is created from a JSON string and a list of images. The images are saved to the server" + + " and are connected to the listing.") @PostMapping("/create") public ResponseEntity<String> createListing( @RequestParam("files") List<MultipartFile> files, - @RequestParam("listing") String listingJson, - @RequestHeader("Authorization") String authHeader + @RequestParam("listing") String listingJson // TODO: Change to DTO? ) { - String loggedInUserName = userService.getUserFromJTW(authHeader).getEmail(); + if(!jwtService.isAuthenticated()) { + return ResponseEntity.status(401).body("User not authenticated, please log in"); + } + String email = jwtService.getAuthenticatedUserEmail(); - String returnMessage; Long num; - if((num = listingService.addListing(listingJson, files, loggedInUserName)) != null) { - returnMessage = num.toString(); + if((num = listingService.addListing(listingJson, files, email)) != null) { + return ResponseEntity.ok(num.toString()); } else { - returnMessage = "Listing not created"; + return ResponseEntity.badRequest().body("Listing not created"); } - return ResponseEntity.ok(returnMessage); } /** * Get listing by id as JSON. If the user is logged in, the favorite boolean is added to the listing. * User don't have to be logged in to get the listing. * - * @param id ID if the listing - * @param authHeader JWT token - * @return Listing as JSON + * @param id ID if the listing. + * @return Listing as JSON. */ + @Operation(summary = "Get a listing by id as JSON", description = "If the user is logged in, the favorite boolean "+ + "is added to the listing. User don't have to be logged in to get the listing.") @GetMapping("/{id}") public ResponseEntity<String> getListing( - @PathVariable Long id, - @RequestHeader(name = "Authorization", required = false) String authHeader + @PathVariable Long id ) { - if (authHeader != null) - return ResponseEntity.ok(listingService.getListingAsJson(id, userService.getUserFromJTW(authHeader).getEmail())); - else - return ResponseEntity.ok(listingService.getListingAsJson(id)); + return listingService.getListingAsJson(id); } - // Deprecated, use search instead + /** + * Get 20 listings as JSON. The search method is strictly better than this method. + * @return 20 listings as JSON. + */ + @Deprecated + @Operation(summary = "Get 20 listings as JSON", description = "The search method is strictly better than this method.") @GetMapping("/get20") public ResponseEntity<String> get20Listings() { return ResponseEntity.ok(listingService.get20ListingsAsJson()); } + /** + * Search for listings. It is possible to search for every field in the listing. It is also possible to + * filter for every field in the listing. + * + * @param request Request body with search and filter parameters. + * @return Page of listings. + */ + @Operation(summary = "Search for listings", description = "It is possible to search for every field in the listing." + + " It is also possible to filter for every field in the listing.") @PostMapping(value = "/search", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) - public Page<Listing> search( - @RequestBody SearchRequest request, - @RequestHeader(name = "Authorization", required = false) String authHeader) { - Page<Listing> page = listingService.searchListing(request); - if(authHeader != null) - page = listingService.addFavoriteBoolean(page, userService.getUserFromJTW(authHeader).getEmail()); - return page; + public ResponseEntity<Page<Listing>> search( + @RequestBody SearchRequest request) { + try { + Page<Listing> page = listingService.searchListing(request); + if(jwtService.isAuthenticated()) + page = listingService.addFavoriteBoolean(page); + return ResponseEntity.ok(page); + } catch (Exception e) { + e.printStackTrace(); + logger.error(e.getMessage()); + return ResponseEntity.status(400).build(); + } + } - @GetMapping("/{id}/delete") - public ResponseEntity<?> deleteListing(@PathVariable Long id, @RequestHeader("Authorization") String authHeader) { - // delete listing - return ResponseEntity.ok(listingService.deleteListing(id, userService.getUserFromJTW(authHeader).getEmail())); + /** + * Deletes a listing. The user must be logged in to delete a listing. + * + * @param id ID of the listing. + * @return A status message with either ok or error. + */ + @Operation(summary = "Delete a listing", description = "The user must be logged in to delete a listing.") + @DeleteMapping("/{id}/delete") + public ResponseEntity<String> deleteListing(@PathVariable Long id) { + if(listingService.deleteListing(id)) + return ResponseEntity.ok("Listing deleted"); + else + return ResponseEntity.status(401).body("Listing not deleted, please check your input data and that you" + + " are logged in"); } - @PostMapping("/{id}/edit") + /** + * Edit a listing. The user must be logged in to edit a listing. + * + * @param id ID of the listing you want to edit. + * @param listingDTO The new listing data. + * @return A status message with either ok or error. + */ + @Operation(summary = "Edit a listing", description = "The user must be logged in to edit a listing.") + @PutMapping("/{id}/edit") public ResponseEntity<String> editListing( @PathVariable Long id, - @RequestHeader("Authorization") String authHeader, - @RequestBody EditRequest editRequest - ) throws JsonProcessingException { - Logger logger = org.slf4j.LoggerFactory.getLogger(ListingController.class); - logger.info("Listing: " + "listingJson"); - return ResponseEntity.ok(listingService.editListing(id, userService.getUserFromJTW(authHeader).getEmail(), - "listingJson")); + @RequestBody ListingDTO listingDTO + ) { + return listingService.editListing(id, listingDTO); } - @PostMapping("/{id}/edit/addPictures") - public ResponseEntity<?> addPicture( + /** + * Add pictures to a listing. The user must be logged in to add pictures to a listing. + * + * @param id ID of the listing. + * @param files List of images. + * @return A status message with either ok or error. + */ + @Operation(summary = "Add pictures to a listing", description = "The user must be logged and own the listing " + + "to add pictures to the listing.") + @PutMapping("/{id}/edit/addPictures") + public ResponseEntity<String> addPicture( @PathVariable Long id, - @RequestHeader("Authorization") String authHeader, @RequestParam("files") List<MultipartFile> files ) { - return ResponseEntity.ok(listingService.addPictures(id, userService.getUserFromJTW(authHeader).getEmail(), files)); + return listingService.addPictures(id, files); } - @GetMapping("/{id}/edit/removePicture/{pictureId}") + /** + * Remove a picture from a listing. The user must be logged in to remove a picture from a listing. + * The user must be the owner of the listing to remove a picture. + * + * @param id ID of the listing. + * @param pictureId ID of the picture. + * @return + */ + @Operation(summary = "Remove a picture from a listing", description = "The user must be logged in to remove a " + + "picture from a listing. The user must be the owner of the listing to remove a picture.") + @PutMapping("/{id}/edit/removePicture/{pictureId}") public ResponseEntity<String> removePicture( @PathVariable Long id, - @PathVariable Long pictureId, - @RequestHeader("Authorization") String authHeader + @PathVariable Long pictureId ) { - return ResponseEntity.ok(listingService.removePicture(id, pictureId, userService.getUserFromJTW(authHeader).getEmail())); + return listingService.removePicture(id, pictureId); } - @GetMapping("/{id}/addFavorite") + /** + * Add a listing to the favorites of the user. The user must be logged in to add a listing to the favorites. + * The user can't add a listing to the favorites if the listing is already in the favorites. + * + * @param id ID of the listing. + * @return A status message with either ok or error. + */ + @Operation(summary = "Add a listing to the favorites of the user", description = "The user must be logged in to " + + "add a listing to the favorites. The user can't add a listing to the favorites if the listing is already " + + "in the favorites.") + @PostMapping("/{id}/addFavorite") public ResponseEntity<String> addFavorite( - @RequestHeader("Authorization") String authHeader, @PathVariable Long id - ) throws JsonProcessingException { - // Extract user ID from the JWT token - User user = userService.getUserFromJTW(authHeader); - - return ResponseEntity.ok(userService.addFavorite(user, id)); + ) { + return userService.addFavorite(id); } - @GetMapping("/{id}/removeFavorite") + /** + * Remove a listing from the favorites of the user. The user must be logged in to remove a listing from the + * favorites. The user can't remove a listing from the favorites if the listing is not in the favorites. + * + * @param id ID of the listing. + * @return A status message with either ok or error. + */ + @Operation(summary = "Remove a listing from the favorites of the user", description = "The user must be logged in "+ + "to remove a listing from the favorites. The user can't remove a listing from the favorites if the " + + "listing is not in the favorites.") + @DeleteMapping("/{id}/removeFavorite") public ResponseEntity<String> removeFavorite( - @RequestHeader("Authorization") String authHeader, @PathVariable Long id - ) throws JsonProcessingException { - // Extract user ID from the JWT token - User user = userService.getUserFromJTW(authHeader); - - return ResponseEntity.ok(userService.removeFavorite(user, id)); + ) { + return userService.removeFavorite(id); } } diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/UserController.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/UserController.java index 93b81c8cdf40be5638db5397e7501139b70385e1..1a45d719b1f60b23a1d0365602000cdc5b3f061d 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/UserController.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/UserController.java @@ -1,18 +1,25 @@ package edu.ntnu.idatt2105.backend.controller; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import edu.ntnu.idatt2105.backend.Repository.UserRepository; +import edu.ntnu.idatt2105.backend.DTO.PasswordEditRequest; +import edu.ntnu.idatt2105.backend.DTO.UserDTO; +import edu.ntnu.idatt2105.backend.repository.UserRepository; import edu.ntnu.idatt2105.backend.model.User; import edu.ntnu.idatt2105.backend.security.JWTService; import edu.ntnu.idatt2105.backend.service.UserService; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; - +/** + * Controller for user information. The user can get their information and edit their information. + * The user can also get their favorite listings and owned listings. + * + * @author Brage H. Kvamme + * @version 1.0 + */ @RestController @CrossOrigin("*") @RequestMapping("/api/user") @@ -20,38 +27,109 @@ import org.springframework.web.bind.annotation.*; public class UserController { @Autowired - private UserService userService; + private final UserService userService; - @GetMapping("/getUser") - public ResponseEntity<String> getUserById( - @RequestHeader("Authorization") String authHeader - ) throws JsonProcessingException { - // Extract user ID from the JWT token - User user = userService.getUserFromJTW(authHeader); + @Autowired + private final JWTService jwtService; + @Autowired + private final UserRepository userRepository; + + /** + * Gets the logged-in user from JWT token. + * + * @return User as JSON string. + * @throws JsonProcessingException if the user cannot be converted to JSON. + */ + @Operation(summary = "Gets the logged-in user from JWT token", description = "The user must be logged in to get" + + " their information") + @GetMapping("/getUser") + public ResponseEntity<String> getUserById() throws JsonProcessingException { + if(!jwtService.isAuthenticated()) { + return ResponseEntity.status(401).body("User not authenticated, please log in"); + } + User user = userRepository.getReferenceById(jwtService.getAuthenticatedUserId()); return ResponseEntity.ok(userService.userToJson(user)); } + /** + * Get the favorite listings of the logged-in user. + * + * @return List of favorite listings as JSON string. + */ + @Operation(summary = "Get the favorite listings of the logged-in user", description = "The user must be logged" + + " in to get the favorite listings") @GetMapping("/getUser/favorites") - public ResponseEntity<String> getUserFavourites( - @RequestHeader("Authorization") String authHeader - ) { - // Extract user ID from the JWT token - User user = userService.getUserFromJTW(authHeader); - + public ResponseEntity<String> getUserFavourites() { + if(!jwtService.isAuthenticated()) { + return ResponseEntity.status(401).body("User not authenticated, please log in"); + } + User user = userRepository.getReferenceById(jwtService.getAuthenticatedUserId()); return ResponseEntity.ok(userService.getFavoritesToJson(user)); } - // Owned listings + /** + * Get the owned listings of the logged-in user. The user must be logged in to get the owned listings. + * + * @return List of owned listings as JSON string. + */ + @Operation(summary = "Get the owned listings of the logged-in user", description = "The user must be logged" + + " in to get the owned listings") @GetMapping("/getUser/listings") - public ResponseEntity<String> getUserListings( - @RequestHeader("Authorization") String authHeader - ) { - // Extract user ID from the JWT token - User user = userService.getUserFromJTW(authHeader); - + public ResponseEntity<String> getUserListings() { + if(!jwtService.isAuthenticated()) { + return ResponseEntity.status(401).body("User not authenticated, please log in"); + } + User user = userRepository.getReferenceById(jwtService.getAuthenticatedUserId()); return ResponseEntity.ok(userService.getListingsToJson(user)); } + /** + * Check if a listing is owned by the user. Used to check if a user can edit a listing. + * + * @param id id of the listing + * @return true if the listing is owned by the user, false otherwise + */ + @Operation(summary = "Check if a listing is owned by the user", description = "Returns true if the listing is" + + " owned by the user, false otherwise") + @GetMapping("/getUser/isOwner/{id}") + public ResponseEntity<Boolean> isOwnedByUser(@PathVariable Long id) { + return userService.isOwnedByUser(id); + } + + /** + * Edits the logged-in user. Send a JSON string with the fields to be edited. The user must be logged in to edit + * their information. With this endpoint, the user can edit all their information except their password. + * + * @param userDTO The user to be edited. + * @return OK if the user was edited successfully, BAD_REQUEST if the user could not be edited. + */ + @Operation(summary = "Edits the logged-in user", description = "The user must be logged in to edit their" + + " information") + @PutMapping("/editUser") + public ResponseEntity<String> editUser(@RequestBody UserDTO userDTO) { + if(!jwtService.isAuthenticated()) { + return ResponseEntity.status(401).body("User not authenticated, please log in"); + } + User user = userRepository.getReferenceById(jwtService.getAuthenticatedUserId()); + return userService.editUser(user, userDTO); + } + + /** + * Edits the logged-in user's password. Send a JSON string with the fields to be edited. The user must be logged in to edit + * their information. With this endpoint, the user can edit their password. + * + * @param passwordEditRequest The user to be edited. Send old and new password as JSON. + * @return OK if the user was edited successfully, BAD_REQUEST if the user could not be edited. + */ + @Operation(summary = "Edits the logged-in user's password", description = "The user must be logged in to edit" + + " their password. They also need a old and a new password. The old password must be correct.") + @PutMapping("/editUser/password") + public ResponseEntity<String> editUserPassword(@RequestBody PasswordEditRequest passwordEditRequest) { + if(!jwtService.isAuthenticated()) { + return ResponseEntity.status(401).body("User not authenticated, please log in"); + } + return jwtService.updatePassword(passwordEditRequest.getOldPassword(), passwordEditRequest.getNewPassword()); + } } \ No newline at end of file diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/WebSocketController.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/WebSocketController.java new file mode 100644 index 0000000000000000000000000000000000000000..1891e84ede43d454c15be6444ac0273bff253267 --- /dev/null +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/controller/WebSocketController.java @@ -0,0 +1,20 @@ +package edu.ntnu.idatt2105.backend.controller; + +import edu.ntnu.idatt2105.backend.service.ConversationService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.stereotype.Controller; + +@Controller +public class WebSocketController { +/* + @Autowired + private ConversationService conversationService; + + @MessageMapping("/send/message") + public void sendMessage(String message) { + // You can customize this method to handle the message as needed. + // For example, you can save the message to the database or process it in some other way. + //conversationService.getOrCreateConversation("/topic/messages", message); + }*/ +} \ No newline at end of file diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/enums/Role.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/enums/Role.java index f0ebf0a3d28b6a02e9beb6cbb21f18a9d640256f..17b82c54a32bc2cb2a95165016f92b8b7dd753ac 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/enums/Role.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/enums/Role.java @@ -1,7 +1,9 @@ package edu.ntnu.idatt2105.backend.enums; +/** + * The different roles a user can have. + */ public enum Role { - ADMIN, USER } diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/exception/ConversationNotFoundException.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/exception/ConversationNotFoundException.java index d9eb537bf815bb18071248af3d93ace8aee5c961..cd3d6662665c6804c8078df599c399503b17520e 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/exception/ConversationNotFoundException.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/exception/ConversationNotFoundException.java @@ -1,5 +1,8 @@ package edu.ntnu.idatt2105.backend.exception; +/** + * Exception thrown when a conversation is not found. + */ public class ConversationNotFoundException extends RuntimeException { public ConversationNotFoundException(String message) { super(message); diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/exception/UserNotFoundException.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/exception/UserNotFoundException.java index 8780923c81599be605d6c52c4c0e025fee75b19b..e0376814d08af1e4434ebda45be263fea9635bfa 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/exception/UserNotFoundException.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/exception/UserNotFoundException.java @@ -1,5 +1,8 @@ package edu.ntnu.idatt2105.backend.exception; +/** + * Exception thrown when a User is not found. + */ public class UserNotFoundException extends RuntimeException { public UserNotFoundException(String message) { super(message); diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/FieldType.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/FieldType.java index 4537fbfc409f3626f9ab5ecd275a461cf4efc774..486f91fdc1aa6938c96dffd4cfe666561d8ee26c 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/FieldType.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/FieldType.java @@ -5,8 +5,10 @@ import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; - -// https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ +/** + * The different types a field can have. + * This class is from this website: https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ + */ @Slf4j public enum FieldType { diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/FilterRequest.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/FilterRequest.java index 80895a971f923f63dd7e48d9b97f0388c5f207f7..8b9d250322cec26080605218b0cd919a28ac45c8 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/FilterRequest.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/FilterRequest.java @@ -13,7 +13,10 @@ import java.io.Serializable; import java.util.List; -//https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ +/** + * The request body for filtering a list of objects. + * This class is from this website: https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ + */ @Data @Builder @NoArgsConstructor diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/Operator.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/Operator.java index cd3dcf6dd6612fb197e23400892d759b8b68b9c0..e4bd896d0e45dfb53bbd734b48bf1fa042118971 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/Operator.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/Operator.java @@ -9,6 +9,10 @@ import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; import java.util.List; +/** + * The different operators a field can have. + * This class is from this website: https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ + */ @Slf4j public enum Operator { diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SearchRequest.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SearchRequest.java index bd018e5db6deca94a429219588e243e93ae7dbfc..844656a55b9ee53aa97382ecd210dd32ec01134f 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SearchRequest.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SearchRequest.java @@ -14,6 +14,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +/** + * The request body for filtering a list of objects. + * This class is from this website: https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ + */ @Data @Builder @NoArgsConstructor diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SearchSpecification.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SearchSpecification.java index b69804c0a9d9e736f5ca12638ffeb1d285310edf..85286cb89d79c179062dad03a7cce633e8275bf3 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SearchSpecification.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SearchSpecification.java @@ -12,6 +12,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +/** + * The specification for filtering a list of objects. + * This class is from this website: https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ + * + * @param <T> The type of the object to filter. + */ @Slf4j @AllArgsConstructor public class SearchSpecification<T> implements Specification<T> { diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SortDirection.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SortDirection.java index 2ee4ae78165f288256603e6da453079e83c2a296..25e93498cc6d624c0b1d800521e0256c346d9dcb 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SortDirection.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SortDirection.java @@ -4,6 +4,10 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.Order; import jakarta.persistence.criteria.Root; +/** + * The different sort directions a field can have. + * This class is from this website: https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ + */ public enum SortDirection { ASC { diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SortRequest.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SortRequest.java index 0d75f6155a5d0a3f869cd54122b0b9e4c8ee7854..49f859565849e207151da2ca98728e60877c8848 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SortRequest.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/filter/SortRequest.java @@ -11,6 +11,10 @@ import lombok.NoArgsConstructor; import java.io.Serial; import java.io.Serializable; +/** + * The request body for filtering a list of objects. + * This class is from this website: https://blog.piinalpin.com/2022/04/searching-and-filtering-using-jpa-specification/ + */ @Data @Builder @NoArgsConstructor diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/model/Listing.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/model/Listing.java index 63b55d31fa32031c882de56be876c680d98c5428..5fa4c3fb59e6b2bf15c93482dec7a04fea57b650 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/model/Listing.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/model/Listing.java @@ -14,6 +14,12 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +/** + * The class that represents a listing in the database. A listing is an object that is for sale. + * + * @author Brage H. Kvamme + * @version 1.0 + */ @Data @Builder @NoArgsConstructor @@ -59,6 +65,8 @@ public class Listing implements Serializable { private LocalDateTime dateCreated; @Column(name = "isFavoriteToCurrentUser") private Boolean isFavoriteToCurrentUser; + @Column(name = "isCurrentUserOwner") + private Boolean isCurrentUserOwner; @JsonIgnore @ManyToMany(mappedBy = "favourites", cascade = CascadeType.ALL, fetch = FetchType.LAZY) diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/model/User.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/model/User.java index e05b7db7552bb6ae78dad8f22f97493c8675aa6e..abc23dc69b76ea1f5486aca73cecef4c0b1b01d1 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/model/User.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/model/User.java @@ -15,6 +15,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +/** + * The class that represents a user in the database. + * + * @author Brage H. Kvamme + * @version 1.0 + */ @Data @Builder @NoArgsConstructor diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/ConversationRepository.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/ConversationRepository.java similarity index 88% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/ConversationRepository.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/ConversationRepository.java index f6a23c4fe0fa755c4dfb1beef89f5379e23f78b6..0c5252bfe092a23ab41969eaaa2699d9d47b72e6 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/ConversationRepository.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/ConversationRepository.java @@ -1,4 +1,4 @@ -package edu.ntnu.idatt2105.backend.Repository; +package edu.ntnu.idatt2105.backend.repository; import edu.ntnu.idatt2105.backend.model.Conversation; import edu.ntnu.idatt2105.backend.model.User; diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/ListingRepository.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/ListingRepository.java similarity index 88% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/ListingRepository.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/ListingRepository.java index 4239894fdd0e1b85c1f5a860af4142785671722a..a4c1f21869e64e93a8011eeba1fdec662211ec54 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/ListingRepository.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/ListingRepository.java @@ -1,4 +1,4 @@ -package edu.ntnu.idatt2105.backend.Repository; +package edu.ntnu.idatt2105.backend.repository; import edu.ntnu.idatt2105.backend.model.Listing; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/MessageRepository.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/MessageRepository.java similarity index 88% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/MessageRepository.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/MessageRepository.java index 1e4dc736ac2c8cd1dd6907adc5b08baf30c35ccf..de3226db1cc041d687387debf2676ee286b593aa 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/MessageRepository.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/MessageRepository.java @@ -1,4 +1,4 @@ -package edu.ntnu.idatt2105.backend.Repository; +package edu.ntnu.idatt2105.backend.repository; import edu.ntnu.idatt2105.backend.model.Conversation; import edu.ntnu.idatt2105.backend.model.Message; diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/UserRepository.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/UserRepository.java similarity index 83% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/UserRepository.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/UserRepository.java index 83fb64e0ab0854a644970a652ac5e963631f3ecf..6c2f576b9ecb8aed2dd15f32010bed65b51018eb 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/Repository/UserRepository.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/repository/UserRepository.java @@ -1,4 +1,4 @@ -package edu.ntnu.idatt2105.backend.Repository; +package edu.ntnu.idatt2105.backend.repository; import edu.ntnu.idatt2105.backend.model.User; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/ApplicationConfig.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/ApplicationConfig.java index 2dc2cc49ce0cfa919f4204c0b549ba6a8b5f46e8..54a30b6cd318548316cd6c82cdb20755527f997c 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/ApplicationConfig.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/ApplicationConfig.java @@ -1,6 +1,6 @@ package edu.ntnu.idatt2105.backend.security; -import edu.ntnu.idatt2105.backend.Repository.UserRepository; +import edu.ntnu.idatt2105.backend.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -17,11 +17,11 @@ import org.springframework.security.crypto.password.PasswordEncoder; @RequiredArgsConstructor public class ApplicationConfig { - private final UserRepository repository; + private final UserRepository userRepository; @Bean public UserDetailsService userDetailsService() { - return username -> repository.findByEmail(username) + return username -> userRepository.findByEmail(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); } diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationService.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/AuthenticationService.java similarity index 81% rename from Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationService.java rename to Backend/src/main/java/edu/ntnu/idatt2105/backend/security/AuthenticationService.java index 215ea8174a00d54f4a85491ab052c9878d66b2c9..647668224f511a6af619b5505cb66032fb767417 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/authentication/AuthenticationService.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/AuthenticationService.java @@ -1,16 +1,19 @@ -package edu.ntnu.idatt2105.backend.security.authentication; +package edu.ntnu.idatt2105.backend.security; -import edu.ntnu.idatt2105.backend.Repository.UserRepository; +import edu.ntnu.idatt2105.backend.DTO.AuthenticationRequest; +import edu.ntnu.idatt2105.backend.DTO.AuthenticationResponse; +import edu.ntnu.idatt2105.backend.DTO.RegisterRequest; +import edu.ntnu.idatt2105.backend.repository.UserRepository; import edu.ntnu.idatt2105.backend.model.User; import edu.ntnu.idatt2105.backend.enums.Role; -import edu.ntnu.idatt2105.backend.security.JWTService; import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.util.NoSuchElementException; + @Service @RequiredArgsConstructor public class AuthenticationService { @@ -19,10 +22,7 @@ public class AuthenticationService { private final JWTService jwtService; private final AuthenticationManager authenticationManager; - Logger logger = org.slf4j.LoggerFactory.getLogger(AuthenticationService.class); - public AuthenticationResponse register(RegisterRequest request) { - logger.info("Registering user with email: " + request.getEmail()); var user = User.builder() .firstName(request.getFirstname()) .lastName(request.getLastname()) @@ -39,8 +39,7 @@ public class AuthenticationService { .build(); } - public AuthenticationResponse authenticate(AuthenticationRequest request) { - logger.info("Authenticating user with email: " + request.getEmail()); + public AuthenticationResponse authenticate(AuthenticationRequest request) throws NoSuchElementException { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( request.getEmail(), diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/JWTService.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/JWTService.java index 0fb17e145fa59bcf2a27a4a311b199d8145067f0..087399a54779a43733f90e7278a6bae9efc96cd2 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/JWTService.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/security/JWTService.java @@ -1,6 +1,6 @@ package edu.ntnu.idatt2105.backend.security; -import edu.ntnu.idatt2105.backend.Repository.UserRepository; +import edu.ntnu.idatt2105.backend.repository.UserRepository; import edu.ntnu.idatt2105.backend.model.User; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -8,7 +8,12 @@ import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.security.Key; import java.util.Date; @@ -25,6 +30,9 @@ public class JWTService { @Autowired private UserRepository userRepository; + @Autowired + private PasswordEncoder passwordEncoder; + // expire in 60 minutes public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) { return Jwts.builder() @@ -47,6 +55,28 @@ public class JWTService { return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } + public boolean isAuthenticated() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication != null && !(authentication instanceof AnonymousAuthenticationToken) && authentication.isAuthenticated(); + } + + public Long getAuthenticatedUserId() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null && authentication.getPrincipal() instanceof User) { + User user = (User) authentication.getPrincipal(); + return user.getId(); + } + return null; + } + + public String getAuthenticatedUserEmail() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + return authentication.getName(); + } + return null; + } + private boolean isTokenExpired(String token) { return extractExpiration(token).before(new Date()); } @@ -59,6 +89,26 @@ public class JWTService { return extractClaim(token, Claims::getSubject); } + public ResponseEntity<String> updatePassword(String oldPassword, String newPassword) { + if(oldPassword == null) + return ResponseEntity.badRequest().body("Old password cannot be null"); + if(newPassword == null) + return ResponseEntity.badRequest().body("New password cannot be null"); + + if(userRepository.findById(getAuthenticatedUserId()).isEmpty()) + return ResponseEntity.badRequest().body("User not found"); + + User user = userRepository.findById(getAuthenticatedUserId()).get(); + + if (!passwordEncoder.matches(oldPassword, user.getPassword())) { + return ResponseEntity.badRequest().body("Old password is wrong"); + } + + user.setPassword(passwordEncoder.encode(newPassword)); + userRepository.save(user); + return ResponseEntity.ok("Password updated"); + } + public long extractId(String token) { String mail = extractUsername(token); return userRepository.findByEmail(mail).get().getId(); diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/ConversationService.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/ConversationService.java index 0c4f69547bb16cc4e41384c63ac48bedfb7b58e9..e6058cdd189c118f76caea2da9c94d3710662974 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/ConversationService.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/ConversationService.java @@ -1,8 +1,8 @@ package edu.ntnu.idatt2105.backend.service; -import edu.ntnu.idatt2105.backend.Repository.ConversationRepository; -import edu.ntnu.idatt2105.backend.Repository.MessageRepository; -import edu.ntnu.idatt2105.backend.Repository.UserRepository; +import edu.ntnu.idatt2105.backend.repository.ConversationRepository; +import edu.ntnu.idatt2105.backend.repository.MessageRepository; +import edu.ntnu.idatt2105.backend.repository.UserRepository; import edu.ntnu.idatt2105.backend.exception.ConversationNotFoundException; import edu.ntnu.idatt2105.backend.exception.UserNotFoundException; import edu.ntnu.idatt2105.backend.model.Conversation; diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/FileStorageService.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/FileStorageService.java index b6556cd75dec226829ac26c28aa62c6befde8cc0..7352aeb5213bacc7b0a4c74de613e13796faa3ab 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/FileStorageService.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/FileStorageService.java @@ -3,6 +3,9 @@ package edu.ntnu.idatt2105.backend.service; import lombok.RequiredArgsConstructor; import org.apache.tomcat.util.http.fileupload.FileUtils; import org.slf4j.Logger; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -70,6 +73,23 @@ public class FileStorageService { return file; } + public ResponseEntity<byte[]> getImageInListing(String listingId, String imageIndex) { + Path filePath = this.fileStorageLocation.resolve(listingId + "/" + imageIndex).normalize(); + File file = filePath.toFile(); + if (!file.exists()) { + return ResponseEntity.badRequest().build(); + } + try { + byte[] fileContent = Files.readAllBytes(file.toPath()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\""+file.getName()+"\"") + .contentType(MediaType.IMAGE_JPEG) + .body(fileContent); + } catch (IOException e) { + return ResponseEntity.badRequest().build(); + } + } + public boolean deleteFolder(String folderName) throws IOException { Path folderPath = this.fileStorageLocation.resolve(folderName).normalize(); File folder = folderPath.toFile(); diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/ListingService.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/ListingService.java index c6d5d751ff0d1463a6d5d4d06675dcc74132cbd4..cbc02c17c35fa13c13664b48af075dca83c8bf45 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/ListingService.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/ListingService.java @@ -1,19 +1,18 @@ package edu.ntnu.idatt2105.backend.service; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import edu.ntnu.idatt2105.backend.DTO.ListingDTO; -import edu.ntnu.idatt2105.backend.Repository.ListingRepository; -import edu.ntnu.idatt2105.backend.Repository.UserRepository; +import edu.ntnu.idatt2105.backend.repository.ListingRepository; +import edu.ntnu.idatt2105.backend.repository.UserRepository; import edu.ntnu.idatt2105.backend.filter.SearchRequest; import edu.ntnu.idatt2105.backend.filter.SearchSpecification; import edu.ntnu.idatt2105.backend.model.Listing; -import jakarta.persistence.EntityManager; +import edu.ntnu.idatt2105.backend.security.JWTService; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -21,7 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicBoolean; @Service @RequiredArgsConstructor @@ -32,6 +31,7 @@ public class ListingService { private final ListingRepository listingRepository; private final UserRepository userRepository; private final FileStorageService fileStorageService; + private final JWTService jwtService; public Page<Listing> searchListing(SearchRequest request) { SearchSpecification<Listing> specification = new SearchSpecification<>(request); @@ -39,48 +39,58 @@ public class ListingService { return listingRepository.findAll(specification, pageable); } - public String deleteListing(long id, String email) { - AtomicReference<String> message = new AtomicReference<>(""); + public boolean deleteListing(long id) throws IllegalArgumentException { + AtomicBoolean deleted = new AtomicBoolean(false); listingRepository.findById(id).ifPresent(listing -> { - if(listing.getOwner().getEmail().equals(email)) { + if(listing.getOwner().getId().equals(jwtService.getAuthenticatedUserId())) { listingRepository.deleteById(id); try { - logger.info("Is Images with id " + id + " deleted: " - + fileStorageService.deleteFolder(Long.toString(id))); + fileStorageService.deleteFolder(Long.toString(id)); + deleted.set(true); } catch (IOException e) { e.printStackTrace(); } - message.set("Listing deleted"); - } else { - message.set("You are not the owner of this listing"); } }); - return message.get(); + return deleted.get(); } - public String getListingAsJson(long id) { + /** + * Gets a listing from the database and returns it as a JSON string. If the user is logged in, the + * listing is has a boolean value indicating whether the listing is a favourite of the user. + * + * @param id The id of the listing to be returned. + * @return A JSON string containing the listing. + */ + public ResponseEntity<String> getListingAsJson(long id) { ObjectMapper mapper = new ObjectMapper(); - String json = ""; - try { - json = mapper.writeValueAsString(convertToListingDTO(listingRepository.findById(id).get(), false)); - } catch (Exception e) { - logger.error("Error converting listing to json: " + e); - } - return json; - } - - public String getListingAsJson(long id, String email) { - ObjectMapper mapper = new ObjectMapper(); - String json = ""; - try { - json = mapper.writeValueAsString(convertToListingDTO(listingRepository.findById(id).get(), - userRepository.findByEmail(email).get().getFavourites().contains(listingRepository.findById(id).get()))); - } catch (Exception e) { - logger.error("Error converting listing to json: " + e); + String json; + if(jwtService.isAuthenticated()) { + try { + json = mapper.writeValueAsString( + convertToListingDTO(listingRepository.findById(id).get()) + ); + } catch (Exception e) { + logger.error("Error converting listing to json: " + e); + return ResponseEntity.badRequest().body("Error converting listing to json: " + e); + } + } else { + try { + json = mapper.writeValueAsString(convertToListingDTO(listingRepository.findById(id).get())); + } catch (Exception e) { + logger.error("Error converting listing to json: " + e); + return ResponseEntity.badRequest().body("Error converting listing to json: " + e); + } } - return json; + return ResponseEntity.ok(json); } + /** + * Gets 20 Listings. This method is deprecated. + * + * @return A JSON string containing 20 listings. + */ + @Deprecated public String get20ListingsAsJson() { logger.info("Starting to convert listings to json"); ObjectMapper mapper = new ObjectMapper(); @@ -123,6 +133,7 @@ public class ListingService { .price(listingDTO.getPrice()) .owner(userRepository.findByEmail(email).get()) .isSold(false) + .isCurrentUserOwner(false) .isFavoriteToCurrentUser(false) .numberOfPictures(files.size()) .dateCreated(java.time.LocalDateTime.now()) @@ -144,31 +155,24 @@ public class ListingService { } } - public Page<Listing> addFavoriteBoolean(Page<Listing> listings, String email) { + public Page<Listing> addFavoriteBoolean(Page<Listing> listings) { + for(Listing listing : listings) { - listing.setIsFavoriteToCurrentUser(userRepository.findByEmail(email).get().getFavourites().contains(listing)); + listing.setIsFavoriteToCurrentUser(userRepository.findByEmail(jwtService.getAuthenticatedUserEmail()) + .get().getFavourites().contains(listing)); } return listings; } - public ListingDTO convertToListingDTO(Listing listing, Boolean isFavoriteToCurrentUser) { - return ListingDTO.builder() - .id(listing.getId()) - .description(listing.getDescription()) - .briefDescription(listing.getBriefDescription()) - .category(listing.getCategory()) - .address(listing.getAddress()) - .latitude(listing.getLatitude()) - .longitude(listing.getLongitude()) - .isSold(listing.getIsSold()) - .price(listing.getPrice()) - .numberOfPictures(listing.getNumberOfPictures()) - .ownerId(listing.getOwner().getId()) - .isFavoriteToCurrentUser(isFavoriteToCurrentUser) - .build(); - } - public ListingDTO convertToListingDTO(Listing listing) { + boolean isFavoriteToCurrentUser = false; + boolean isCurrentUserOwner = false; + if(jwtService.isAuthenticated()) + isCurrentUserOwner = listing.getOwner().getEmail().equals(jwtService.getAuthenticatedUserEmail()); + if(jwtService.isAuthenticated()) + isFavoriteToCurrentUser = userRepository.findByEmail(jwtService.getAuthenticatedUserEmail()) + .get().getFavourites().contains(listing); + return ListingDTO.builder() .id(listing.getId()) .description(listing.getDescription()) @@ -181,17 +185,15 @@ public class ListingService { .price(listing.getPrice()) .numberOfPictures(listing.getNumberOfPictures()) .ownerId(listing.getOwner().getId()) - .isFavoriteToCurrentUser(false) + .isFavoriteToCurrentUser(isFavoriteToCurrentUser) + .isCurrentUserOwner(isCurrentUserOwner) .build(); } - public String editListing(Long id, String email, String listingJson) throws JsonProcessingException { + public ResponseEntity<String> editListing(Long id, ListingDTO listingDTO) { try { - ObjectMapper mapper = new ObjectMapper(); - logger.info("ListingService: editListing: " + listingJson); - ListingDTO listingDTO = mapper.readValue(listingJson, ListingDTO.class); - logger.info("ListingService: editListing: " + listingDTO.toString()); - if(listingRepository.findById(id).get().getOwner().getEmail().equals(email)) { + logger.info("ListingService: editListing: " + listingDTO); + if(listingRepository.findById(id).get().getOwner().getId().equals(jwtService.getAuthenticatedUserId())) { Listing listing = listingRepository.findById(id).get(); if(listingDTO.getDescription() != null) listing.setDescription(listingDTO.getDescription()); @@ -201,65 +203,66 @@ public class ListingService { listing.setCategory(listingDTO.getCategory()); if(listingDTO.getAddress() != null) listing.setAddress(listingDTO.getAddress()); - if(listingDTO.getLatitude() == 0L) + if(listingDTO.getLatitude() != 0L) listing.setLatitude(listingDTO.getLatitude()); - if(listingDTO.getLongitude() == 0L) + if(listingDTO.getLongitude() != 0L) listing.setLongitude(listingDTO.getLongitude()); listingRepository.save(listing); - return "Listing edited"; + return ResponseEntity.ok("Listing edited"); } else { - return "You are not the owner of this listing"; + return ResponseEntity.status(401).body("You are not the owner of this listing"); } } catch(Exception e) { logger.error("Error editing listing: " + e); - return "Error editing listing"; + return ResponseEntity.status(400).body("Error editing listing"); } } - public String addPictures(Long id, String email, List<MultipartFile> files) { + public ResponseEntity<String> addPictures(Long id, List<MultipartFile> files) { if(files.isEmpty()) { - return "No files selected"; + return ResponseEntity.status(400).body("No files selected"); } // check if file is image for(MultipartFile file : files) if(!Objects.requireNonNull(file.getContentType()).contains("image")) - return "File is not an image"; - + return ResponseEntity.status(400).body("At least one file is not an image"); try { - if(listingRepository.findById(id).get().getOwner().getEmail().equals(email)) { + if(listingRepository.findById(id).get().getOwner().getId().equals(jwtService.getAuthenticatedUserId())) { for(MultipartFile file : files) { - fileStorageService.handleFileUpload(file, id.toString(), Integer.toString(listingRepository.findById(id).get().getNumberOfPictures())); + fileStorageService.handleFileUpload( + file, + id.toString(), + Integer.toString(listingRepository.findById(id).get().getNumberOfPictures()) + ); Listing listing = listingRepository.findById(id).get(); listing.setNumberOfPictures(listing.getNumberOfPictures() + 1); listingRepository.save(listing); } - return "Pictures added"; + return ResponseEntity.ok("Pictures added"); } else { - return "You are not the owner of this listing"; + return ResponseEntity.status(401).body("You are not the owner of this listing"); } } catch (Exception e) { logger.error("Error adding pictures to listing: " + e); - return "Error adding pictures to listing"; + return ResponseEntity.status(400).body("Error adding pictures to listing: " + e); } - } - public String removePicture(Long id, Long pictureId, String email) { + public ResponseEntity<String> removePicture(Long id, Long pictureId) { try { - if(listingRepository.findById(id).get().getOwner().getEmail().equals(email)) { + if(listingRepository.findById(id).get().getOwner().getId().equals(jwtService.getAuthenticatedUserId())) { fileStorageService.deleteFile(id.toString(), pictureId.toString()); fileStorageService.removeFileGaps(id.toString()); Listing listing = listingRepository.findById(id).get(); listing.setNumberOfPictures(listing.getNumberOfPictures() - 1); listingRepository.save(listing); - return "Picture removed"; + return ResponseEntity.ok("Picture removed"); } else { - return "You are not the owner of this listing"; + return ResponseEntity.status(401).body("You are not the owner of this listing"); } } catch (Exception e) { logger.error("Error removing picture from listing: " + e); - return "Error removing picture from listing"; + return ResponseEntity.status(400).body("Error removing picture from listing: " + e); } - } } diff --git a/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/UserService.java b/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/UserService.java index e1e84d333c26eb7ac3e3e8ec43d47dc621ca6d71..62b5d791aaf679893df49cddc712f95363e4e108 100644 --- a/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/UserService.java +++ b/Backend/src/main/java/edu/ntnu/idatt2105/backend/service/UserService.java @@ -4,13 +4,14 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import edu.ntnu.idatt2105.backend.DTO.ListingDTO; import edu.ntnu.idatt2105.backend.DTO.UserDTO; -import edu.ntnu.idatt2105.backend.Repository.ListingRepository; -import edu.ntnu.idatt2105.backend.Repository.UserRepository; +import edu.ntnu.idatt2105.backend.repository.ListingRepository; +import edu.ntnu.idatt2105.backend.repository.UserRepository; import edu.ntnu.idatt2105.backend.model.Listing; import edu.ntnu.idatt2105.backend.model.User; import edu.ntnu.idatt2105.backend.security.JWTService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -59,7 +60,7 @@ public class UserService { List<Listing> favourites = user.getFavourites(); List<ListingDTO> listingDTOS = new ArrayList<>(); for (Listing listing: favourites) { - listingDTOS.add(listingService.convertToListingDTO(listing, true)); + listingDTOS.add(listingService.convertToListingDTO(listing)); } ObjectMapper objectMapper = new ObjectMapper(); try { @@ -70,43 +71,54 @@ public class UserService { return null; } - public String addFavorite(User user, Long listingId) { + public ResponseEntity<String> addFavorite(Long listingId) { try { + User user = userRepository.findById(jwtService.getAuthenticatedUserId()).get(); List<Listing> favourites = user.getFavourites(); Listing listing = listingRepository.findById(listingId).get(); if (favourites.contains(listing)) { - return "Listing already in favourites"; + return ResponseEntity.badRequest().body("Listing already in favourites"); } favourites.add(listing); user.setFavourites(favourites); userRepository.save(user); - return "Listing added to favourites"; + return ResponseEntity.ok("Listing added to favourites"); } catch (Exception e) { - return "Listing couldn't be added to favourites: " + e.getMessage(); + return ResponseEntity.badRequest().body("Listing couldn't be added to favourites: " + e.getMessage()); } } - public String removeFavorite(User user, Long listingID) { + /** + * Removes a listing from the authenticated user's favourites. + * Returns a bad request if the listing is not in the user's favourites. + * Returns a bad request if the listing does not exist. + * Returns a bad request if the user does not exist. + * Returns ok if the listing was removed from the user's favourites. + * + * @param listingID the id of the listing to be removed from the user's favourites + * @return a response entity with the appropriate status code and message + */ + public ResponseEntity<String> removeFavorite(Long listingID) { try { + User user = userRepository.findById(jwtService.getAuthenticatedUserId()).get(); Listing listing = listingRepository.findById(listingID).get(); List<Listing> favourites = user.getFavourites(); if (!favourites.remove(listing)) { - return "Listing not in favourites"; + return ResponseEntity.badRequest().body("Listing not in favourites"); } user.setFavourites(favourites); userRepository.save(user); - return "Listing removed from favourites"; + return ResponseEntity.ok("Listing removed from favourites"); } catch (Exception e) { - return "Listing couldn't be removed from favourites: " + e.getMessage(); + return ResponseEntity.badRequest().body("Listing couldn't be removed from favourites: " + e.getMessage()); } - } public String getListingsToJson(User user) { List<Listing> listings = user.getListings(); List<ListingDTO> listingDTOS = new ArrayList<>(); for (Listing listing: listings) { - listingDTOS.add(listingService.convertToListingDTO(listing, user.getFavourites().contains(listing))); + listingDTOS.add(listingService.convertToListingDTO(listing)); } ObjectMapper objectMapper = new ObjectMapper(); try { @@ -116,4 +128,34 @@ public class UserService { } return null; } + + /** + * Checks if the authenticated user owns the listing with the given id. Returns true if the user owns the listing, + * false otherwise. + * + * @param id the id of the listing to be checked + * @return a response entity with the appropriate status code and message + */ + public ResponseEntity<Boolean> isOwnedByUser(Long id) { + if (jwtService.isAuthenticated()) { + User user = userRepository.findById(jwtService.getAuthenticatedUserId()).get(); + Listing listing = listingRepository.findById(id).get(); + return ResponseEntity.ok(user.getListings().contains(listing)); + } else { + return ResponseEntity.badRequest().body(false); + } + } + + public ResponseEntity<String> editUser(User user, UserDTO userDTO) { + if(userDTO.getFirstName() != null) + user.setFirstName(userDTO.getFirstName()); + if(userDTO.getLastName() != null) + user.setLastName(userDTO.getLastName()); + if(userDTO.getPhoneNumber() != null) + user.setPhoneNumber(userDTO.getPhoneNumber()); + if (userDTO.getAddress() != null) + user.setAddress(userDTO.getAddress()); + userRepository.save(user); + return ResponseEntity.ok("User edited"); + } } diff --git a/Backend/src/main/resources/images/252/0 b/Backend/src/main/resources/images/252/0 new file mode 100644 index 0000000000000000000000000000000000000000..2c589e13f25022d49bab786fc1d6073099b2aa4d Binary files /dev/null and b/Backend/src/main/resources/images/252/0 differ diff --git a/Backend/src/main/resources/images/302/0 b/Backend/src/main/resources/images/302/0 new file mode 100644 index 0000000000000000000000000000000000000000..0cd8f0418e37f1003d4d0573aa5007bdc94c13cd Binary files /dev/null and b/Backend/src/main/resources/images/302/0 differ diff --git a/Backend/src/main/resources/images/303/0 b/Backend/src/main/resources/images/303/0 new file mode 100644 index 0000000000000000000000000000000000000000..5877970d31a0346c71b557692a30aa7322353026 Binary files /dev/null and b/Backend/src/main/resources/images/303/0 differ diff --git a/Backend/src/main/resources/images/352/0 b/Backend/src/main/resources/images/352/0 new file mode 100644 index 0000000000000000000000000000000000000000..d320543e2a8cb4e6c7c90008e0912c2f76867ef5 Binary files /dev/null and b/Backend/src/main/resources/images/352/0 differ diff --git a/Backend/src/main/resources/images/352/1 b/Backend/src/main/resources/images/352/1 new file mode 100644 index 0000000000000000000000000000000000000000..de7b3facacb25fb02f001ad3e3f941ad9e3e1f27 Binary files /dev/null and b/Backend/src/main/resources/images/352/1 differ diff --git a/Backend/src/main/resources/images/352/2 b/Backend/src/main/resources/images/352/2 new file mode 100644 index 0000000000000000000000000000000000000000..0cd8f0418e37f1003d4d0573aa5007bdc94c13cd Binary files /dev/null and b/Backend/src/main/resources/images/352/2 differ diff --git a/Backend/src/main/resources/images/352/3 b/Backend/src/main/resources/images/352/3 new file mode 100644 index 0000000000000000000000000000000000000000..b3056fb5b440aa0c2f392cfed1b129c734ef111f Binary files /dev/null and b/Backend/src/main/resources/images/352/3 differ diff --git a/Backend/src/main/resources/images/352/4 b/Backend/src/main/resources/images/352/4 new file mode 100644 index 0000000000000000000000000000000000000000..5877970d31a0346c71b557692a30aa7322353026 Binary files /dev/null and b/Backend/src/main/resources/images/352/4 differ diff --git a/Backend/src/main/resources/images/352/5 b/Backend/src/main/resources/images/352/5 new file mode 100644 index 0000000000000000000000000000000000000000..8a2ee556214aa5d8fe08954dc5d6d7a2c4375e69 Binary files /dev/null and b/Backend/src/main/resources/images/352/5 differ diff --git a/Backend/src/main/resources/images/353/0 b/Backend/src/main/resources/images/353/0 new file mode 100644 index 0000000000000000000000000000000000000000..de7b3facacb25fb02f001ad3e3f941ad9e3e1f27 Binary files /dev/null and b/Backend/src/main/resources/images/353/0 differ diff --git a/Backend/src/main/resources/images/353/1 b/Backend/src/main/resources/images/353/1 new file mode 100644 index 0000000000000000000000000000000000000000..0cd8f0418e37f1003d4d0573aa5007bdc94c13cd Binary files /dev/null and b/Backend/src/main/resources/images/353/1 differ diff --git a/Backend/src/main/resources/images/353/2 b/Backend/src/main/resources/images/353/2 new file mode 100644 index 0000000000000000000000000000000000000000..b3056fb5b440aa0c2f392cfed1b129c734ef111f Binary files /dev/null and b/Backend/src/main/resources/images/353/2 differ diff --git a/Backend/src/main/resources/images/353/3 b/Backend/src/main/resources/images/353/3 new file mode 100644 index 0000000000000000000000000000000000000000..5877970d31a0346c71b557692a30aa7322353026 Binary files /dev/null and b/Backend/src/main/resources/images/353/3 differ diff --git a/Backend/src/main/resources/images/353/4 b/Backend/src/main/resources/images/353/4 new file mode 100644 index 0000000000000000000000000000000000000000..8a2ee556214aa5d8fe08954dc5d6d7a2c4375e69 Binary files /dev/null and b/Backend/src/main/resources/images/353/4 differ diff --git a/Backend/src/main/resources/images/402/0 b/Backend/src/main/resources/images/402/0 new file mode 100644 index 0000000000000000000000000000000000000000..de7b3facacb25fb02f001ad3e3f941ad9e3e1f27 Binary files /dev/null and b/Backend/src/main/resources/images/402/0 differ diff --git a/Backend/src/test/java/edu/ntnu/idatt2105/backend/controller/AuthControllerTest.java b/Backend/src/test/java/edu/ntnu/idatt2105/backend/controller/AuthControllerTest.java index 01b50148457278cc1b31e3c8bacb8b4f9cae9794..8612091e24eb2972872faacfc2b8248a19068a9d 100644 --- a/Backend/src/test/java/edu/ntnu/idatt2105/backend/controller/AuthControllerTest.java +++ b/Backend/src/test/java/edu/ntnu/idatt2105/backend/controller/AuthControllerTest.java @@ -1,9 +1,9 @@ package edu.ntnu.idatt2105.backend.controller; -import edu.ntnu.idatt2105.backend.security.authentication.AuthenticationRequest; -import edu.ntnu.idatt2105.backend.security.authentication.AuthenticationResponse; -import edu.ntnu.idatt2105.backend.security.authentication.AuthenticationService; -import edu.ntnu.idatt2105.backend.security.authentication.RegisterRequest; +import edu.ntnu.idatt2105.backend.DTO.AuthenticationRequest; +import edu.ntnu.idatt2105.backend.DTO.AuthenticationResponse; +import edu.ntnu.idatt2105.backend.security.AuthenticationService; +import edu.ntnu.idatt2105.backend.DTO.RegisterRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -14,9 +14,6 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.when; - @ExtendWith(SpringExtension.class) @SpringBootTest @AutoConfigureMockMvc