From f0c1dbd0a6622213a3d6b0a5dcb9b8b4f7142187 Mon Sep 17 00:00:00 2001 From: Karelin <> Date: Sun, 2 Jan 2022 22:05:27 +0100 Subject: [PATCH 1/6] First part of profile-picture implementation --- API-SPECIFICATION.md | 5 +++ README.md | 27 +++++++++++++ build.gradle | 1 + .../finder/ApplicationInitializer.java | 30 ++++++++++++++ .../finder/CustomGlobalExceptionHandler.java | 10 ++++- .../business/finder/FinderApplication.java | 3 +- .../application/ExternalServiceUploader.java | 15 +++++++ .../application/LocalPictureUploader.java | 25 ++++++++++++ .../LocalPictureUploaderService.java | 26 ++++++++++++ .../application/PictureUploaderResponse.java | 15 +++++++ .../application/PictureUploaderStrategy.java | 7 ++++ .../exception/UploadPictureException.java | 9 +++++ .../port/LocalPictureUploaderUseCase.java | 21 ++++++++++ .../UploadUserProfilePictureService.java | 33 +++++++++++++++ .../application/port/CreateUserUseCase.java | 2 +- .../application/port/UpdateUserUseCase.java | 2 +- .../port/UploadUserProfilePictureUseCase.java | 36 +++++++++++++++++ .../user/db/BfProfilePictureRepository.java | 7 ++++ .../finder/user/domain/BfProfilePicture.java | 40 +++++++++++++++++++ .../business/finder/user/domain/BfUser.java | 2 + .../user/domain/{ => type}/BfUserStatus.java | 2 +- .../user/domain/{ => type}/BfUserType.java | 2 +- .../domain/type/ProfilePictureStorage.java | 6 +++ .../finder/user/web/RegisterController.java | 2 +- .../finder/user/web/UserController.java | 22 ++++++++-- src/main/resources/application.properties | 5 +++ 26 files changed, 345 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/business/finder/ApplicationInitializer.java create mode 100644 src/main/java/com/business/finder/upload/application/ExternalServiceUploader.java create mode 100644 src/main/java/com/business/finder/upload/application/LocalPictureUploader.java create mode 100644 src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java create mode 100644 src/main/java/com/business/finder/upload/application/PictureUploaderResponse.java create mode 100644 src/main/java/com/business/finder/upload/application/PictureUploaderStrategy.java create mode 100644 src/main/java/com/business/finder/upload/application/exception/UploadPictureException.java create mode 100644 src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java create mode 100644 src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java create mode 100644 src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java create mode 100644 src/main/java/com/business/finder/user/db/BfProfilePictureRepository.java create mode 100644 src/main/java/com/business/finder/user/domain/BfProfilePicture.java rename src/main/java/com/business/finder/user/domain/{ => type}/BfUserStatus.java (51%) rename src/main/java/com/business/finder/user/domain/{ => type}/BfUserType.java (52%) create mode 100644 src/main/java/com/business/finder/user/domain/type/ProfilePictureStorage.java diff --git a/API-SPECIFICATION.md b/API-SPECIFICATION.md index 9feab43..048c299 100644 --- a/API-SPECIFICATION.md +++ b/API-SPECIFICATION.md @@ -42,6 +42,11 @@ PUT: /investment-proposal/{investment-proposal-uuid} -> update given investment- DELETE: /investment-proposal/{investment-proposal-uuid} -> delete investment by UUID; +==============================[User-Profile-Picture-Controller] +PUT: /user/profile-picture/ -> update user profile picture. (contains also add operation); + +DELETE: /user/profile-picture/ -> remove user profile picture. + InvestmentProposalEntity(WIP): - subject; - description; diff --git a/README.md b/README.md index adc4fec..8c2bffa 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,35 @@ TODO: 10. Make sure that every business action has been logged. 11. User is removed. Implement scheduler for removing archive partnership-proposals. 12. One user can add only one proposal. +13. Announcements to users. Mobile app: 1. Announcement 2. Announcement -> update. 3. "Remove user" button should be implemented in application for sure. + +Questions: + +Form Partnership Proposal: +1. Subject +2. Industry +3. Country +4. City +5. KnowledgeOfProposalCreator +6. Team Available +7. Team Description +8. Additional Description +9. Team language +10. Attachment? (business plan) + +Form Investment Proposal: +1. Subject +2. Project Description +3. Country +4. City +5. Team language (optional) +6. Min investment (optional) +7. Project Budget (optional) +8. Payback period (optional) +9. History of company (optional) +10. Attachment? (business plan) diff --git a/build.gradle b/build.gradle index ab2ef0d..917d9e0 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation 'org.hibernate:hibernate-validator:7.0.2.Final' implementation 'org.apache.commons:commons-lang3:3.0' implementation 'org.springframework.boot:spring-boot-starter-security:2.6.1' + implementation 'org.springframework:spring-web:5.3.14' // implementation('io.springfox:springfox-swagger2:3.0.0') // implementation('io.springfox:springfox-swagger-ui:2.8.0') } diff --git a/src/main/java/com/business/finder/ApplicationInitializer.java b/src/main/java/com/business/finder/ApplicationInitializer.java new file mode 100644 index 0000000..2c294e4 --- /dev/null +++ b/src/main/java/com/business/finder/ApplicationInitializer.java @@ -0,0 +1,30 @@ +package com.business.finder; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +@Component +public class ApplicationInitializer implements ApplicationRunner { + + @Value("${bf.user.profile.picture.folder}") + private String profilePictureUploadPath; + + @Override + public void run(ApplicationArguments args) throws Exception { + createDirectoriesForLocalProfilePictures(); + } + + private void createDirectoriesForLocalProfilePictures() { + try { + Files.createDirectories(Paths.get(profilePictureUploadPath)); + } catch (IOException e) { + throw new RuntimeException("Could not create upload folder! Error: " + e); + } + } +} diff --git a/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java b/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java index f4691dc..f7e9494 100644 --- a/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java +++ b/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java @@ -2,12 +2,14 @@ import com.business.finder.partnership.application.exception.NoAccessToPartnershipProposalException; import com.business.finder.partnership.application.exception.PartnershipProposalIsNotFoundException; +import com.business.finder.upload.application.exception.UploadPictureException; import com.business.finder.user.application.exception.BfUserException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.multipart.MaxUploadSizeExceededException; import java.util.Date; import java.util.LinkedHashMap; @@ -29,8 +31,14 @@ public ResponseEntity handleException(MethodArgumentNotValidException ex return handleError(HttpStatus.BAD_REQUEST, errors); } + @ExceptionHandler(MaxUploadSizeExceededException.class) + public ResponseEntity handleMaxSizeException(MaxUploadSizeExceededException exc) { + return handleError(HttpStatus.EXPECTATION_FAILED, List.of("Unable to upload. File is too large! Internal info: " + exc)); + } + @ExceptionHandler({IllegalArgumentException.class, - BfUserException.class}) + BfUserException.class, + UploadPictureException.class}) public ResponseEntity handleBadRequest(RuntimeException ex) { return handleError(HttpStatus.BAD_REQUEST, List.of(ex.getMessage())); } diff --git a/src/main/java/com/business/finder/FinderApplication.java b/src/main/java/com/business/finder/FinderApplication.java index 5eebec8..d9e6371 100644 --- a/src/main/java/com/business/finder/FinderApplication.java +++ b/src/main/java/com/business/finder/FinderApplication.java @@ -3,11 +3,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import javax.annotation.PostConstruct; + @SpringBootApplication public class FinderApplication { public static void main(String[] args) { SpringApplication.run(FinderApplication.class, args); } - } diff --git a/src/main/java/com/business/finder/upload/application/ExternalServiceUploader.java b/src/main/java/com/business/finder/upload/application/ExternalServiceUploader.java new file mode 100644 index 0000000..e541d9f --- /dev/null +++ b/src/main/java/com/business/finder/upload/application/ExternalServiceUploader.java @@ -0,0 +1,15 @@ +package com.business.finder.upload.application; + +import org.hibernate.cfg.NotYetImplementedException; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +@Component("externalServiceUploader") +class ExternalServiceUploader implements PictureUploaderStrategy { + + @Override + public PictureUploaderResponse upload(MultipartFile file, String fileName, String path) { + throw new NotYetImplementedException("ExternalServiceUploader strategy is only template. It's not implemented at this moment."); + } + +} diff --git a/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java b/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java new file mode 100644 index 0000000..c5917e1 --- /dev/null +++ b/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java @@ -0,0 +1,25 @@ +package com.business.finder.upload.application; + +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Component("localPictureUploader") +class LocalPictureUploader implements PictureUploaderStrategy { + + @Override + public PictureUploaderResponse upload(MultipartFile file, String fileName, String path) { + Path root = Paths.get(path); + try { + Files.copy(file.getInputStream(), root.resolve(fileName + "." + StringUtils.getFilenameExtension(file.getOriginalFilename()))); + } catch (Exception e) { + e.printStackTrace(); + } + return PictureUploaderResponse.OK; + } +} diff --git a/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java b/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java new file mode 100644 index 0000000..db43ab4 --- /dev/null +++ b/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java @@ -0,0 +1,26 @@ +package com.business.finder.upload.application; + +import com.business.finder.upload.application.exception.UploadPictureException; +import com.business.finder.upload.application.port.LocalPictureUploaderUseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +class LocalPictureUploaderService implements LocalPictureUploaderUseCase { + + private final PictureUploaderStrategy localPictureUploader; + + @Override + public Response upload(MultipartFile file, String fileName, String path) { + PictureUploaderResponse response = localPictureUploader.upload(file, "fileName", "path"); + if (response.getErrors().isEmpty()) { + return Response.OK; + } else { + throw new UploadPictureException("Unhandled business exception during uploading local picture: " + response.getErrors().get(0)); + } + } + +} diff --git a/src/main/java/com/business/finder/upload/application/PictureUploaderResponse.java b/src/main/java/com/business/finder/upload/application/PictureUploaderResponse.java new file mode 100644 index 0000000..e978eab --- /dev/null +++ b/src/main/java/com/business/finder/upload/application/PictureUploaderResponse.java @@ -0,0 +1,15 @@ +package com.business.finder.upload.application; + + +import lombok.Value; + +import java.util.Collections; +import java.util.List; + +@Value +public class PictureUploaderResponse { + public static PictureUploaderResponse OK = new PictureUploaderResponse(true, Collections.emptyList()); + + boolean success; + List errors; // should be enum, if it will be usable. +} \ No newline at end of file diff --git a/src/main/java/com/business/finder/upload/application/PictureUploaderStrategy.java b/src/main/java/com/business/finder/upload/application/PictureUploaderStrategy.java new file mode 100644 index 0000000..343b689 --- /dev/null +++ b/src/main/java/com/business/finder/upload/application/PictureUploaderStrategy.java @@ -0,0 +1,7 @@ +package com.business.finder.upload.application; + +import org.springframework.web.multipart.MultipartFile; + +public interface PictureUploaderStrategy { + PictureUploaderResponse upload(MultipartFile file, String fileName, String path); +} diff --git a/src/main/java/com/business/finder/upload/application/exception/UploadPictureException.java b/src/main/java/com/business/finder/upload/application/exception/UploadPictureException.java new file mode 100644 index 0000000..c5c9290 --- /dev/null +++ b/src/main/java/com/business/finder/upload/application/exception/UploadPictureException.java @@ -0,0 +1,9 @@ +package com.business.finder.upload.application.exception; + +public class UploadPictureException extends RuntimeException { + + public UploadPictureException(String errorMessage) { + super(errorMessage); + } + +} diff --git a/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java b/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java new file mode 100644 index 0000000..f29ef01 --- /dev/null +++ b/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java @@ -0,0 +1,21 @@ +package com.business.finder.upload.application.port; + +import lombok.Value; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Collections; +import java.util.List; + +public interface LocalPictureUploaderUseCase { + + Response upload(MultipartFile file, String fileName, String path); + + @Value + class Response { + public static Response OK = new Response(true, Collections.emptyList()); + + boolean success; + List errors; // should be enum, if we will use it. + } + +} diff --git a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java new file mode 100644 index 0000000..a612891 --- /dev/null +++ b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java @@ -0,0 +1,33 @@ +package com.business.finder.user.application; + +import com.business.finder.upload.application.port.LocalPictureUploaderUseCase; +import com.business.finder.user.application.port.UploadUserProfilePictureUseCase; +import com.business.finder.user.db.BfProfilePictureRepository; +import com.business.finder.user.domain.BfProfilePicture; +import lombok.RequiredArgsConstructor; +import org.hibernate.cfg.NotYetImplementedException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UploadUserProfilePictureService implements UploadUserProfilePictureUseCase { + + @Value("${bf.user.profile.picture.folder}") + private String userProfilePictureFolder; + + private final LocalPictureUploaderUseCase localPictureUploaderUseCase; + private final BfProfilePictureRepository profilePictureRepository; + + @Override + public Response upload(UploadUserProfilePictureCommand command) { + final String fileName = command.getUserId().toString(); + LocalPictureUploaderUseCase.Response response = localPictureUploaderUseCase.upload(command.getFile(), fileName, userProfilePictureFolder); + if (response.isSuccess()) { + BfProfilePicture entity = new BfProfilePicture(fileName, command.getUserId()); + profilePictureRepository.save(entity); + } else { + throw new NotYetImplementedException("TODO"); + } + } +} diff --git a/src/main/java/com/business/finder/user/application/port/CreateUserUseCase.java b/src/main/java/com/business/finder/user/application/port/CreateUserUseCase.java index 44471c9..7c63179 100644 --- a/src/main/java/com/business/finder/user/application/port/CreateUserUseCase.java +++ b/src/main/java/com/business/finder/user/application/port/CreateUserUseCase.java @@ -1,6 +1,6 @@ package com.business.finder.user.application.port; -import com.business.finder.user.domain.BfUserType; +import com.business.finder.user.domain.type.BfUserType; import lombok.Value; import java.util.Arrays; diff --git a/src/main/java/com/business/finder/user/application/port/UpdateUserUseCase.java b/src/main/java/com/business/finder/user/application/port/UpdateUserUseCase.java index 6fd0ff0..93ce45b 100644 --- a/src/main/java/com/business/finder/user/application/port/UpdateUserUseCase.java +++ b/src/main/java/com/business/finder/user/application/port/UpdateUserUseCase.java @@ -14,7 +14,7 @@ class UpdateUserResponse { public static UpdateUserResponse OK = new UpdateUserResponse(true, Collections.emptyList()); boolean success; - List errors; + List errors;// TODO(skarelin): DeleteUserUseCase.Error?? Fix it. } @Value diff --git a/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java b/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java new file mode 100644 index 0000000..903ca2e --- /dev/null +++ b/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java @@ -0,0 +1,36 @@ +package com.business.finder.user.application.port; + +import lombok.Value; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.NotNull; +import java.util.Collections; +import java.util.List; + +public interface UploadUserProfilePictureUseCase { + + Response upload(UploadUserProfilePictureCommand command); + + @Value + class Response { + + public static Response OK = new Response(true, Collections.emptyList()); + + public static Response error(Error error) { + return new Response(false, Collections.singletonList(error)); + } + + boolean success; + List errors; + } + + @Value + class UploadUserProfilePictureCommand { + @NotNull MultipartFile file; + @NotNull Long userId; + } + + enum Error { + + } +} diff --git a/src/main/java/com/business/finder/user/db/BfProfilePictureRepository.java b/src/main/java/com/business/finder/user/db/BfProfilePictureRepository.java new file mode 100644 index 0000000..659baac --- /dev/null +++ b/src/main/java/com/business/finder/user/db/BfProfilePictureRepository.java @@ -0,0 +1,7 @@ +package com.business.finder.user.db; + +import com.business.finder.user.domain.BfProfilePicture; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BfProfilePictureRepository extends JpaRepository { +} diff --git a/src/main/java/com/business/finder/user/domain/BfProfilePicture.java b/src/main/java/com/business/finder/user/domain/BfProfilePicture.java new file mode 100644 index 0000000..c009a96 --- /dev/null +++ b/src/main/java/com/business/finder/user/domain/BfProfilePicture.java @@ -0,0 +1,40 @@ +package com.business.finder.user.domain; + +import com.business.finder.jpa.BaseEntity; +import com.business.finder.user.domain.type.ProfilePictureStorage; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Table(name = "BF_PROFILE_PICTURE") +@Getter +@Setter +@Entity +@EntityListeners(AuditingEntityListener.class) +@NoArgsConstructor +// Why just not field in BfUser entity? At this moment we are not sure where will be stored pictures of users. +// It can be cloud, external-service, local machine, some other solution. +// That's why at this moment we need some extra information. Maybe, this entity will be useful in the future. +public class BfProfilePicture extends BaseEntity { + + @CreatedDate + private LocalDateTime createdAt; + + private String fileName; + + @Enumerated(EnumType.STRING) + private ProfilePictureStorage pictureStorage; + + private Long userId; + + public BfProfilePicture(String fileName, Long userId) { + this.fileName = fileName; + this.userId = userId; + this.pictureStorage = ProfilePictureStorage.LOCAL; + } +} diff --git a/src/main/java/com/business/finder/user/domain/BfUser.java b/src/main/java/com/business/finder/user/domain/BfUser.java index 19aebcc..47f8060 100644 --- a/src/main/java/com/business/finder/user/domain/BfUser.java +++ b/src/main/java/com/business/finder/user/domain/BfUser.java @@ -2,6 +2,8 @@ import com.business.finder.jpa.BaseEntity; import com.business.finder.partnership.domain.PartnershipProposal; +import com.business.finder.user.domain.type.BfUserStatus; +import com.business.finder.user.domain.type.BfUserType; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; diff --git a/src/main/java/com/business/finder/user/domain/BfUserStatus.java b/src/main/java/com/business/finder/user/domain/type/BfUserStatus.java similarity index 51% rename from src/main/java/com/business/finder/user/domain/BfUserStatus.java rename to src/main/java/com/business/finder/user/domain/type/BfUserStatus.java index 416e632..bcedf40 100644 --- a/src/main/java/com/business/finder/user/domain/BfUserStatus.java +++ b/src/main/java/com/business/finder/user/domain/type/BfUserStatus.java @@ -1,4 +1,4 @@ -package com.business.finder.user.domain; +package com.business.finder.user.domain.type; public enum BfUserStatus { NEW, ACTIVATED diff --git a/src/main/java/com/business/finder/user/domain/BfUserType.java b/src/main/java/com/business/finder/user/domain/type/BfUserType.java similarity index 52% rename from src/main/java/com/business/finder/user/domain/BfUserType.java rename to src/main/java/com/business/finder/user/domain/type/BfUserType.java index 08189ef..00b93f8 100644 --- a/src/main/java/com/business/finder/user/domain/BfUserType.java +++ b/src/main/java/com/business/finder/user/domain/type/BfUserType.java @@ -1,4 +1,4 @@ -package com.business.finder.user.domain; +package com.business.finder.user.domain.type; public enum BfUserType { PERSONAL, BUSINESS diff --git a/src/main/java/com/business/finder/user/domain/type/ProfilePictureStorage.java b/src/main/java/com/business/finder/user/domain/type/ProfilePictureStorage.java new file mode 100644 index 0000000..e1301cf --- /dev/null +++ b/src/main/java/com/business/finder/user/domain/type/ProfilePictureStorage.java @@ -0,0 +1,6 @@ +package com.business.finder.user.domain.type; + + +public enum ProfilePictureStorage { + LOCAL +} diff --git a/src/main/java/com/business/finder/user/web/RegisterController.java b/src/main/java/com/business/finder/user/web/RegisterController.java index 2378ee8..864fdee 100644 --- a/src/main/java/com/business/finder/user/web/RegisterController.java +++ b/src/main/java/com/business/finder/user/web/RegisterController.java @@ -3,7 +3,7 @@ import com.business.finder.user.application.port.CreateUserUseCase; import com.business.finder.user.application.port.CreateUserUseCase.CreateUserCommand; import com.business.finder.user.application.port.CreateUserUseCase.CreateUserResponse; -import com.business.finder.user.domain.BfUserType; +import com.business.finder.user.domain.type.BfUserType; import lombok.Data; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/business/finder/user/web/UserController.java b/src/main/java/com/business/finder/user/web/UserController.java index dc9f694..ac9df07 100644 --- a/src/main/java/com/business/finder/user/web/UserController.java +++ b/src/main/java/com/business/finder/user/web/UserController.java @@ -1,9 +1,13 @@ package com.business.finder.user.web; +import com.business.finder.security.UserEntityDetails; import com.business.finder.user.application.port.DeleteUserUseCase; import com.business.finder.user.application.port.DeleteUserUseCase.DeleteUserResponse; import com.business.finder.user.application.port.UpdateUserUseCase; import com.business.finder.user.application.port.UpdateUserUseCase.UpdateUserCommand; +import com.business.finder.user.application.port.UpdateUserUseCase.UpdateUserResponse; +import com.business.finder.user.application.port.UploadUserProfilePictureUseCase; +import com.business.finder.user.application.port.UploadUserProfilePictureUseCase.UploadUserProfilePictureCommand; import lombok.AllArgsConstructor; import lombok.Data; import org.springframework.http.ResponseEntity; @@ -11,6 +15,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.validation.Valid; import javax.validation.constraints.Size; @@ -25,6 +30,7 @@ public class UserController { private final DeleteUserUseCase deleteUserUseCase; private final UpdateUserUseCase updateUserUseCase; + private final UploadUserProfilePictureUseCase uploadUserProfilePictureUseCase; @DeleteMapping public ResponseEntity deleteUser(@RequestParam String email, @AuthenticationPrincipal UserDetails user) { @@ -40,10 +46,20 @@ public ResponseEntity deleteUser(@RequestParam String email, } } - // TODO. Profile picture should be here. Probably MultipartFile and send it to server; + @PutMapping - public void updateUser(@Valid @RequestBody RestUpdateUserCommand command, @AuthenticationPrincipal UserDetails user) { - updateUserUseCase.update(command.toUpdateUserCommand(user.getUsername())); + public ResponseEntity updateUser(@Valid @RequestBody RestUpdateUserCommand command, @AuthenticationPrincipal UserDetails user) { + UpdateUserResponse response = updateUserUseCase.update(command.toUpdateUserCommand(user.getUsername())); + if (response.isSuccess()) { + return ResponseEntity.ok(response); + } else { + return ResponseEntity.badRequest().body(response); + } + } + + @PutMapping("/profile-picture") + public void uploadUserProfilePicture(@RequestParam MultipartFile file, @AuthenticationPrincipal UserEntityDetails userDetails) { + uploadUserProfilePictureUseCase.upload(new UploadUserProfilePictureCommand(file, userDetails.getCurrentUserId())); } @Data diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 937f12a..815a4d1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -10,6 +10,11 @@ spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true +spring.servlet.multipart.max-file-size=700KB +spring.servlet.multipart.max-request-size=700KB + +bf.user.profile.picture.folder=../user-profile-pictures + #security debug logs #logging.level.org.springframework.security=debug \ No newline at end of file From 3ea2d9c2dfbcaf929d411e684096b1f9e3764652 Mon Sep 17 00:00:00 2001 From: Karelin <> Date: Mon, 3 Jan 2022 00:40:57 +0100 Subject: [PATCH 2/6] Validators for uploading profile pictures --- README.md | 43 ++++++------------- .../API-SPECIFICATION.md | 0 docs/TECH-SPECIFICATION.md | 21 +++++++++ .../finder/CustomGlobalExceptionHandler.java | 1 + .../port/QueryPartnershipProposalUseCase.java | 4 +- .../LocalPictureUploaderService.java | 6 +-- .../port/LocalPictureUploaderUseCase.java | 6 +-- .../UploadUserProfilePictureService.java | 39 ++++++++++++++--- .../application/port/CreateUserUseCase.java | 2 +- .../port/UploadUserProfilePictureUseCase.java | 16 +++---- .../UploadUserProfilePictureValidator.java | 27 ++++++++++++ .../finder/user/domain/BfProfilePicture.java | 8 +++- .../domain/type/ProfilePictureStatus.java | 5 +++ .../finder/user/web/UserController.java | 10 ++++- src/main/resources/application.properties | 1 + 15 files changed, 132 insertions(+), 57 deletions(-) rename API-SPECIFICATION.md => docs/API-SPECIFICATION.md (100%) create mode 100644 docs/TECH-SPECIFICATION.md create mode 100644 src/main/java/com/business/finder/user/application/validator/UploadUserProfilePictureValidator.java create mode 100644 src/main/java/com/business/finder/user/domain/type/ProfilePictureStatus.java diff --git a/README.md b/README.md index 8c2bffa..d23b7dc 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,23 @@ 20.12.1996 - project started. -Tech specification: -1. Prefix for every table in database: BF_ (business-finder) -2. At this moment in APIs we have two types errors: business (success + errors), request (timestamp + status + errors); -Later probably we will use only one structure. -- JSON structure for business error: -{ - success: true/false, - errors: [...] -} -3. Email is the unique username for every user in application. -4. Spring Security: we are not using @PreAuthorize. Only @Secured is allowed. -5. We have system user in application. See `BfSecurityConfiguration.systemUser()`; Use `private final User systemUser;` to inject it. -6. Every API should be documented. Later it's more readable in Swagger. -7. (if mobile application should be updated) Change `bf.application.version` in properties for every deploy to PROD environment. (should be CI/CD in future) -8. (not now, after logging implementation) We are using LogBack. We should log every business action. -9. If you want to add the custom business exception, please remember to update `CustomGlobalExceptionHandler` -10. Entity as a response should be mapped to DTO. -11. Command should be one-directional. - TODO: + 1. Add flyway; 2. Correct configuration for database; 3. build.gradle - version should be declared as variables; -4. @DeleteMapping for user - requirement for iPhone users. -5. Pay attention to @Transactional annotation. -6. Expiring user. If subscription is cancelled. Job which making the user expired and unactivated. -7. Countries and cities (probably it will be stored in database) -8. Admin panel. PartnershipProposal entity - add there field about verified. -9. Add swagger. -10. Make sure that every business action has been logged. -11. User is removed. Implement scheduler for removing archive partnership-proposals. -12. One user can add only one proposal. -13. Announcements to users. +4. Pay attention to @Transactional annotation. +5. Expiring user. If subscription is cancelled. Job which making the user expired and unactivated. +6. Countries and cities (probably it will be stored in database) +7. Admin panel. PartnershipProposal entity - add there field about verified. +8. Add swagger. +9. Make sure that every business action has been logged. +10. User is removed. Implement scheduler for removing archive partnership-proposals. +11. Announcements to users. +12. Change Error enum to ErrorCode. It's more readable. +13. Compare Partnership and Investment entities. For example Language enum should be already added! Mobile app: + 1. Announcement 2. Announcement -> update. 3. "Remove user" button should be implemented in application for sure. @@ -42,6 +25,7 @@ Mobile app: Questions: Form Partnership Proposal: + 1. Subject 2. Industry 3. Country @@ -54,6 +38,7 @@ Form Partnership Proposal: 10. Attachment? (business plan) Form Investment Proposal: + 1. Subject 2. Project Description 3. Country diff --git a/API-SPECIFICATION.md b/docs/API-SPECIFICATION.md similarity index 100% rename from API-SPECIFICATION.md rename to docs/API-SPECIFICATION.md diff --git a/docs/TECH-SPECIFICATION.md b/docs/TECH-SPECIFICATION.md new file mode 100644 index 0000000..967e2de --- /dev/null +++ b/docs/TECH-SPECIFICATION.md @@ -0,0 +1,21 @@ +Tech specification: + +1. Prefix for every table in database: BF_ (business-finder) +2. At this moment in APIs we have two types errors: business (success + errors), request (timestamp + status + errors); + Later probably we will use only one structure. + +- JSON structure for business error: + { success: true/false, errors: [...] + } + +3. Email is the unique username for every user in application. +4. Spring Security: we are not using @PreAuthorize. Only @Secured is allowed. +5. We have system user in application. See `BfSecurityConfiguration.systemUser()`; Use `private final User systemUser;` + to inject it. +6. Every API should be documented. Later it's more readable in Swagger. +7. (if mobile application should be updated) Change `bf.application.version` in properties for every deploy to PROD + environment. (should be CI/CD in future) +8. (not now, after logging implementation) We are using LogBack. We should log every business action. +9. If you want to add the custom business exception, please remember to update `CustomGlobalExceptionHandler` +10. Entity as a response should be mapped to DTO. +11. Command should be one-directional. \ No newline at end of file diff --git a/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java b/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java index f7e9494..724850f 100644 --- a/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java +++ b/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java @@ -18,6 +18,7 @@ import java.util.stream.Collectors; @ControllerAdvice +// TODO. Add trace-id here I think. For every RuntimeException. Easier to analyze later. class CustomGlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) diff --git a/src/main/java/com/business/finder/partnership/application/port/QueryPartnershipProposalUseCase.java b/src/main/java/com/business/finder/partnership/application/port/QueryPartnershipProposalUseCase.java index bba2233..d059ba4 100644 --- a/src/main/java/com/business/finder/partnership/application/port/QueryPartnershipProposalUseCase.java +++ b/src/main/java/com/business/finder/partnership/application/port/QueryPartnershipProposalUseCase.java @@ -74,11 +74,11 @@ class Response { public static Response OK = new Response(true, Collections.emptyList()); public static Response errors(Error... errors) { - return new Response(true, Arrays.asList(errors)); + return new Response(false, Arrays.asList(errors)); } public static Response errors(List errors) { - return new Response(true, errors); + return new Response(false, errors); } boolean success; diff --git a/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java b/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java index db43ab4..cda42e1 100644 --- a/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java +++ b/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java @@ -14,10 +14,10 @@ class LocalPictureUploaderService implements LocalPictureUploaderUseCase { private final PictureUploaderStrategy localPictureUploader; @Override - public Response upload(MultipartFile file, String fileName, String path) { - PictureUploaderResponse response = localPictureUploader.upload(file, "fileName", "path"); + public LocalPictureUploadedResponse upload(MultipartFile file, String fileName, String path) { + PictureUploaderResponse response = localPictureUploader.upload(file, fileName, path); if (response.getErrors().isEmpty()) { - return Response.OK; + return LocalPictureUploadedResponse.OK; } else { throw new UploadPictureException("Unhandled business exception during uploading local picture: " + response.getErrors().get(0)); } diff --git a/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java b/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java index f29ef01..b58e075 100644 --- a/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java +++ b/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java @@ -8,11 +8,11 @@ public interface LocalPictureUploaderUseCase { - Response upload(MultipartFile file, String fileName, String path); + LocalPictureUploadedResponse upload(MultipartFile file, String fileName, String path); @Value - class Response { - public static Response OK = new Response(true, Collections.emptyList()); + class LocalPictureUploadedResponse { + public static LocalPictureUploadedResponse OK = new LocalPictureUploadedResponse(true, Collections.emptyList()); boolean success; List errors; // should be enum, if we will use it. diff --git a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java index a612891..311763f 100644 --- a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java +++ b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java @@ -1,13 +1,21 @@ package com.business.finder.user.application; import com.business.finder.upload.application.port.LocalPictureUploaderUseCase; +import com.business.finder.upload.application.port.LocalPictureUploaderUseCase.LocalPictureUploadedResponse; import com.business.finder.user.application.port.UploadUserProfilePictureUseCase; +import com.business.finder.user.application.validator.UploadUserProfilePictureValidator; import com.business.finder.user.db.BfProfilePictureRepository; import com.business.finder.user.domain.BfProfilePicture; +import com.business.finder.user.domain.type.ProfilePictureStatus; +import com.business.finder.user.domain.type.ProfilePictureStorage; import lombok.RequiredArgsConstructor; -import org.hibernate.cfg.NotYetImplementedException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.transaction.Transactional; +import java.util.List; @Service @RequiredArgsConstructor @@ -18,16 +26,33 @@ public class UploadUserProfilePictureService implements UploadUserProfilePicture private final LocalPictureUploaderUseCase localPictureUploaderUseCase; private final BfProfilePictureRepository profilePictureRepository; + private final UploadUserProfilePictureValidator validator; @Override - public Response upload(UploadUserProfilePictureCommand command) { + @Transactional + public UploadUserProfilePictureResponse upload(UploadUserProfilePictureCommand command) { final String fileName = command.getUserId().toString(); - LocalPictureUploaderUseCase.Response response = localPictureUploaderUseCase.upload(command.getFile(), fileName, userProfilePictureFolder); + final String fileExtension = getFileExtensionFrom(command.getFile()); + + List errors = validator.validate(fileExtension); + + if (!errors.isEmpty()) { + return UploadUserProfilePictureResponse.errors(errors); + } + + BfProfilePicture entity = new BfProfilePicture(fileName, command.getUserId(), ProfilePictureStorage.LOCAL); + BfProfilePicture savedEntity = profilePictureRepository.save(entity); + + LocalPictureUploadedResponse response = localPictureUploaderUseCase.upload(command.getFile(), fileName, userProfilePictureFolder); + if (response.isSuccess()) { - BfProfilePicture entity = new BfProfilePicture(fileName, command.getUserId()); - profilePictureRepository.save(entity); - } else { - throw new NotYetImplementedException("TODO"); + savedEntity.setStatus(ProfilePictureStatus.PICTURE_UPLOADED); } + + return UploadUserProfilePictureResponse.OK; + } + + private String getFileExtensionFrom(MultipartFile file) { + return StringUtils.getFilenameExtension(file.getOriginalFilename()); } } diff --git a/src/main/java/com/business/finder/user/application/port/CreateUserUseCase.java b/src/main/java/com/business/finder/user/application/port/CreateUserUseCase.java index 7c63179..814fbbc 100644 --- a/src/main/java/com/business/finder/user/application/port/CreateUserUseCase.java +++ b/src/main/java/com/business/finder/user/application/port/CreateUserUseCase.java @@ -27,7 +27,7 @@ public static CreateUserResponse error(Error userErrorCode) { } public static CreateUserResponse errors(Error... errors) { - return new CreateUserResponse(true, Arrays.asList(errors)); + return new CreateUserResponse(false, Arrays.asList(errors)); } boolean success; diff --git a/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java b/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java index 903ca2e..675ef60 100644 --- a/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java +++ b/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java @@ -9,19 +9,19 @@ public interface UploadUserProfilePictureUseCase { - Response upload(UploadUserProfilePictureCommand command); + UploadUserProfilePictureResponse upload(UploadUserProfilePictureCommand command); @Value - class Response { + class UploadUserProfilePictureResponse { - public static Response OK = new Response(true, Collections.emptyList()); + public static UploadUserProfilePictureResponse OK = new UploadUserProfilePictureResponse(true, Collections.emptyList()); - public static Response error(Error error) { - return new Response(false, Collections.singletonList(error)); + public static UploadUserProfilePictureResponse errors(List errors) { + return new UploadUserProfilePictureResponse(false, errors); } boolean success; - List errors; + List errors; } @Value @@ -30,7 +30,7 @@ class UploadUserProfilePictureCommand { @NotNull Long userId; } - enum Error { - + enum ErrorCode { + NOT_ALLOWED_EXTENSION } } diff --git a/src/main/java/com/business/finder/user/application/validator/UploadUserProfilePictureValidator.java b/src/main/java/com/business/finder/user/application/validator/UploadUserProfilePictureValidator.java new file mode 100644 index 0000000..a9b2a0c --- /dev/null +++ b/src/main/java/com/business/finder/user/application/validator/UploadUserProfilePictureValidator.java @@ -0,0 +1,27 @@ +package com.business.finder.user.application.validator; + +import com.business.finder.user.application.port.UploadUserProfilePictureUseCase.ErrorCode; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class UploadUserProfilePictureValidator { + + @Value("${bf.user.profile.picture.allowed-extensions}") + private List allowedProfilePictureExtensions; + + public List validate(String pictureExtension) { + List errors = new ArrayList<>(); + boolean isAllowed = allowedProfilePictureExtensions + .stream() + .anyMatch(allowedExtension -> allowedExtension.equals(pictureExtension)); + if (!isAllowed) { + errors.add(ErrorCode.NOT_ALLOWED_EXTENSION); + } + return errors; + } + +} diff --git a/src/main/java/com/business/finder/user/domain/BfProfilePicture.java b/src/main/java/com/business/finder/user/domain/BfProfilePicture.java index c009a96..9db7093 100644 --- a/src/main/java/com/business/finder/user/domain/BfProfilePicture.java +++ b/src/main/java/com/business/finder/user/domain/BfProfilePicture.java @@ -1,6 +1,7 @@ package com.business.finder.user.domain; import com.business.finder.jpa.BaseEntity; +import com.business.finder.user.domain.type.ProfilePictureStatus; import com.business.finder.user.domain.type.ProfilePictureStorage; import lombok.Getter; import lombok.NoArgsConstructor; @@ -30,11 +31,14 @@ public class BfProfilePicture extends BaseEntity { @Enumerated(EnumType.STRING) private ProfilePictureStorage pictureStorage; + private ProfilePictureStatus status; + private Long userId; - public BfProfilePicture(String fileName, Long userId) { + public BfProfilePicture(String fileName, Long userId, ProfilePictureStorage storage) { this.fileName = fileName; this.userId = userId; - this.pictureStorage = ProfilePictureStorage.LOCAL; + this.pictureStorage = storage; + this.status = ProfilePictureStatus.NEW; } } diff --git a/src/main/java/com/business/finder/user/domain/type/ProfilePictureStatus.java b/src/main/java/com/business/finder/user/domain/type/ProfilePictureStatus.java new file mode 100644 index 0000000..aac1bf9 --- /dev/null +++ b/src/main/java/com/business/finder/user/domain/type/ProfilePictureStatus.java @@ -0,0 +1,5 @@ +package com.business.finder.user.domain.type; + +public enum ProfilePictureStatus { + NEW, PICTURE_UPLOADED +} diff --git a/src/main/java/com/business/finder/user/web/UserController.java b/src/main/java/com/business/finder/user/web/UserController.java index ac9df07..617b612 100644 --- a/src/main/java/com/business/finder/user/web/UserController.java +++ b/src/main/java/com/business/finder/user/web/UserController.java @@ -8,6 +8,7 @@ import com.business.finder.user.application.port.UpdateUserUseCase.UpdateUserResponse; import com.business.finder.user.application.port.UploadUserProfilePictureUseCase; import com.business.finder.user.application.port.UploadUserProfilePictureUseCase.UploadUserProfilePictureCommand; +import com.business.finder.user.application.port.UploadUserProfilePictureUseCase.UploadUserProfilePictureResponse; import lombok.AllArgsConstructor; import lombok.Data; import org.springframework.http.ResponseEntity; @@ -58,8 +59,13 @@ public ResponseEntity updateUser(@Valid @RequestBody RestUpd } @PutMapping("/profile-picture") - public void uploadUserProfilePicture(@RequestParam MultipartFile file, @AuthenticationPrincipal UserEntityDetails userDetails) { - uploadUserProfilePictureUseCase.upload(new UploadUserProfilePictureCommand(file, userDetails.getCurrentUserId())); + public ResponseEntity uploadUserProfilePicture(@RequestParam MultipartFile file, @AuthenticationPrincipal UserEntityDetails userDetails) { + UploadUserProfilePictureResponse response = uploadUserProfilePictureUseCase.upload(new UploadUserProfilePictureCommand(file, userDetails.getCurrentUserId())); + if (response.isSuccess()) { + return ResponseEntity.ok(response); + } else { + return ResponseEntity.badRequest().body(response); + } } @Data diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 815a4d1..b9ac19b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -14,6 +14,7 @@ spring.servlet.multipart.max-file-size=700KB spring.servlet.multipart.max-request-size=700KB bf.user.profile.picture.folder=../user-profile-pictures +bf.user.profile.picture.allowed-extensions=png,jpg #security debug logs From 9893e20129799cd92940016f17f02b3d5b660a8f Mon Sep 17 00:00:00 2001 From: Karelin <> Date: Mon, 3 Jan 2022 01:02:28 +0100 Subject: [PATCH 3/6] Updated logs --- README.md | 2 ++ .../java/com/business/finder/ApplicationInitializer.java | 3 +++ .../application/QueryPartnershipProposalService.java | 9 +++++++-- .../finder/upload/application/LocalPictureUploader.java | 6 ++++-- .../finder/user/application/CreateUserService.java | 3 +++ .../finder/user/application/DeleteUserService.java | 8 +++++++- .../finder/user/application/UpdateUserService.java | 3 +++ .../application/UploadUserProfilePictureService.java | 3 +++ .../finder/user/application/port/UpdateUserUseCase.java | 2 ++ .../java/com/business/finder/user/domain/BfUser.java | 2 ++ 10 files changed, 36 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d23b7dc..33c1804 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ TODO: 11. Announcements to users. 12. Change Error enum to ErrorCode. It's more readable. 13. Compare Partnership and Investment entities. For example Language enum should be already added! +14. Limit for proposals: 50. Smart value should be included I think. +15. After updating proposals we also need to approve it in Admin panel. Let's just set "NEW" or "UPDATED" status in entities. Mobile app: diff --git a/src/main/java/com/business/finder/ApplicationInitializer.java b/src/main/java/com/business/finder/ApplicationInitializer.java index 2c294e4..a12224c 100644 --- a/src/main/java/com/business/finder/ApplicationInitializer.java +++ b/src/main/java/com/business/finder/ApplicationInitializer.java @@ -1,5 +1,6 @@ package com.business.finder; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; @@ -10,6 +11,7 @@ import java.nio.file.Paths; @Component +@Slf4j public class ApplicationInitializer implements ApplicationRunner { @Value("${bf.user.profile.picture.folder}") @@ -17,6 +19,7 @@ public class ApplicationInitializer implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { + log.info("Creating directories for local profile pictures..."); createDirectoriesForLocalProfilePictures(); } diff --git a/src/main/java/com/business/finder/partnership/application/QueryPartnershipProposalService.java b/src/main/java/com/business/finder/partnership/application/QueryPartnershipProposalService.java index 08bd725..758e320 100644 --- a/src/main/java/com/business/finder/partnership/application/QueryPartnershipProposalService.java +++ b/src/main/java/com/business/finder/partnership/application/QueryPartnershipProposalService.java @@ -6,6 +6,7 @@ import com.business.finder.partnership.db.PartnershipProposalRepository; import com.business.finder.partnership.domain.PartnershipProposal; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.transaction.Transactional; @@ -15,6 +16,7 @@ @Service @RequiredArgsConstructor +@Slf4j class QueryPartnershipProposalService implements QueryPartnershipProposalUseCase { private final PartnershipProposalRepository repository; @@ -27,7 +29,8 @@ public Response create(CreatePartnershipProposalCommand command) { if (errors.isEmpty()) { PartnershipProposal partnershipProposal = command.toPartnershipProposal(); - repository.save(partnershipProposal); + PartnershipProposal savedProposal = repository.save(partnershipProposal); + log.info("Created partnership proposal " + savedProposal.getUuid() + " by user " + command.getUserId()); return Response.OK; } else { return Response.errors(errors); @@ -45,6 +48,7 @@ public Response update(UpdatePartnershipProposalCommand command) { .map(partnershipProposal -> authorize(partnershipProposal, command.getUserId())) .map(partnershipProposal -> { updateFields(command, partnershipProposal); + log.info("Updated partnership proposal " + partnershipProposal.getUuid() + " by user " + command.getUserId()); return Response.OK; }) .orElseThrow(() -> new PartnershipProposalIsNotFoundException("Partnership proposal is not found during updating request. UUID: " + command.getPartnershipProposalUuid())); @@ -64,13 +68,14 @@ public Response remove(RemovePartnershipProposalCommand command) { PartnershipProposal partnershipProposal = repository.findByUuid(command.getPartnershipProposalUuid()) .map(proposal -> authorize(proposal, command.getCurrentUserId())) .orElseThrow(() -> new PartnershipProposalIsNotFoundException("Given partnership proposal not found. UUID: " + command.getPartnershipProposalUuid())); - + log.info("Removing partnership proposal " + partnershipProposal.getUuid() + " by user " + command.getCurrentUserId()); repository.delete(partnershipProposal); return Response.OK; } private PartnershipProposal authorize(PartnershipProposal partnershipProposal, Long userId) { if (!partnershipProposal.getBfUserId().equals(userId)) { + log.error("User " + userId + " tried to get access for not his proposal " + partnershipProposal.getUuid()); throw new NoAccessToPartnershipProposalException("Current user doesn't have permission to partnership proposal " + partnershipProposal.getUuid()); } return partnershipProposal; diff --git a/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java b/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java index c5917e1..a1e5d8f 100644 --- a/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java +++ b/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java @@ -1,15 +1,16 @@ package com.business.finder.upload.application; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @Component("localPictureUploader") +@Slf4j class LocalPictureUploader implements PictureUploaderStrategy { @Override @@ -17,8 +18,9 @@ public PictureUploaderResponse upload(MultipartFile file, String fileName, Strin Path root = Paths.get(path); try { Files.copy(file.getInputStream(), root.resolve(fileName + "." + StringUtils.getFilenameExtension(file.getOriginalFilename()))); + log.info("Local picture storage: uploaded file with name " + fileName); } catch (Exception e) { - e.printStackTrace(); + throw new RuntimeException("Exception during uploading picture for local storage. Stacktrace: " + e); } return PictureUploaderResponse.OK; } diff --git a/src/main/java/com/business/finder/user/application/CreateUserService.java b/src/main/java/com/business/finder/user/application/CreateUserService.java index 9675025..9b898a7 100644 --- a/src/main/java/com/business/finder/user/application/CreateUserService.java +++ b/src/main/java/com/business/finder/user/application/CreateUserService.java @@ -4,6 +4,7 @@ import com.business.finder.user.db.BfUserRepository; import com.business.finder.user.domain.BfUser; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -11,6 +12,7 @@ @Service @RequiredArgsConstructor +@Slf4j class CreateUserService implements CreateUserUseCase { private final BfUserRepository bfUserRepository; @@ -25,6 +27,7 @@ public CreateUserResponse createUser(CreateUserCommand command) { passwordEncoder.encode(command.getPassword()), command.getUserType()); bfUserRepository.save(bfUser); + log.info("Created new user: " + bfUser); return CreateUserResponse.OK; } } diff --git a/src/main/java/com/business/finder/user/application/DeleteUserService.java b/src/main/java/com/business/finder/user/application/DeleteUserService.java index 493ec03..699ebbf 100644 --- a/src/main/java/com/business/finder/user/application/DeleteUserService.java +++ b/src/main/java/com/business/finder/user/application/DeleteUserService.java @@ -7,6 +7,7 @@ import com.business.finder.user.db.BfUserRepository; import com.business.finder.user.domain.BfUser; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; @@ -15,6 +16,7 @@ @Service @AllArgsConstructor +@Slf4j class DeleteUserService implements DeleteUserUseCase { private final BfUserRepository bfUserRepository; @@ -27,8 +29,12 @@ public DeleteUserResponse deleteUser(String userEmail, UserDetails user) { Optional userForDelete = bfUserRepository.findByEmailIgnoreCase(user.getUsername()); if (userForDelete.isPresent()) { - if (user.getUsername().equalsIgnoreCase(userForDelete.get().getEmail())) { + final String username = userForDelete.get().getEmail(); + if (user.getUsername().equalsIgnoreCase(username)) { + log.info("Archive user: " + username); archiveUserService.archiveUser(new ArchiveUserCommand(user.getUsername())); + + log.info("Removing user from repository: " + username); bfUserRepository.delete(userForDelete.get()); // TODO. What about partnership-proposals? return DeleteUserResponse.OK; diff --git a/src/main/java/com/business/finder/user/application/UpdateUserService.java b/src/main/java/com/business/finder/user/application/UpdateUserService.java index 8e2a2bb..2c7066d 100644 --- a/src/main/java/com/business/finder/user/application/UpdateUserService.java +++ b/src/main/java/com/business/finder/user/application/UpdateUserService.java @@ -4,6 +4,7 @@ import com.business.finder.user.db.BfUserRepository; import com.business.finder.user.domain.BfUser; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @@ -11,6 +12,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class UpdateUserService implements UpdateUserUseCase { private final BfUserRepository repository; @@ -20,6 +22,7 @@ public class UpdateUserService implements UpdateUserUseCase { public UpdateUserResponse update(UpdateUserCommand command) { return repository.findByEmailIgnoreCase(command.getUserEmail()) .map(user -> { + log.info("Updating user: " + command.getUserEmail() + " by command: " + command); updateFields(command, user); return UpdateUserResponse.OK; }) diff --git a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java index 311763f..d86744d 100644 --- a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java +++ b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java @@ -9,6 +9,7 @@ import com.business.finder.user.domain.type.ProfilePictureStatus; import com.business.finder.user.domain.type.ProfilePictureStorage; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -19,6 +20,7 @@ @Service @RequiredArgsConstructor +@Slf4j public class UploadUserProfilePictureService implements UploadUserProfilePictureUseCase { @Value("${bf.user.profile.picture.folder}") @@ -49,6 +51,7 @@ public UploadUserProfilePictureResponse upload(UploadUserProfilePictureCommand c savedEntity.setStatus(ProfilePictureStatus.PICTURE_UPLOADED); } + log.info("Added profile picture for user: " + command.getUserId()); return UploadUserProfilePictureResponse.OK; } diff --git a/src/main/java/com/business/finder/user/application/port/UpdateUserUseCase.java b/src/main/java/com/business/finder/user/application/port/UpdateUserUseCase.java index 93ce45b..1927d74 100644 --- a/src/main/java/com/business/finder/user/application/port/UpdateUserUseCase.java +++ b/src/main/java/com/business/finder/user/application/port/UpdateUserUseCase.java @@ -1,5 +1,6 @@ package com.business.finder.user.application.port; +import lombok.ToString; import lombok.Value; import javax.validation.constraints.NotBlank; @@ -18,6 +19,7 @@ class UpdateUserResponse { } @Value + @ToString class UpdateUserCommand { String userDescription; @NotBlank String userEmail; diff --git a/src/main/java/com/business/finder/user/domain/BfUser.java b/src/main/java/com/business/finder/user/domain/BfUser.java index 47f8060..8186910 100644 --- a/src/main/java/com/business/finder/user/domain/BfUser.java +++ b/src/main/java/com/business/finder/user/domain/BfUser.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @@ -22,6 +23,7 @@ @Entity @EntityListeners(AuditingEntityListener.class) @NoArgsConstructor +@ToString(exclude = {"password"}) public class BfUser extends BaseEntity { @CreatedDate From fd63894db91a3ed8a285e87462ac5c4fa2e44ed4 Mon Sep 17 00:00:00 2001 From: Karelin <> Date: Tue, 4 Jan 2022 13:58:50 +0100 Subject: [PATCH 4/6] Upload and replace pictures --- .../com/business/finder/CustomGlobalExceptionHandler.java | 1 + .../finder/upload/application/ExternalServiceUploader.java | 2 +- .../finder/upload/application/LocalPictureUploader.java | 5 +++-- .../upload/application/LocalPictureUploaderService.java | 4 ++-- .../finder/upload/application/PictureUploaderStrategy.java | 2 +- .../upload/application/port/LocalPictureUploaderUseCase.java | 2 +- .../user/application/UploadUserProfilePictureService.java | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java b/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java index 724850f..4363e23 100644 --- a/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java +++ b/src/main/java/com/business/finder/CustomGlobalExceptionHandler.java @@ -19,6 +19,7 @@ @ControllerAdvice // TODO. Add trace-id here I think. For every RuntimeException. Easier to analyze later. + // TODO. Add also handle for Exception. Maybe? class CustomGlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) diff --git a/src/main/java/com/business/finder/upload/application/ExternalServiceUploader.java b/src/main/java/com/business/finder/upload/application/ExternalServiceUploader.java index e541d9f..6289378 100644 --- a/src/main/java/com/business/finder/upload/application/ExternalServiceUploader.java +++ b/src/main/java/com/business/finder/upload/application/ExternalServiceUploader.java @@ -8,7 +8,7 @@ class ExternalServiceUploader implements PictureUploaderStrategy { @Override - public PictureUploaderResponse upload(MultipartFile file, String fileName, String path) { + public PictureUploaderResponse uploadAndReplace(MultipartFile file, String fileName, String path) { throw new NotYetImplementedException("ExternalServiceUploader strategy is only template. It's not implemented at this moment."); } diff --git a/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java b/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java index a1e5d8f..98b8d1a 100644 --- a/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java +++ b/src/main/java/com/business/finder/upload/application/LocalPictureUploader.java @@ -8,16 +8,17 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; @Component("localPictureUploader") @Slf4j class LocalPictureUploader implements PictureUploaderStrategy { @Override - public PictureUploaderResponse upload(MultipartFile file, String fileName, String path) { + public PictureUploaderResponse uploadAndReplace(MultipartFile file, String fileName, String path) { Path root = Paths.get(path); try { - Files.copy(file.getInputStream(), root.resolve(fileName + "." + StringUtils.getFilenameExtension(file.getOriginalFilename()))); + Files.copy(file.getInputStream(), root.resolve(fileName + "." + StringUtils.getFilenameExtension(file.getOriginalFilename())), StandardCopyOption.REPLACE_EXISTING); log.info("Local picture storage: uploaded file with name " + fileName); } catch (Exception e) { throw new RuntimeException("Exception during uploading picture for local storage. Stacktrace: " + e); diff --git a/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java b/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java index cda42e1..2edf95a 100644 --- a/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java +++ b/src/main/java/com/business/finder/upload/application/LocalPictureUploaderService.java @@ -14,8 +14,8 @@ class LocalPictureUploaderService implements LocalPictureUploaderUseCase { private final PictureUploaderStrategy localPictureUploader; @Override - public LocalPictureUploadedResponse upload(MultipartFile file, String fileName, String path) { - PictureUploaderResponse response = localPictureUploader.upload(file, fileName, path); + public LocalPictureUploadedResponse uploadAndReplace(MultipartFile file, String fileName, String path) { + PictureUploaderResponse response = localPictureUploader.uploadAndReplace(file, fileName, path); if (response.getErrors().isEmpty()) { return LocalPictureUploadedResponse.OK; } else { diff --git a/src/main/java/com/business/finder/upload/application/PictureUploaderStrategy.java b/src/main/java/com/business/finder/upload/application/PictureUploaderStrategy.java index 343b689..bae6a47 100644 --- a/src/main/java/com/business/finder/upload/application/PictureUploaderStrategy.java +++ b/src/main/java/com/business/finder/upload/application/PictureUploaderStrategy.java @@ -3,5 +3,5 @@ import org.springframework.web.multipart.MultipartFile; public interface PictureUploaderStrategy { - PictureUploaderResponse upload(MultipartFile file, String fileName, String path); + PictureUploaderResponse uploadAndReplace(MultipartFile file, String fileName, String path); } diff --git a/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java b/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java index b58e075..ecf1511 100644 --- a/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java +++ b/src/main/java/com/business/finder/upload/application/port/LocalPictureUploaderUseCase.java @@ -8,7 +8,7 @@ public interface LocalPictureUploaderUseCase { - LocalPictureUploadedResponse upload(MultipartFile file, String fileName, String path); + LocalPictureUploadedResponse uploadAndReplace(MultipartFile file, String fileName, String path); @Value class LocalPictureUploadedResponse { diff --git a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java index d86744d..359188d 100644 --- a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java +++ b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java @@ -45,7 +45,7 @@ public UploadUserProfilePictureResponse upload(UploadUserProfilePictureCommand c BfProfilePicture entity = new BfProfilePicture(fileName, command.getUserId(), ProfilePictureStorage.LOCAL); BfProfilePicture savedEntity = profilePictureRepository.save(entity); - LocalPictureUploadedResponse response = localPictureUploaderUseCase.upload(command.getFile(), fileName, userProfilePictureFolder); + LocalPictureUploadedResponse response = localPictureUploaderUseCase.uploadAndReplace(command.getFile(), fileName, userProfilePictureFolder); if (response.isSuccess()) { savedEntity.setStatus(ProfilePictureStatus.PICTURE_UPLOADED); From 188a83339c180fb33d11ca4c240ae73f544ef9de Mon Sep 17 00:00:00 2001 From: Karelin <> Date: Tue, 4 Jan 2022 21:33:37 +0100 Subject: [PATCH 5/6] BfProfilePicture - update or replace. Correct database. --- README.md | 1 + .../finder/ApplicationInitializer.java | 1 + .../finder/security/UserEntityDetails.java | 4 ++++ .../UploadUserProfilePictureService.java | 24 +++++++++++++------ .../port/UploadUserProfilePictureUseCase.java | 1 + .../UploadUserProfilePictureValidator.java | 7 +++--- .../user/db/BfProfilePictureRepository.java | 3 +++ .../finder/user/domain/BfProfilePicture.java | 14 ++++++++++- .../domain/type/ProfilePictureExtension.java | 5 ++++ .../finder/user/web/UserController.java | 4 +++- 10 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/business/finder/user/domain/type/ProfilePictureExtension.java diff --git a/README.md b/README.md index 33c1804..760e25a 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ TODO: 13. Compare Partnership and Investment entities. For example Language enum should be already added! 14. Limit for proposals: 50. Smart value should be included I think. 15. After updating proposals we also need to approve it in Admin panel. Let's just set "NEW" or "UPDATED" status in entities. +16. Create BfUserDetails entity. Mobile app: diff --git a/src/main/java/com/business/finder/ApplicationInitializer.java b/src/main/java/com/business/finder/ApplicationInitializer.java index a12224c..50468a4 100644 --- a/src/main/java/com/business/finder/ApplicationInitializer.java +++ b/src/main/java/com/business/finder/ApplicationInitializer.java @@ -25,6 +25,7 @@ public void run(ApplicationArguments args) throws Exception { private void createDirectoriesForLocalProfilePictures() { try { + // TODO. We can also initialize default picture which will be copied from /resources folder. Files.createDirectories(Paths.get(profilePictureUploadPath)); } catch (IOException e) { throw new RuntimeException("Could not create upload folder! Error: " + e); diff --git a/src/main/java/com/business/finder/security/UserEntityDetails.java b/src/main/java/com/business/finder/security/UserEntityDetails.java index aa43299..a09dbfa 100644 --- a/src/main/java/com/business/finder/security/UserEntityDetails.java +++ b/src/main/java/com/business/finder/security/UserEntityDetails.java @@ -55,4 +55,8 @@ public boolean isEnabled() { public Long getCurrentUserId() { return entity.getId(); } + + public String getCurrentUserUuid() { + return entity.getUuid(); + } } diff --git a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java index 359188d..6697ce8 100644 --- a/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java +++ b/src/main/java/com/business/finder/user/application/UploadUserProfilePictureService.java @@ -6,6 +6,7 @@ import com.business.finder.user.application.validator.UploadUserProfilePictureValidator; import com.business.finder.user.db.BfProfilePictureRepository; import com.business.finder.user.domain.BfProfilePicture; +import com.business.finder.user.domain.type.ProfilePictureExtension; import com.business.finder.user.domain.type.ProfilePictureStatus; import com.business.finder.user.domain.type.ProfilePictureStorage; import lombok.RequiredArgsConstructor; @@ -33,8 +34,8 @@ public class UploadUserProfilePictureService implements UploadUserProfilePicture @Override @Transactional public UploadUserProfilePictureResponse upload(UploadUserProfilePictureCommand command) { - final String fileName = command.getUserId().toString(); - final String fileExtension = getFileExtensionFrom(command.getFile()); + final String fileName = command.getUserUuid(); + final ProfilePictureExtension fileExtension = getFileExtensionFrom(command.getFile()); List errors = validator.validate(fileExtension); @@ -42,20 +43,29 @@ public UploadUserProfilePictureResponse upload(UploadUserProfilePictureCommand c return UploadUserProfilePictureResponse.errors(errors); } - BfProfilePicture entity = new BfProfilePicture(fileName, command.getUserId(), ProfilePictureStorage.LOCAL); - BfProfilePicture savedEntity = profilePictureRepository.save(entity); + BfProfilePicture entity = getOrCreateBfProfilePicture(command.getUserId()); + entity.setFileName(fileName); + entity.setPictureStorage(ProfilePictureStorage.LOCAL); + entity.setExtension(fileExtension); LocalPictureUploadedResponse response = localPictureUploaderUseCase.uploadAndReplace(command.getFile(), fileName, userProfilePictureFolder); if (response.isSuccess()) { - savedEntity.setStatus(ProfilePictureStatus.PICTURE_UPLOADED); + entity.setStatus(ProfilePictureStatus.PICTURE_UPLOADED); } log.info("Added profile picture for user: " + command.getUserId()); return UploadUserProfilePictureResponse.OK; } - private String getFileExtensionFrom(MultipartFile file) { - return StringUtils.getFilenameExtension(file.getOriginalFilename()); + private ProfilePictureExtension getFileExtensionFrom(MultipartFile file) { + String fileExtension = StringUtils.getFilenameExtension(file.getOriginalFilename()); + return ProfilePictureExtension.valueOf(fileExtension.toUpperCase()); + } + + private BfProfilePicture getOrCreateBfProfilePicture(Long userId) { + return profilePictureRepository + .findByUserId(userId) + .orElseGet(() -> profilePictureRepository.save(new BfProfilePicture(userId))); } } diff --git a/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java b/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java index 675ef60..2e95237 100644 --- a/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java +++ b/src/main/java/com/business/finder/user/application/port/UploadUserProfilePictureUseCase.java @@ -28,6 +28,7 @@ public static UploadUserProfilePictureResponse errors(List errors) { class UploadUserProfilePictureCommand { @NotNull MultipartFile file; @NotNull Long userId; + @NotNull String userUuid; } enum ErrorCode { diff --git a/src/main/java/com/business/finder/user/application/validator/UploadUserProfilePictureValidator.java b/src/main/java/com/business/finder/user/application/validator/UploadUserProfilePictureValidator.java index a9b2a0c..516fcbd 100644 --- a/src/main/java/com/business/finder/user/application/validator/UploadUserProfilePictureValidator.java +++ b/src/main/java/com/business/finder/user/application/validator/UploadUserProfilePictureValidator.java @@ -1,6 +1,7 @@ package com.business.finder.user.application.validator; import com.business.finder.user.application.port.UploadUserProfilePictureUseCase.ErrorCode; +import com.business.finder.user.domain.type.ProfilePictureExtension; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -13,15 +14,15 @@ public class UploadUserProfilePictureValidator { @Value("${bf.user.profile.picture.allowed-extensions}") private List allowedProfilePictureExtensions; - public List validate(String pictureExtension) { + public List validate(ProfilePictureExtension pictureExtension) { List errors = new ArrayList<>(); boolean isAllowed = allowedProfilePictureExtensions .stream() - .anyMatch(allowedExtension -> allowedExtension.equals(pictureExtension)); + .map(allowedExtension -> ProfilePictureExtension.valueOf(allowedExtension.toUpperCase())) + .anyMatch(pictureExtension::equals); if (!isAllowed) { errors.add(ErrorCode.NOT_ALLOWED_EXTENSION); } return errors; } - } diff --git a/src/main/java/com/business/finder/user/db/BfProfilePictureRepository.java b/src/main/java/com/business/finder/user/db/BfProfilePictureRepository.java index 659baac..3952b7c 100644 --- a/src/main/java/com/business/finder/user/db/BfProfilePictureRepository.java +++ b/src/main/java/com/business/finder/user/db/BfProfilePictureRepository.java @@ -3,5 +3,8 @@ import com.business.finder.user.domain.BfProfilePicture; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface BfProfilePictureRepository extends JpaRepository { + Optional findByUserId(Long userId); } diff --git a/src/main/java/com/business/finder/user/domain/BfProfilePicture.java b/src/main/java/com/business/finder/user/domain/BfProfilePicture.java index 9db7093..0e5efa6 100644 --- a/src/main/java/com/business/finder/user/domain/BfProfilePicture.java +++ b/src/main/java/com/business/finder/user/domain/BfProfilePicture.java @@ -1,6 +1,7 @@ package com.business.finder.user.domain; import com.business.finder.jpa.BaseEntity; +import com.business.finder.user.domain.type.ProfilePictureExtension; import com.business.finder.user.domain.type.ProfilePictureStatus; import com.business.finder.user.domain.type.ProfilePictureStorage; import lombok.Getter; @@ -10,6 +11,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; +import javax.validation.constraints.NotNull; import java.time.LocalDateTime; @Table(name = "BF_PROFILE_PICTURE") @@ -31,14 +33,24 @@ public class BfProfilePicture extends BaseEntity { @Enumerated(EnumType.STRING) private ProfilePictureStorage pictureStorage; + @Enumerated(EnumType.STRING) private ProfilePictureStatus status; + @Enumerated(EnumType.STRING) + private ProfilePictureExtension extension; + + @NotNull private Long userId; - public BfProfilePicture(String fileName, Long userId, ProfilePictureStorage storage) { + public BfProfilePicture(String fileName, Long userId, ProfilePictureStorage storage, ProfilePictureExtension extension) { this.fileName = fileName; this.userId = userId; this.pictureStorage = storage; + this.extension = extension; this.status = ProfilePictureStatus.NEW; } + + public BfProfilePicture(Long userId) { + this.userId = userId; + } } diff --git a/src/main/java/com/business/finder/user/domain/type/ProfilePictureExtension.java b/src/main/java/com/business/finder/user/domain/type/ProfilePictureExtension.java new file mode 100644 index 0000000..28ef2d1 --- /dev/null +++ b/src/main/java/com/business/finder/user/domain/type/ProfilePictureExtension.java @@ -0,0 +1,5 @@ +package com.business.finder.user.domain.type; + +public enum ProfilePictureExtension { + PNG, JPG +} diff --git a/src/main/java/com/business/finder/user/web/UserController.java b/src/main/java/com/business/finder/user/web/UserController.java index 617b612..ef05b44 100644 --- a/src/main/java/com/business/finder/user/web/UserController.java +++ b/src/main/java/com/business/finder/user/web/UserController.java @@ -60,7 +60,9 @@ public ResponseEntity updateUser(@Valid @RequestBody RestUpd @PutMapping("/profile-picture") public ResponseEntity uploadUserProfilePicture(@RequestParam MultipartFile file, @AuthenticationPrincipal UserEntityDetails userDetails) { - UploadUserProfilePictureResponse response = uploadUserProfilePictureUseCase.upload(new UploadUserProfilePictureCommand(file, userDetails.getCurrentUserId())); + UploadUserProfilePictureResponse response = uploadUserProfilePictureUseCase.upload(new UploadUserProfilePictureCommand(file, + userDetails.getCurrentUserId(), + userDetails.getCurrentUserUuid())); if (response.isSuccess()) { return ResponseEntity.ok(response); } else { From 7bf1d24e344e57d7c5309e40974097d4c869f4e0 Mon Sep 17 00:00:00 2001 From: Karelin <> Date: Wed, 5 Jan 2022 13:32:08 +0100 Subject: [PATCH 6/6] Updated postman --- ... Finder-2021-12-28.postman_collection.json | 217 ----------- ...Finder-2022-01-05.postman_collection.json} | 349 +++++++++++++++--- .../finder/user/web/UserController.java | 5 + 3 files changed, 295 insertions(+), 276 deletions(-) delete mode 100644 postman/Business Finder-2021-12-28.postman_collection.json rename postman/{Business Finder-2022-01-01.postman_collection.json => Business Finder-2022-01-05.postman_collection.json} (51%) diff --git a/postman/Business Finder-2021-12-28.postman_collection.json b/postman/Business Finder-2021-12-28.postman_collection.json deleted file mode 100644 index 516c296..0000000 --- a/postman/Business Finder-2021-12-28.postman_collection.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "info": { - "_postman_id": "4e271a30-fe61-4837-8b01-d17ead263c48", - "name": "Business Finder", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "Partnership-Proposal-Controller", - "item": [ - { - "name": "Fetch pageable partnership-proposal", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"subject\": \"Software House. I am senior developer\",\n\t\"industry\": \"IT\",\n\t\"country\": \"USA\",\n\t\"knowledgeOfProposalCreator\": \"I am a senior developer. I know java, spring and reactive programming. Also, I am a good leader\",\n\t\"isTeamAvailable\": true,\n\t\"teamDescription\": \"I work with my one friend. He is senior front-end developer\",\n\t\"additionalDescription\": \"Feel free to join us! We have daily, we are paying money and % from our company!\"\n}" - }, - "url": { - "raw": "{{host}}/partnership-proposal/all", - "host": [ - "{{host}}" - ], - "path": [ - "partnership-proposal", - "all" - ] - } - }, - "response": [] - }, - { - "name": "Add partnership proposal", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"subject\": \"Software House. I am senior developer\",\n\t\"industry\": \"IT\",\n\t\"country\": \"USA\",\n\t\"knowledgeOfProposalCreator\": \"I am a senior developer. I know java, spring and reactive programming. Also, I am a good leader\",\n\t\"isTeamAvailable\": true,\n\t\"teamDescription\": \"I work with my one friend. He is senior front-end developer\",\n\t\"additionalDescription\": \"Feel free to join us! We have daily, we are paying money and % from our company!\"\n}" - }, - "url": { - "raw": "{{host}}/partnership-proposal", - "host": [ - "{{host}}" - ], - "path": [ - "partnership-proposal" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "User-Controller", - "item": [ - { - "name": "Delete current user", - "request": { - "method": "DELETE", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "", - "value": "", - "type": "text", - "disabled": true - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n\t\"password\": \"123123\"\n}" - }, - "url": { - "raw": "{{host}}/user?email=karelin", - "host": [ - "{{host}}" - ], - "path": [ - "user" - ], - "query": [ - { - "key": "email", - "value": "karelin" - } - ] - } - }, - "response": [] - }, - { - "name": "Change user description", - "request": { - "method": "PUT", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"profileDescription\": \"Hello, I am Sergey Karelin - Senior Java Developer. Feel free to contact with me!\"\n}" - }, - "url": { - "raw": "{{host}}/user", - "host": [ - "{{host}}" - ], - "path": [ - "user" - ] - } - }, - "response": [] - } - ] - }, - { - "name": "Register User", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n \"password\": \"123123\",\n \"userType\": \"PERSONAL\"\n}" - }, - "url": { - "raw": "{{host}}/register", - "host": [ - "{{host}}" - ], - "path": [ - "register" - ] - } - }, - "response": [] - }, - { - "name": "Login User", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n\t\"password\": \"123123\"\n}" - }, - "url": { - "raw": "{{host}}/login", - "host": [ - "{{host}}" - ], - "path": [ - "login" - ] - } - }, - "response": [] - }, - { - "name": "Logout user", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n\t\"password\": \"123123\"\n}" - }, - "url": { - "raw": "{{host}}/logout", - "host": [ - "{{host}}" - ], - "path": [ - "logout" - ] - } - }, - "response": [] - } - ] -} \ No newline at end of file diff --git a/postman/Business Finder-2022-01-01.postman_collection.json b/postman/Business Finder-2022-01-05.postman_collection.json similarity index 51% rename from postman/Business Finder-2022-01-01.postman_collection.json rename to postman/Business Finder-2022-01-05.postman_collection.json index 2c6fe65..4751067 100644 --- a/postman/Business Finder-2022-01-01.postman_collection.json +++ b/postman/Business Finder-2022-01-05.postman_collection.json @@ -1,20 +1,16 @@ { - "variables": [], "info": { + "_postman_id": "2c3c7e21-ca78-4d4d-8a88-e8a7c7d42a8a", "name": "Business Finder", - "_postman_id": "5029393f-23d7-0b68-450b-654f4de5b323", - "description": "", - "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "Partnership-Proposal-Controller", - "description": "", "item": [ { - "name": "Fetch pageable partnership-proposal", + "name": "Get partnership proposal", "request": { - "url": "{{host}}/partnership-proposal/all", "method": "GET", "header": [ { @@ -22,18 +18,95 @@ "value": "application/json" } ], + "url": { + "raw": "{{host}}/partnership-proposal/5f2b4806-9fa1-454c-ad6d-3374cc01e886", + "host": [ + "{{host}}" + ], + "path": [ + "partnership-proposal", + "5f2b4806-9fa1-454c-ad6d-3374cc01e886" + ] + } + }, + "response": [] + }, + { + "name": "Delete partnership proposal", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{host}}/partnership-proposal/5f2b4806-9fa1-454c-ad6d-3374cc01e886", + "host": [ + "{{host}}" + ], + "path": [ + "partnership-proposal", + "5f2b4806-9fa1-454c-ad6d-3374cc01e886" + ] + } + }, + "response": [] + }, + { + "name": "Update partnership proposal", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], "body": { "mode": "raw", "raw": "{\n\t\"subject\": \"Software House. I am senior developer\",\n\t\"industry\": \"IT\",\n\t\"country\": \"USA\",\n\t\"knowledgeOfProposalCreator\": \"I am a senior developer. I know java, spring and reactive programming. Also, I am a good leader\",\n\t\"isTeamAvailable\": true,\n\t\"teamDescription\": \"I work with my one friend. He is senior front-end developer\",\n\t\"additionalDescription\": \"Feel free to join us! We have daily, we are paying money and % from our company!\"\n}" }, - "description": "" + "url": { + "raw": "{{host}}/partnership-proposal/{uuid}", + "host": [ + "{{host}}" + ], + "path": [ + "partnership-proposal", + "{uuid}" + ] + } + }, + "response": [] + }, + { + "name": "Fetch pageable partnership-proposal Copy", + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{host}}/partnership-proposal/all", + "host": [ + "{{host}}" + ], + "path": [ + "partnership-proposal", + "all" + ] + } }, "response": [] }, { "name": "Add partnership proposal", "request": { - "url": "{{host}}/partnership-proposal", "method": "POST", "header": [ { @@ -45,7 +118,15 @@ "mode": "raw", "raw": "{\n\t\"subject\": \"Software House. I am senior developer\",\n\t\"industry\": \"IT\",\n\t\"country\": \"USA\",\n\t\"knowledgeOfProposalCreator\": \"I am a senior developer. I know java, spring and reactive programming. Also, I am a good leader\",\n\t\"isTeamAvailable\": true,\n\t\"teamDescription\": \"I work with my one friend. He is senior front-end developer\",\n\t\"additionalDescription\": \"Feel free to join us! We have daily, we are paying money and % from our company!\"\n}" }, - "description": "" + "url": { + "raw": "{{host}}/partnership-proposal", + "host": [ + "{{host}}" + ], + "path": [ + "partnership-proposal" + ] + } }, "response": [] } @@ -53,11 +134,27 @@ }, { "name": "User-Controller", - "description": "", "item": [ { "name": "Delete current user", "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "", + "value": "", + "type": "text", + "disabled": true + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n\t\"password\": \"123123\"\n}" + }, "url": { "raw": "{{host}}/user?email=karelin", "host": [ @@ -71,34 +168,40 @@ "key": "email", "value": "karelin" } - ], - "variable": [] - }, - "method": "DELETE", + ] + } + }, + "response": [] + }, + { + "name": "Change user description", + "request": { + "method": "PUT", "header": [ { "key": "Content-Type", "value": "application/json" - }, - { - "key": "", - "value": "", - "type": "text", - "disabled": true } ], "body": { "mode": "raw", - "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n\t\"password\": \"123123\"\n}" + "raw": "{\n\t\"profileDescription\": \"Hello, I am Sergey Karelin - Senior Java Developer. Feel free to contact with me!\"\n}" }, - "description": "" + "url": { + "raw": "{{host}}/user", + "host": [ + "{{host}}" + ], + "path": [ + "user" + ] + } }, "response": [] }, { - "name": "Change user description", + "name": "Update user profile picture", "request": { - "url": "{{host}}/user", "method": "PUT", "header": [ { @@ -107,10 +210,60 @@ } ], "body": { - "mode": "raw", - "raw": "{\n\t\"profileDescription\": \"Hello, I am Sergey Karelin - Senior Java Developer. Feel free to contact with me!\"\n}" + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "/home/user/Downloads/98191aa2-99dc-4f3b-8366-6475387783d5.png" + } + ] + }, + "url": { + "raw": "{{host}}/user/profile-picture/", + "host": [ + "{{host}}" + ], + "path": [ + "user", + "profile-picture", + "" + ] + } + }, + "response": [] + }, + { + "name": "TODO: remove user profile picture", + "request": { + "method": "DELETE", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "/home/user/Downloads/98191aa2-99dc-4f3b-8366-6475387783d5.png" + } + ] }, - "description": "" + "url": { + "raw": "{{host}}/user/profile-picture/", + "host": [ + "{{host}}" + ], + "path": [ + "user", + "profile-picture", + "" + ] + } }, "response": [] } @@ -118,43 +271,52 @@ }, { "name": "Investment-Proposal-Controller", - "description": "", "item": [ { "name": "Add investment proposal", "request": { - "url": "{{host}}/investment-proposal", "method": "POST", "header": [ { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"projectSubject\": \"Software House 3\",\n\t\"projectDescription\": \"Create smart house with maximum low price\",\n\t\"industry\": \"IT\",\n\t\"country\": \"USA\",\n\t\"city\":\"Washington\",\n\t\"minimumInvestmentValue\": 10000,\n\t\"projectBudget\": 500000,\n\t\"teamLanguage\": \"EN\",\n\t\"expectedPaybackPeriod\": 50\n}" }, - "description": "" + "url": { + "raw": "{{host}}/investment-proposal", + "host": [ + "{{host}}" + ], + "path": [ + "investment-proposal" + ] + } }, "response": [] }, { "name": "Get investment proposal", "request": { - "url": "{{host}}/investment-proposal/4ee0e0f6-bba6-4311-aa9a-20d5895a222b", "method": "GET", "header": [ { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" + "url": { + "raw": "{{host}}/investment-proposal/4ee0e0f6-bba6-4311-aa9a-20d5895a222b", + "host": [ + "{{host}}" + ], + "path": [ + "investment-proposal", + "4ee0e0f6-bba6-4311-aa9a-20d5895a222b" + ] }, "description": "Add investment uuid like a PathVariable;" }, @@ -163,39 +325,50 @@ { "name": "Get all investment proposals", "request": { - "url": "{{host}}/investment-proposal/all", "method": "GET", "header": [ { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], - "body": { - "mode": "raw", - "raw": "" - }, - "description": "" + "url": { + "raw": "{{host}}/investment-proposal/all", + "host": [ + "{{host}}" + ], + "path": [ + "investment-proposal", + "all" + ] + } }, "response": [] }, { "name": "Update investment proposal", "request": { - "url": "{{host}}/investment-proposal/41ad570c-3f93-436e-8e35-fb8ed0ff1065", "method": "PUT", "header": [ { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"projectSubject\": \"Software House after update\",\n\t\"projectDescription\": \"Create smart house with maximum low price\",\n\t\"industry\": \"IT\",\n\t\"country\": \"USA\",\n\t\"city\":\"Washington\",\n\t\"minimumInvestmentValue\": 10000,\n\t\"projectBudget\": 500000,\n\t\"teamLanguage\": \"EN\",\n\t\"expectedPaybackPeriod\": 50\n}" }, + "url": { + "raw": "{{host}}/investment-proposal/41ad570c-3f93-436e-8e35-fb8ed0ff1065", + "host": [ + "{{host}}" + ], + "path": [ + "investment-proposal", + "41ad570c-3f93-436e-8e35-fb8ed0ff1065" + ] + }, "description": "Add investment uuid like a PathVariable;" }, "response": [] @@ -203,19 +376,27 @@ { "name": "Deletel investment proposal", "request": { - "url": "{{host}}/investment-proposal/41ad570c-3f93-436e-8e35-fb8ed0ff1065", "method": "DELETE", "header": [ { "key": "Content-Type", - "value": "application/json", - "description": "" + "value": "application/json" } ], "body": { "mode": "raw", "raw": "" }, + "url": { + "raw": "{{host}}/investment-proposal/41ad570c-3f93-436e-8e35-fb8ed0ff1065", + "host": [ + "{{host}}" + ], + "path": [ + "investment-proposal", + "41ad570c-3f93-436e-8e35-fb8ed0ff1065" + ] + }, "description": "Add investment uuid like a PathVariable;" }, "response": [] @@ -225,7 +406,6 @@ { "name": "Register User", "request": { - "url": "{{host}}/register", "method": "POST", "header": [ { @@ -237,14 +417,21 @@ "mode": "raw", "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n \"password\": \"123123\",\n \"userType\": \"PERSONAL\"\n}" }, - "description": "" + "url": { + "raw": "{{host}}/register", + "host": [ + "{{host}}" + ], + "path": [ + "register" + ] + } }, "response": [] }, { "name": "Login User", "request": { - "url": "{{host}}/login", "method": "POST", "header": [ { @@ -256,14 +443,21 @@ "mode": "raw", "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n\t\"password\": \"123123\"\n}" }, - "description": "" + "url": { + "raw": "{{host}}/login", + "host": [ + "{{host}}" + ], + "path": [ + "login" + ] + } }, "response": [] }, { "name": "Logout user", "request": { - "url": "{{host}}/logout", "method": "POST", "header": [ { @@ -275,7 +469,44 @@ "mode": "raw", "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n\t\"password\": \"123123\"\n}" }, - "description": "" + "url": { + "raw": "{{host}}/logout", + "host": [ + "{{host}}" + ], + "path": [ + "logout" + ] + } + }, + "response": [] + }, + { + "name": "Version of application", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n\t\"email\": \"karelin@gmail.com\",\n\t\"password\": \"123123\"\n}" + }, + "url": { + "raw": "{{host}}/version", + "host": [ + "{{host}}" + ], + "path": [ + "version" + ] + } }, "response": [] } diff --git a/src/main/java/com/business/finder/user/web/UserController.java b/src/main/java/com/business/finder/user/web/UserController.java index ef05b44..c43e20d 100644 --- a/src/main/java/com/business/finder/user/web/UserController.java +++ b/src/main/java/com/business/finder/user/web/UserController.java @@ -70,6 +70,11 @@ public ResponseEntity uploadUserProfilePicture } } + @DeleteMapping("/profile-picture") + public void removeUserProfilePicture() { + // TODO. + } + @Data static class RestUpdateUserCommand { @Size(max = 3000)