From c39659ebe59f5646ca1ecaab2f2ccdba0fb2ce4f Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:17:42 +0300 Subject: [PATCH 01/26] Added findByExistUniqueLink method to find a unique link. --- .../urlshortener/link/LinkService.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index 2e45d91..69921f1 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import java.util.List; + import java.util.Objects; import java.util.UUID; @@ -125,6 +126,24 @@ public void deleteById(UUID id) { //TODO: needs test linkRepository.deleteById(id); } + /** + * Searches for a unique existing link by a short link. + * If an active link is found for the specified short link, returns that link. + * + * @param shortLink A string representing the short link to be searched. + * @return The active link found for the specified short link. + * @throws NoLinkFoundByShortLinkException If no link was found by the short link. + * @throws NullLinkPropertyException If the found link does not have the ACTIVE status. + */ + public Link findByExistUniqueLink(String shortLink) { + Link existingLink = linkRepository.findByShortLink(shortLink).orElseThrow(NoLinkFoundByShortLinkException::new); + if (existingLink.getStatus() == LinkStatus.ACTIVE) { + return existingLink; + } else { + throw new NullLinkPropertyException(); + } + } + /** * Throws a DeletedLinkException if the link has been marked as deleted. * From 37bc7860e654b151ec4a5c135fd2c38d95d604e6 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:23:11 +0300 Subject: [PATCH 02/26] Added custom annotation UrlShortValidator with used to validate short URLs. --- .../urlshortener/link/UrlShortValidator.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidator.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidator.java b/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidator.java new file mode 100644 index 0000000..1ef347f --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidator.java @@ -0,0 +1,40 @@ +package com.linkurlshorter.urlshortener.link; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; +/** + * An annotation {@link UrlShortValidator} used to validate short URLs. + * Can be applied to fields, methods, or other annotations. + * Uses the {@link UrlShortValidatorImpl} implementation to perform the validation. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Constraint(validatedBy = UrlShortValidatorImpl.class) +public @interface UrlShortValidator { + + /** + * The message to be used to inform about the failed validation. + * Default: "". + * + * @return Error message. + */ + String message() default ""; + + /** + * Groups to which this constraint belongs. Default: empty array. + * + * @return Constraint groups. + */ + Class[] groups() default {}; + + /** + * Parameters that can be used to configure the constraint. + * Default: an empty array. + * + * @return The parameters of the constraint. + */ + Class[] payload() default {}; +} From 90ff83de5954ad37c7e80bd4c1a8b8f92b28e4a2 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:23:50 +0300 Subject: [PATCH 03/26] Added custom annotation UrlShortValidatorImpl with used to validate short URLs. --- .../link/UrlShortValidatorImpl.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidatorImpl.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidatorImpl.java b/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidatorImpl.java new file mode 100644 index 0000000..f8ca669 --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidatorImpl.java @@ -0,0 +1,46 @@ +package com.linkurlshorter.urlshortener.link; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +/** + * The UrlShortValidatorImpl class implements the {@link ConstraintValidator} interface for annotating an UrlShortValidator. + * Used to validate short links to ensure that they are unique and active. + * + * @author Vlas Pototskyi + */ +@RequiredArgsConstructor +public class UrlShortValidatorImpl implements ConstraintValidator { + private final LinkService linkService; + + /** + * Checks if the short link is unique and active. + * + * @param shortLink A string representing the short link to be validated. + * @param context The context to be validated. + * @return true if the short link is unique and active; false otherwise. + * @throws NullPointerException if the link is not found in the database. + */ + @Override + public boolean isValid(String shortLink, ConstraintValidatorContext context) { + Link link = linkService.findByShortLink(shortLink); + Objects.requireNonNull(link, "This link cannot be null!"); + + if (link.getStatus() == LinkStatus.INACTIVE) { + context.buildConstraintViolationWithTemplate("This link is inactive!") + .addConstraintViolation(); + return false; + } + + Link existLink = linkService.findByExistUniqueLink(shortLink); + if (existLink != null) { + context.buildConstraintViolationWithTemplate("This link already exists!") + .addConstraintViolation(); + return false; + } + return true; + } +} From 6e124e1cb60179e99d2207f2b2c2a09b05c2bfc1 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:25:24 +0300 Subject: [PATCH 04/26] The EndTimeLinkValidator annotation has been added to check the link end time. --- .../link/EndTimeLinkValidator.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidator.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidator.java b/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidator.java new file mode 100644 index 0000000..48843fa --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidator.java @@ -0,0 +1,44 @@ +package com.linkurlshorter.urlshortener.link; + + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +/** + * The {@link EndTimeLinkValidator} annotation is used to validate the link's end time. + * This annotation can be applied to fields, methods, or other annotations. + * It uses the UrlShortValidatorImpl implementation to perform the validation. + * + * @author Vlas Pototskyi + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Constraint(validatedBy = UrlShortValidatorImpl.class) +public @interface EndTimeLinkValidator { + + /** + * The message to be used to inform about the failed validation. + * Default: "Link time is not valid!". + * + * @return Error message. + */ + String message() default "Link time is not valid!"; + + /** + * Groups to which this constraint belongs. Default: empty array. + * + * @return Constraint groups. + */ + Class[] groups() default {}; + + /** + * Parameters that can be used to configure the constraint. + * Default: an empty array. + * + * @return The parameters of the constraint. + */ + Class[] payload() default {}; +} From b8ac5fbaff2de9f578262ce28e28e8fec36e0139 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:27:07 +0300 Subject: [PATCH 05/26] Added the EndTimeLinkValidatorImpl class to implement custom annotation. --- .../link/EndTimeLinkValidatorImpl.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidatorImpl.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidatorImpl.java b/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidatorImpl.java new file mode 100644 index 0000000..7170f00 --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidatorImpl.java @@ -0,0 +1,28 @@ +package com.linkurlshorter.urlshortener.link; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.time.LocalDateTime; + +/** + * The EndTimeLinkValidatorImpl class implements the {@link ConstraintValidator} interface + * for annotating an EndTimeLinkValidator. It is used to validate the LocalDateTime value to check + * that the time to which the link is valid is correct. + * + * @author Vlas Pototskyi + */ +public class EndTimeLinkValidatorImpl implements ConstraintValidator { + /** + * Checks if the time to which the reference is valid is greater than the current time. + * + * @param value LocalDateTime The value to be validated. + * @param context The context to validate. + * @return true if the time to which the reference is valid is greater than the current time; false otherwise. + */ + @Override + public boolean isValid(LocalDateTime value, ConstraintValidatorContext context) { + LocalDateTime currentTime = LocalDateTime.now(); + return value.isAfter(currentTime); + } +} From f3701511b1645f22cdf644811ac9c626b22f4106 Mon Sep 17 00:00:00 2001 From: ArtemPoliakov Date: Wed, 17 Apr 2024 12:19:46 +0300 Subject: [PATCH 06/26] added link controller modifying methods, also reformatted some files to match checkstyle --- .../urlshortener/link/LinkController.java | 3 ++ .../urlshortener/link/LinkInfoResponse.java | 9 ++++++ .../link/LinkInfoResponseMapper.java | 29 +++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponseMapper.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java index aecbf2e..90c12f2 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java @@ -18,6 +18,7 @@ import java.util.UUID; import java.util.stream.Collectors; + /** * Controller for Link-related operations such as create, delete, update and get info + statistics * @@ -36,6 +37,8 @@ public class LinkController { private final EntityManager entityManager; private final LinkInfoDtoMapper linkDtoMapper; + private final LinkInfoResponseMapper infoResponseMapper; + /** * Controller method for creating a new link. *

diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponse.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponse.java index 170c52a..92be514 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponse.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponse.java @@ -7,6 +7,15 @@ import java.util.List; +/** + * Represents a response containing information about a link. + * This class provides various properties related to a link, such as its ID, long link, + * short link, creation time, expiration time, usage statistics, and status. + * Instances of this class can be created using the provided builder pattern. + * + * @author Artem Poliakov + * @version 1.0 + */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponseMapper.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponseMapper.java new file mode 100644 index 0000000..7fbc493 --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponseMapper.java @@ -0,0 +1,29 @@ +package com.linkurlshorter.urlshortener.link; + +import org.springframework.stereotype.Component; + +/** + * Mapper class responsible for mapping a Link entity to a LinkInfoResponse object. + *

+ * This mapper class provides a method to map a Link entity along with an optional error message + *

+ * to a LinkInfoResponse object. + * + * @author Artem Poliakov + * @version 1.0 + */ +@Component +public class LinkInfoResponseMapper { + public LinkInfoResponse mapLinkToResponse(Link link, String error) { + return LinkInfoResponse.builder() + .id(link.getId()) + .longLink(link.getLongLink()) + .shortLink(link.getShortLink()) + .createdTime(link.getCreatedTime()) + .expirationTime(link.getExpirationTime()) + .usageStatistics(link.getStatistics()) + .status(link.getStatus()) + .error(error) + .build(); + } +} From 69b0fca4816ff7f2d675cac7954362b8441abf70 Mon Sep 17 00:00:00 2001 From: ArtemPoliakov Date: Wed, 17 Apr 2024 12:19:46 +0300 Subject: [PATCH 07/26] added link controller modifying methods, also reformatted some files to match checkstyle --- .../urlshortener/link/LinkController.java | 49 +------------------ 1 file changed, 1 insertion(+), 48 deletions(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java index 90c12f2..08e6770 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java @@ -13,10 +13,7 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; /** @@ -35,7 +32,7 @@ public class LinkController { private final LinkService linkService; private final UserService userService; private final EntityManager entityManager; - private final LinkInfoDtoMapper linkDtoMapper; + private final LinkInfoResponseMapper infoResponseMapper; private final LinkInfoResponseMapper infoResponseMapper; @@ -140,50 +137,6 @@ public ResponseEntity refreshLink(@RequestParam UUID id) throw new ForbiddenException(OPERATION_FORBIDDEN_MSG); } } - //TODO: for info and all-links-info exclude all results of status DELETED! - @GetMapping("/info") - public ResponseEntity getInfoByShortLink(@RequestParam String shortLink) { - Link link = linkService.findByShortLink(shortLink); - if (doesUserHaveRightsForLinkById(link.getId())) { - LinkInfoDto dto = linkDtoMapper.mapLinkToDto(link); - LinkInfoResponse response = new LinkInfoResponse(List.of(dto), "ok"); - return ResponseEntity.ok(response); - } else { - throw new ForbiddenException(OPERATION_FORBIDDEN_MSG); - } - } - - @GetMapping("/all-links-info") - public ResponseEntity getAllLinksForUser() { - try { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - User requesterUser = userService.findByEmail(authentication.getName()); - List linksDto = linkService - .findAllByUser(requesterUser) - .stream() - .map(linkDtoMapper::mapLinkToDto) - .sorted(Comparator.comparing(LinkInfoDto::getUsageStatistics).reversed()) - .collect(Collectors.toList()); - return ResponseEntity.ok(new LinkInfoResponse(linksDto, "ok")); - } catch (Exception e) { - throw new InternalServerLinkException(); - } - - } - - //TODO: exclude all results of non-Active (or only of DELETED?) - @GetMapping("/url-usage-top-for-user") - public ResponseEntity getLinksStatsForUser() { - try { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - User requesterUser = userService.findByEmail(authentication.getName()); - List stats = linkService.getLinkUsageStatsByUserId(requesterUser.getId()); - stats.sort(Comparator.comparing(LinkStatisticsDto::getUsageStatistics).reversed()); - return ResponseEntity.ok(new LinkStatisticsResponse(stats, "ok")); - } catch (Exception e) { - throw new InternalServerLinkException(); - } - } /** * Generates a new short link. From ce57b64985fb6b15c22474c05c2cace0040d0dd8 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Wed, 17 Apr 2024 18:49:54 +0300 Subject: [PATCH 08/26] Swapped the service link --- .../urlshortener/link/LinkService.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index 69921f1..9c98a94 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -125,6 +125,23 @@ public void deleteById(UUID id) { //TODO: needs test } linkRepository.deleteById(id); } + /** + * Searches for a unique existing link by a short link. + * If an active link is found for the specified short link, returns that link. + * + * @param shortLink A string representing the short link to be searched. + * @return The active link found for the specified short link. + * @throws NoLinkFoundByShortLinkException If no link was found by the short link. + * @throws NullLinkPropertyException If the found link does not have the ACTIVE status. + */ + public Link findByExistUniqueLink(String shortLink) { + Link existingLink = linkRepository.findByShortLink(shortLink).orElseThrow(NoLinkFoundByShortLinkException::new); + if (existingLink.getStatus() == LinkStatus.ACTIVE) { + return existingLink; + } else { + throw new NullLinkPropertyException(); + } + } /** * Searches for a unique existing link by a short link. From 5c7965eb362e64994397dcb370a734f19e71097b Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Wed, 17 Apr 2024 20:56:21 +0300 Subject: [PATCH 09/26] Small refactor code --- .../urlshortener/link/LinkService.java | 28 +++++-------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index 9c98a94..04c6355 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -39,6 +39,7 @@ public Link save(Link link) { * @throws NullLinkPropertyException If the 'link' parameter is null. * @throws DeletedLinkException If the link has been marked as deleted. */ + public Link update(Link link) { if (Objects.isNull(link)) { throw new NullLinkPropertyException(); @@ -89,19 +90,20 @@ public Link findByShortLink(String shortLink) { return link; } - public List findAllByUser(User user){ - if(Objects.isNull(user)){ + public List findAllByUser(User user) { + if (Objects.isNull(user)) { throw new NullLinkPropertyException(); } return linkRepository.findAllByUser(user); } - public List getLinkUsageStatsByUserId(UUID userId){ - if(Objects.isNull(userId)){ + public List getLinkUsageStatsByUserId(UUID userId) { + if (Objects.isNull(userId)) { throw new NullLinkPropertyException(); } return linkRepository.getLinkUsageStatsForUser(userId); } + /** * Marks a link entity as deleted by its short link. * @@ -125,23 +127,6 @@ public void deleteById(UUID id) { //TODO: needs test } linkRepository.deleteById(id); } - /** - * Searches for a unique existing link by a short link. - * If an active link is found for the specified short link, returns that link. - * - * @param shortLink A string representing the short link to be searched. - * @return The active link found for the specified short link. - * @throws NoLinkFoundByShortLinkException If no link was found by the short link. - * @throws NullLinkPropertyException If the found link does not have the ACTIVE status. - */ - public Link findByExistUniqueLink(String shortLink) { - Link existingLink = linkRepository.findByShortLink(shortLink).orElseThrow(NoLinkFoundByShortLinkException::new); - if (existingLink.getStatus() == LinkStatus.ACTIVE) { - return existingLink; - } else { - throw new NullLinkPropertyException(); - } - } /** * Searches for a unique existing link by a short link. @@ -173,3 +158,4 @@ private void throwIfDeleted(Link link) { } } } + From eef8a3b5202ed2974133d917c9797f5bf66c56af Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Wed, 17 Apr 2024 21:37:50 +0300 Subject: [PATCH 10/26] Small refactor code --- .../java/com/linkurlshorter/urlshortener/link/LinkService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index 04c6355..a2d20f2 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -5,7 +5,6 @@ import org.springframework.stereotype.Service; import java.util.List; - import java.util.Objects; import java.util.UUID; From 26f3c1824416628c2793ed014dd71c66f2d4491c Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Wed, 17 Apr 2024 22:10:46 +0300 Subject: [PATCH 11/26] test --- .../java/com/linkurlshorter/urlshortener/link/LinkService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index a2d20f2..20c4900 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -145,6 +145,7 @@ public Link findByExistUniqueLink(String shortLink) { } } + /** * Throws a DeletedLinkException if the link has been marked as deleted. * From 3947d0edb70fc45b63651b9efd541f10fe925afd Mon Sep 17 00:00:00 2001 From: ArtemPoliakov Date: Thu, 18 Apr 2024 11:08:00 +0300 Subject: [PATCH 12/26] Updated link repository and service, controller, added javadoc --- .../urlshortener/link/Link.java | 3 +- .../urlshortener/link/LinkController.java | 65 ++++++++++--------- .../urlshortener/link/LinkInfoResponse.java | 9 ++- .../urlshortener/link/LinkRepository.java | 9 +-- .../urlshortener/link/LinkService.java | 6 +- .../urlshortener/link/LinkStatisticsDto.java | 8 ++- .../link/LinkStatisticsResponse.java | 9 ++- .../urlshortener/user/User.java | 3 +- .../urlshortener/link/LinkServiceTest.java | 28 ++++---- 9 files changed, 81 insertions(+), 59 deletions(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/Link.java b/src/main/java/com/linkurlshorter/urlshortener/link/Link.java index 68226ac..c7aaad6 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/Link.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/Link.java @@ -50,6 +50,7 @@ public class Link { @Column(name = "statistics") private int statistics; @Column(name = "status") + @Builder.Default @Enumerated(EnumType.STRING) - private LinkStatus status; + private LinkStatus status = LinkStatus.ACTIVE; } diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java index aecbf2e..51f0acc 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java @@ -16,7 +16,6 @@ import java.util.Comparator; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; /** * Controller for Link-related operations such as create, delete, update and get info + statistics @@ -33,7 +32,6 @@ public class LinkController { private final LinkService linkService; private final UserService userService; - private final EntityManager entityManager; private final LinkInfoDtoMapper linkDtoMapper; /** @@ -57,7 +55,7 @@ public class LinkController { @PostMapping("/create") public ResponseEntity createLink(@RequestBody @Valid CreateLinkRequest createRequest) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - UUID userId = userService.findByEmail(authentication.getName()).getId(); + User user = userService.findByEmail(authentication.getName()); String newShortUrl = generateShortLink(); try { linkService.save( @@ -65,7 +63,7 @@ public ResponseEntity createLink(@RequestBody @Valid CreateL .longLink(createRequest.getLongLink()) .shortLink(newShortUrl) .expirationTime(LocalDateTime.now().plusDays(SHORT_LINK_LIFETIME_IN_DAYS)) - .user(entityManager.getReference(User.class, userId)) + .user(user) .build() //TODO: add validations (short link being unique etc) ); } catch (Exception e) { @@ -137,7 +135,13 @@ public ResponseEntity refreshLink(@RequestParam UUID id) throw new ForbiddenException(OPERATION_FORBIDDEN_MSG); } } - //TODO: for info and all-links-info exclude all results of status DELETED! + /** + * Retrieves information about a link using its short link. + * + * @param shortLink the short link of the link to retrieve information for + * @return a ResponseEntity containing the response object with information about the link + * @throws ForbiddenException if the authenticated user does not have rights to access the link + */ @GetMapping("/info") public ResponseEntity getInfoByShortLink(@RequestParam String shortLink) { Link link = linkService.findByShortLink(shortLink); @@ -149,37 +153,36 @@ public ResponseEntity getInfoByShortLink(@RequestParam String throw new ForbiddenException(OPERATION_FORBIDDEN_MSG); } } - + /** + * Retrieves information about all links associated with the authenticated user. + * + * @return a ResponseEntity containing the response object with information about all links for the user + */ @GetMapping("/all-links-info") public ResponseEntity getAllLinksForUser() { - try { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - User requesterUser = userService.findByEmail(authentication.getName()); - List linksDto = linkService - .findAllByUser(requesterUser) - .stream() - .map(linkDtoMapper::mapLinkToDto) - .sorted(Comparator.comparing(LinkInfoDto::getUsageStatistics).reversed()) - .collect(Collectors.toList()); - return ResponseEntity.ok(new LinkInfoResponse(linksDto, "ok")); - } catch (Exception e) { - throw new InternalServerLinkException(); - } - + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + UUID requesterUserId = userService.findByEmail(authentication.getName()).getId(); + List linksDto = linkService + .findAllByUserId(requesterUserId) + .stream() + .map(linkDtoMapper::mapLinkToDto) + .sorted(Comparator.comparing(LinkInfoDto::getUsageStatistics).reversed()) + .toList(); + return ResponseEntity.ok(new LinkInfoResponse(linksDto, "ok")); } - - //TODO: exclude all results of non-Active (or only of DELETED?) + /** + * Retrieves usage statistics for all links associated with the authenticated user. + * + * @return a ResponseEntity containing the response object with usage statistics for all links for the user, + * links are sorted in descending order + */ @GetMapping("/url-usage-top-for-user") public ResponseEntity getLinksStatsForUser() { - try { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - User requesterUser = userService.findByEmail(authentication.getName()); - List stats = linkService.getLinkUsageStatsByUserId(requesterUser.getId()); - stats.sort(Comparator.comparing(LinkStatisticsDto::getUsageStatistics).reversed()); - return ResponseEntity.ok(new LinkStatisticsResponse(stats, "ok")); - } catch (Exception e) { - throw new InternalServerLinkException(); - } + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + User requesterUser = userService.findByEmail(authentication.getName()); + List stats = linkService.getLinkUsageStatsByUserId(requesterUser.getId()); + stats.sort(Comparator.comparing(LinkStatisticsDto::getUsageStatistics).reversed()); + return ResponseEntity.ok(new LinkStatisticsResponse(stats, "ok")); } /** diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponse.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponse.java index 170c52a..933a926 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponse.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponse.java @@ -6,7 +6,14 @@ import lombok.NoArgsConstructor; import java.util.List; - +/** + * Data transfer object (DTO) for representing a response containing link information. + * This class encapsulates a list of {@link LinkInfoDto} objects representing link information, + * along with an optional error message. + * + * @author Artem Poliakov + * @version 1.0 + */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkRepository.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkRepository.java index 7ac2e54..aeeac7c 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkRepository.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkRepository.java @@ -3,13 +3,9 @@ import com.linkurlshorter.urlshortener.user.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.transaction.annotation.Transactional; -import java.sql.Timestamp; -import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -34,9 +30,10 @@ public interface LinkRepository extends JpaRepository { */ Optional findByShortLink(String shortLink); - List findAllByUser(User user); + @Query("SELECT l from Link l WHERE l.user.id = :userId AND l.status <> 'DELETED'") + List findAllByUserId(@Param(value = "userId") UUID userId); @Query("SELECT new com.linkurlshorter.urlshortener.link.LinkStatisticsDto(l.id, l.shortLink, l.statistics)" + - " FROM Link l WHERE l.user.id = :userId") + " FROM Link l WHERE l.user.id = :userId AND l.status <> 'DELETED'") List getLinkUsageStatsForUser(@Param(value = "userId") UUID userId); } diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index 2e45d91..44be52c 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -88,11 +88,11 @@ public Link findByShortLink(String shortLink) { return link; } - public List findAllByUser(User user){ - if(Objects.isNull(user)){ + public List findAllByUserId(UUID userId){ + if(Objects.isNull(userId)){ throw new NullLinkPropertyException(); } - return linkRepository.findAllByUser(user); + return linkRepository.findAllByUserId(userId); } public List getLinkUsageStatsByUserId(UUID userId){ diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkStatisticsDto.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkStatisticsDto.java index 6d9fbc8..bd0c914 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkStatisticsDto.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkStatisticsDto.java @@ -6,7 +6,13 @@ import lombok.NoArgsConstructor; import java.util.UUID; - +/** + * Data transfer object (DTO) for representing link statistics. + * This class encapsulates information about a link's ID, short link, and usage statistics. + * + * @author Artem Poliakov + * @version 1.0 + */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkStatisticsResponse.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkStatisticsResponse.java index a96dd69..5afe0a1 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkStatisticsResponse.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkStatisticsResponse.java @@ -6,7 +6,14 @@ import lombok.NoArgsConstructor; import java.util.List; - +/** + * Data transfer object (DTO) for representing a response containing statistics of links. + * This class encapsulates a list of {@link LinkStatisticsDto} objects representing link statistics, + * along with an optional error message. + * + * @author Artem Poliakov + * @version 1.0 + */ @Data @AllArgsConstructor @NoArgsConstructor diff --git a/src/main/java/com/linkurlshorter/urlshortener/user/User.java b/src/main/java/com/linkurlshorter/urlshortener/user/User.java index 4d8faa8..a4e20af 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/user/User.java +++ b/src/main/java/com/linkurlshorter/urlshortener/user/User.java @@ -37,7 +37,8 @@ public class User { private String password; @Column(name = "role") @Enumerated(EnumType.STRING) - private UserRole role; + @Builder.Default + private UserRole role = UserRole.USER; @EqualsAndHashCode.Exclude @OneToMany(mappedBy = "user", cascade = {CascadeType.MERGE, CascadeType.PERSIST}) private List links; diff --git a/src/test/java/com/linkurlshorter/urlshortener/link/LinkServiceTest.java b/src/test/java/com/linkurlshorter/urlshortener/link/LinkServiceTest.java index 1130c37..5fe16ea 100644 --- a/src/test/java/com/linkurlshorter/urlshortener/link/LinkServiceTest.java +++ b/src/test/java/com/linkurlshorter/urlshortener/link/LinkServiceTest.java @@ -2,12 +2,17 @@ import com.linkurlshorter.urlshortener.user.User; import com.linkurlshorter.urlshortener.user.UserRole; +import jakarta.persistence.EntityManager; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.context.SpringBootTest; import java.time.LocalDateTime; import java.util.*; @@ -189,36 +194,31 @@ void findByShortLinkNotFoundTest() { } /** - * Test case for the {@link LinkService#findAllByUser(User)} method. + * Test case for the {@link LinkService#findAllByUserId(UUID)} method. */ @Test - void findByAllByUserTest() { - User user = User.builder() - .id(UUID.fromString("84991c79-f6a9-4b7b-b1b4-0d66c0b92c81")) - .email("test1@gmail.com") - .password("password1") - .role(UserRole.USER) - .build(); - + void findByAllByUserIdTest() { + UUID userId = UUID.fromString("84991c79-f6a9-4b7b-b1b4-0d66c0b92c81"); + User user = User.builder().id(userId).build(); List userLinks = Arrays.asList( Link.builder().id(UUID.randomUUID()).user(user).build(), Link.builder().id(UUID.randomUUID()).user(user).build(), Link.builder().id(UUID.randomUUID()).user(user).build() ); - when(linkRepository.findAllByUser(user)).thenReturn(userLinks); - List foundLinks = linkService.findAllByUser(user); + when(linkRepository.findAllByUserId(userId)).thenReturn(userLinks); + List foundLinks = linkService.findAllByUserId(userId); assertThat(foundLinks).isNotNull().isEqualTo(userLinks); - verify(linkRepository, times(1)).findAllByUser(user); + verify(linkRepository, times(1)).findAllByUserId(userId); } /** - * Test case for the {@link LinkService#findAllByUser(User)} method when the provided user is null. + * Test case for the {@link LinkService#findAllByUserId(UUID)} method when the provided user is null. */ @Test void findAllByNullUserTest() { - assertThatThrownBy(() -> linkService.findAllByUser(null)) + assertThatThrownBy(() -> linkService.findAllByUserId(null)) .isInstanceOf(NullLinkPropertyException.class); } From e0368c1eec9009d7f01ac4c50c9fcb0f7263eb9f Mon Sep 17 00:00:00 2001 From: EgorSivenko Date: Thu, 18 Apr 2024 12:24:10 +0300 Subject: [PATCH 13/26] Updated a base URL path for link controller endpoints --- .../com/linkurlshorter/urlshortener/link/LinkController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java index 51f0acc..a915c86 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java @@ -25,7 +25,7 @@ */ @RestController @RequiredArgsConstructor -@RequestMapping("/link") +@RequestMapping("/api/V1/link") public class LinkController { private static final int SHORT_LINK_LIFETIME_IN_DAYS = 30; private static final String OPERATION_FORBIDDEN_MSG = "Operation forbidden!"; From 9c016b41c45bcff58f34418adf4fed61b1120cd2 Mon Sep 17 00:00:00 2001 From: EgorSivenko Date: Thu, 18 Apr 2024 12:26:35 +0300 Subject: [PATCH 14/26] Improved the security configuration for all existing endpoints --- .../urlshortener/security/SecurityConfig.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/security/SecurityConfig.java b/src/main/java/com/linkurlshorter/urlshortener/security/SecurityConfig.java index 46a86a6..3bdddbc 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/security/SecurityConfig.java +++ b/src/main/java/com/linkurlshorter/urlshortener/security/SecurityConfig.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -52,9 +53,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/V1/auth/**").permitAll() - .requestMatchers("/api/V1/user/**").authenticated() - .anyRequest().permitAll() + .requestMatchers(HttpMethod.POST, "/api/V1/auth/**").permitAll() + .requestMatchers(HttpMethod.POST, "/api/V1/user/**").authenticated() + .requestMatchers(HttpMethod.GET, "/*").permitAll() + .requestMatchers("/api/V1/link/**").authenticated() + .anyRequest().denyAll() ) .userDetailsService(customUserDetailsService) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) From bb5256cd4400a2f548827c089620a52272e0f7f4 Mon Sep 17 00:00:00 2001 From: EgorSivenko Date: Thu, 18 Apr 2024 13:37:33 +0300 Subject: [PATCH 15/26] Added validation of the existing email address when changing email --- .../urlshortener/user/UserController.java | 9 +++++++-- .../urlshortener/user/UserService.java | 14 ++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/user/UserController.java b/src/main/java/com/linkurlshorter/urlshortener/user/UserController.java index 160e558..022f288 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/user/UserController.java +++ b/src/main/java/com/linkurlshorter/urlshortener/user/UserController.java @@ -1,5 +1,6 @@ package com.linkurlshorter.urlshortener.user; +import com.linkurlshorter.urlshortener.auth.exception.EmailAlreadyTakenException; import com.linkurlshorter.urlshortener.jwt.JwtUtil; import com.linkurlshorter.urlshortener.security.CustomUserDetailsService; import io.swagger.v3.oas.annotations.Operation; @@ -76,9 +77,13 @@ public ResponseEntity changePassword(@RequestBody @Valid @Operation(summary = "Change user email") public ResponseEntity changeEmail(@RequestBody @Valid ChangeUserEmailRequest emailRequest) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String newEmail = emailRequest.getNewEmail(); + if (userService.existsByEmail(newEmail)) { + throw new EmailAlreadyTakenException(newEmail); + } int alteredCount = userService.updateByEmailDynamically( User.builder() - .email(emailRequest.getNewEmail()) + .email(newEmail) .build(), authentication.getName() ); @@ -86,7 +91,7 @@ public ResponseEntity changeEmail(@RequestBody @Valid Cha throw new NoSuchEmailFoundException(); } else { UserModifyingResponse response = new UserModifyingResponse("ok"); - String refreshedToken = getRefreshedToken(emailRequest.getNewEmail()); + String refreshedToken = getRefreshedToken(newEmail); return ResponseEntity .ok() diff --git a/src/main/java/com/linkurlshorter/urlshortener/user/UserService.java b/src/main/java/com/linkurlshorter/urlshortener/user/UserService.java index c71e5c5..2f1c30c 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/user/UserService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/user/UserService.java @@ -103,6 +103,20 @@ public User findByEmail(String email) { return userRepository.findByEmail(email).orElseThrow(NoUserFoundByEmailException::new); } + /** + * Checks if a user exists by the given email address. + * + * @param email the email address of the user to check existence for. + * @return {@code true} if a user with the specified email address exists, {@code false} otherwise. + * @throws NullEmailException if the provided email address is null. + */ + public boolean existsByEmail(String email) { + if (Objects.isNull(email)) { + throw new NullEmailException(); + } + return userRepository.findByEmail(email).isPresent(); + } + /** * Deletes a user entity by its ID. * From 3c179248b88ce0312cab8c87d55f23765cccf5d8 Mon Sep 17 00:00:00 2001 From: EgorSivenko Date: Thu, 18 Apr 2024 13:50:56 +0300 Subject: [PATCH 16/26] Returning jwtToken in response body after email change --- .../urlshortener/user/UserController.java | 10 +++------- .../urlshortener/user/UserModifyingResponse.java | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/user/UserController.java b/src/main/java/com/linkurlshorter/urlshortener/user/UserController.java index 022f288..16b1ca0 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/user/UserController.java +++ b/src/main/java/com/linkurlshorter/urlshortener/user/UserController.java @@ -60,7 +60,7 @@ public ResponseEntity changePassword(@RequestBody @Valid if (alteredCount <= 0) { throw new NoSuchEmailFoundException(); } else { - UserModifyingResponse response = new UserModifyingResponse("ok"); + UserModifyingResponse response = new UserModifyingResponse("ok", null); return ResponseEntity.ok(response); } } @@ -90,13 +90,9 @@ public ResponseEntity changeEmail(@RequestBody @Valid Cha if (alteredCount <= 0) { throw new NoSuchEmailFoundException(); } else { - UserModifyingResponse response = new UserModifyingResponse("ok"); String refreshedToken = getRefreshedToken(newEmail); - - return ResponseEntity - .ok() - .header("Authorization", "Bearer " + refreshedToken) - .body(response); + UserModifyingResponse response = new UserModifyingResponse("ok", refreshedToken); + return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/linkurlshorter/urlshortener/user/UserModifyingResponse.java b/src/main/java/com/linkurlshorter/urlshortener/user/UserModifyingResponse.java index bf57624..9d75c8a 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/user/UserModifyingResponse.java +++ b/src/main/java/com/linkurlshorter/urlshortener/user/UserModifyingResponse.java @@ -21,4 +21,5 @@ @Builder public class UserModifyingResponse { private String error; + private String jwtToken; } \ No newline at end of file From d7c61343dda8eb3b94d5965bffc53260ca76ba2b Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:17:42 +0300 Subject: [PATCH 17/26] Added findByExistUniqueLink method to find a unique link. --- .../urlshortener/link/LinkService.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index 44be52c..198978f 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -5,6 +5,7 @@ import org.springframework.stereotype.Service; import java.util.List; + import java.util.Objects; import java.util.UUID; @@ -125,6 +126,24 @@ public void deleteById(UUID id) { //TODO: needs test linkRepository.deleteById(id); } + /** + * Searches for a unique existing link by a short link. + * If an active link is found for the specified short link, returns that link. + * + * @param shortLink A string representing the short link to be searched. + * @return The active link found for the specified short link. + * @throws NoLinkFoundByShortLinkException If no link was found by the short link. + * @throws NullLinkPropertyException If the found link does not have the ACTIVE status. + */ + public Link findByExistUniqueLink(String shortLink) { + Link existingLink = linkRepository.findByShortLink(shortLink).orElseThrow(NoLinkFoundByShortLinkException::new); + if (existingLink.getStatus() == LinkStatus.ACTIVE) { + return existingLink; + } else { + throw new NullLinkPropertyException(); + } + } + /** * Throws a DeletedLinkException if the link has been marked as deleted. * From 6cb5f4f4cc2d8a8c7d48e7e037520874224a2e09 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:23:11 +0300 Subject: [PATCH 18/26] Added custom annotation UrlShortValidator with used to validate short URLs. --- .../urlshortener/link/UrlShortValidator.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidator.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidator.java b/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidator.java new file mode 100644 index 0000000..1ef347f --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidator.java @@ -0,0 +1,40 @@ +package com.linkurlshorter.urlshortener.link; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; +/** + * An annotation {@link UrlShortValidator} used to validate short URLs. + * Can be applied to fields, methods, or other annotations. + * Uses the {@link UrlShortValidatorImpl} implementation to perform the validation. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Constraint(validatedBy = UrlShortValidatorImpl.class) +public @interface UrlShortValidator { + + /** + * The message to be used to inform about the failed validation. + * Default: "". + * + * @return Error message. + */ + String message() default ""; + + /** + * Groups to which this constraint belongs. Default: empty array. + * + * @return Constraint groups. + */ + Class[] groups() default {}; + + /** + * Parameters that can be used to configure the constraint. + * Default: an empty array. + * + * @return The parameters of the constraint. + */ + Class[] payload() default {}; +} From c6d0e75b94f8e341fe905d49c0dd170c65d49138 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:23:50 +0300 Subject: [PATCH 19/26] Added custom annotation UrlShortValidatorImpl with used to validate short URLs. --- .../link/UrlShortValidatorImpl.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidatorImpl.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidatorImpl.java b/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidatorImpl.java new file mode 100644 index 0000000..f8ca669 --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/UrlShortValidatorImpl.java @@ -0,0 +1,46 @@ +package com.linkurlshorter.urlshortener.link; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; + +import java.util.Objects; + +/** + * The UrlShortValidatorImpl class implements the {@link ConstraintValidator} interface for annotating an UrlShortValidator. + * Used to validate short links to ensure that they are unique and active. + * + * @author Vlas Pototskyi + */ +@RequiredArgsConstructor +public class UrlShortValidatorImpl implements ConstraintValidator { + private final LinkService linkService; + + /** + * Checks if the short link is unique and active. + * + * @param shortLink A string representing the short link to be validated. + * @param context The context to be validated. + * @return true if the short link is unique and active; false otherwise. + * @throws NullPointerException if the link is not found in the database. + */ + @Override + public boolean isValid(String shortLink, ConstraintValidatorContext context) { + Link link = linkService.findByShortLink(shortLink); + Objects.requireNonNull(link, "This link cannot be null!"); + + if (link.getStatus() == LinkStatus.INACTIVE) { + context.buildConstraintViolationWithTemplate("This link is inactive!") + .addConstraintViolation(); + return false; + } + + Link existLink = linkService.findByExistUniqueLink(shortLink); + if (existLink != null) { + context.buildConstraintViolationWithTemplate("This link already exists!") + .addConstraintViolation(); + return false; + } + return true; + } +} From 7eef2361e9eac4dec5652b9b457c1aad065fae57 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:25:24 +0300 Subject: [PATCH 20/26] The EndTimeLinkValidator annotation has been added to check the link end time. --- .../link/EndTimeLinkValidator.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidator.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidator.java b/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidator.java new file mode 100644 index 0000000..48843fa --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidator.java @@ -0,0 +1,44 @@ +package com.linkurlshorter.urlshortener.link; + + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +/** + * The {@link EndTimeLinkValidator} annotation is used to validate the link's end time. + * This annotation can be applied to fields, methods, or other annotations. + * It uses the UrlShortValidatorImpl implementation to perform the validation. + * + * @author Vlas Pototskyi + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +@Constraint(validatedBy = UrlShortValidatorImpl.class) +public @interface EndTimeLinkValidator { + + /** + * The message to be used to inform about the failed validation. + * Default: "Link time is not valid!". + * + * @return Error message. + */ + String message() default "Link time is not valid!"; + + /** + * Groups to which this constraint belongs. Default: empty array. + * + * @return Constraint groups. + */ + Class[] groups() default {}; + + /** + * Parameters that can be used to configure the constraint. + * Default: an empty array. + * + * @return The parameters of the constraint. + */ + Class[] payload() default {}; +} From 9eecf97f8a4cf6ba6e4518e0b526ccaaf78c792b Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Tue, 16 Apr 2024 23:27:07 +0300 Subject: [PATCH 21/26] Added the EndTimeLinkValidatorImpl class to implement custom annotation. --- .../link/EndTimeLinkValidatorImpl.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidatorImpl.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidatorImpl.java b/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidatorImpl.java new file mode 100644 index 0000000..7170f00 --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/EndTimeLinkValidatorImpl.java @@ -0,0 +1,28 @@ +package com.linkurlshorter.urlshortener.link; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.time.LocalDateTime; + +/** + * The EndTimeLinkValidatorImpl class implements the {@link ConstraintValidator} interface + * for annotating an EndTimeLinkValidator. It is used to validate the LocalDateTime value to check + * that the time to which the link is valid is correct. + * + * @author Vlas Pototskyi + */ +public class EndTimeLinkValidatorImpl implements ConstraintValidator { + /** + * Checks if the time to which the reference is valid is greater than the current time. + * + * @param value LocalDateTime The value to be validated. + * @param context The context to validate. + * @return true if the time to which the reference is valid is greater than the current time; false otherwise. + */ + @Override + public boolean isValid(LocalDateTime value, ConstraintValidatorContext context) { + LocalDateTime currentTime = LocalDateTime.now(); + return value.isAfter(currentTime); + } +} From 0a278c60a0451ed77fb2370927510bc966584115 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Thu, 18 Apr 2024 17:46:01 +0300 Subject: [PATCH 22/26] test --- .../link/LinkInfoResponseMapper.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponseMapper.java diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponseMapper.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponseMapper.java new file mode 100644 index 0000000..7fbc493 --- /dev/null +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkInfoResponseMapper.java @@ -0,0 +1,29 @@ +package com.linkurlshorter.urlshortener.link; + +import org.springframework.stereotype.Component; + +/** + * Mapper class responsible for mapping a Link entity to a LinkInfoResponse object. + *

+ * This mapper class provides a method to map a Link entity along with an optional error message + *

+ * to a LinkInfoResponse object. + * + * @author Artem Poliakov + * @version 1.0 + */ +@Component +public class LinkInfoResponseMapper { + public LinkInfoResponse mapLinkToResponse(Link link, String error) { + return LinkInfoResponse.builder() + .id(link.getId()) + .longLink(link.getLongLink()) + .shortLink(link.getShortLink()) + .createdTime(link.getCreatedTime()) + .expirationTime(link.getExpirationTime()) + .usageStatistics(link.getStatistics()) + .status(link.getStatus()) + .error(error) + .build(); + } +} From ee5c414b154b2d34487ffe41d442c887b26a80d1 Mon Sep 17 00:00:00 2001 From: ArtemPoliakov Date: Wed, 17 Apr 2024 12:19:46 +0300 Subject: [PATCH 23/26] added link controller modifying methods, also reformatted some files to match checkstyle --- .../urlshortener/link/LinkController.java | 63 +++---------------- 1 file changed, 8 insertions(+), 55 deletions(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java index a915c86..08e6770 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkController.java @@ -13,10 +13,9 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; -import java.util.Comparator; -import java.util.List; import java.util.UUID; + /** * Controller for Link-related operations such as create, delete, update and get info + statistics * @@ -25,14 +24,17 @@ */ @RestController @RequiredArgsConstructor -@RequestMapping("/api/V1/link") +@RequestMapping("/link") public class LinkController { private static final int SHORT_LINK_LIFETIME_IN_DAYS = 30; private static final String OPERATION_FORBIDDEN_MSG = "Operation forbidden!"; private final LinkService linkService; private final UserService userService; - private final LinkInfoDtoMapper linkDtoMapper; + private final EntityManager entityManager; + private final LinkInfoResponseMapper infoResponseMapper; + + private final LinkInfoResponseMapper infoResponseMapper; /** * Controller method for creating a new link. @@ -55,7 +57,7 @@ public class LinkController { @PostMapping("/create") public ResponseEntity createLink(@RequestBody @Valid CreateLinkRequest createRequest) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - User user = userService.findByEmail(authentication.getName()); + UUID userId = userService.findByEmail(authentication.getName()).getId(); String newShortUrl = generateShortLink(); try { linkService.save( @@ -63,7 +65,7 @@ public ResponseEntity createLink(@RequestBody @Valid CreateL .longLink(createRequest.getLongLink()) .shortLink(newShortUrl) .expirationTime(LocalDateTime.now().plusDays(SHORT_LINK_LIFETIME_IN_DAYS)) - .user(user) + .user(entityManager.getReference(User.class, userId)) .build() //TODO: add validations (short link being unique etc) ); } catch (Exception e) { @@ -135,55 +137,6 @@ public ResponseEntity refreshLink(@RequestParam UUID id) throw new ForbiddenException(OPERATION_FORBIDDEN_MSG); } } - /** - * Retrieves information about a link using its short link. - * - * @param shortLink the short link of the link to retrieve information for - * @return a ResponseEntity containing the response object with information about the link - * @throws ForbiddenException if the authenticated user does not have rights to access the link - */ - @GetMapping("/info") - public ResponseEntity getInfoByShortLink(@RequestParam String shortLink) { - Link link = linkService.findByShortLink(shortLink); - if (doesUserHaveRightsForLinkById(link.getId())) { - LinkInfoDto dto = linkDtoMapper.mapLinkToDto(link); - LinkInfoResponse response = new LinkInfoResponse(List.of(dto), "ok"); - return ResponseEntity.ok(response); - } else { - throw new ForbiddenException(OPERATION_FORBIDDEN_MSG); - } - } - /** - * Retrieves information about all links associated with the authenticated user. - * - * @return a ResponseEntity containing the response object with information about all links for the user - */ - @GetMapping("/all-links-info") - public ResponseEntity getAllLinksForUser() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - UUID requesterUserId = userService.findByEmail(authentication.getName()).getId(); - List linksDto = linkService - .findAllByUserId(requesterUserId) - .stream() - .map(linkDtoMapper::mapLinkToDto) - .sorted(Comparator.comparing(LinkInfoDto::getUsageStatistics).reversed()) - .toList(); - return ResponseEntity.ok(new LinkInfoResponse(linksDto, "ok")); - } - /** - * Retrieves usage statistics for all links associated with the authenticated user. - * - * @return a ResponseEntity containing the response object with usage statistics for all links for the user, - * links are sorted in descending order - */ - @GetMapping("/url-usage-top-for-user") - public ResponseEntity getLinksStatsForUser() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - User requesterUser = userService.findByEmail(authentication.getName()); - List stats = linkService.getLinkUsageStatsByUserId(requesterUser.getId()); - stats.sort(Comparator.comparing(LinkStatisticsDto::getUsageStatistics).reversed()); - return ResponseEntity.ok(new LinkStatisticsResponse(stats, "ok")); - } /** * Generates a new short link. From 684f774e3280b0c6812ae1c152ee058a6c3c651d Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Wed, 17 Apr 2024 18:49:54 +0300 Subject: [PATCH 24/26] Swapped the service link --- .../urlshortener/link/LinkService.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index 198978f..fb956f4 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -125,6 +125,23 @@ public void deleteById(UUID id) { //TODO: needs test } linkRepository.deleteById(id); } + /** + * Searches for a unique existing link by a short link. + * If an active link is found for the specified short link, returns that link. + * + * @param shortLink A string representing the short link to be searched. + * @return The active link found for the specified short link. + * @throws NoLinkFoundByShortLinkException If no link was found by the short link. + * @throws NullLinkPropertyException If the found link does not have the ACTIVE status. + */ + public Link findByExistUniqueLink(String shortLink) { + Link existingLink = linkRepository.findByShortLink(shortLink).orElseThrow(NoLinkFoundByShortLinkException::new); + if (existingLink.getStatus() == LinkStatus.ACTIVE) { + return existingLink; + } else { + throw new NullLinkPropertyException(); + } + } /** * Searches for a unique existing link by a short link. From 063d146b7323985c3a304da32f847967a0cc4d26 Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Wed, 17 Apr 2024 21:37:50 +0300 Subject: [PATCH 25/26] Small refactor code --- .../java/com/linkurlshorter/urlshortener/link/LinkService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index fb956f4..9e3b389 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -5,7 +5,6 @@ import org.springframework.stereotype.Service; import java.util.List; - import java.util.Objects; import java.util.UUID; From b722f9aa3d4b1945fe467fd5d05b5cadd52bcdac Mon Sep 17 00:00:00 2001 From: Vlasosik <128188585+Vlasosik@users.noreply.github.com> Date: Wed, 17 Apr 2024 22:10:46 +0300 Subject: [PATCH 26/26] test --- .../java/com/linkurlshorter/urlshortener/link/LinkService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java index 9e3b389..d464b94 100644 --- a/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java +++ b/src/main/java/com/linkurlshorter/urlshortener/link/LinkService.java @@ -160,6 +160,7 @@ public Link findByExistUniqueLink(String shortLink) { } } + /** * Throws a DeletedLinkException if the link has been marked as deleted. *