Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Feat: only allow creators of urls to retrieve them and get stats #18

Merged
merged 4 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import com.shrtly.url.shortener.dtos.UrlResponseDTO;
import com.shrtly.url.shortener.models.Url;
import com.shrtly.url.shortener.models.UrlStat;
import com.shrtly.url.shortener.models.User;
import com.shrtly.url.shortener.repository.UrlStatsRepository;
import com.shrtly.url.shortener.services.UrlService;
import com.shrtly.url.shortener.services.UserService;
import com.shrtly.url.shortener.utils.StandardApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -14,14 +16,18 @@
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;

import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

Expand All @@ -31,10 +37,12 @@ public class UrlController {

private final UrlService urlService;
private final UrlStatsRepository urlStatsRepository;
private final UserService userService;

public UrlController(UrlService urlService, UrlStatsRepository urlStatsRepository) {
public UrlController(UrlService urlService, UrlStatsRepository urlStatsRepository, UserService userService) {
this.urlService = urlService;
this.urlStatsRepository = urlStatsRepository;
this.userService = userService;
}

@Operation(summary = "Index route for api", description = "Returns a greeting message")
Expand Down Expand Up @@ -74,6 +82,18 @@ public ResponseEntity<StandardApiResponse<UrlResponseDTO>> getUrl(@Parameter(des
if(url == null) {
return new ResponseEntity<>(new StandardApiResponse<>(false, "Url not found", null), HttpStatus.NOT_FOUND);
}
// Retrieve the currently logged-in user's email
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String currentUserEmail = (principal instanceof UserDetails) ? ((UserDetails) principal).getUsername() : principal.toString();

// retrieve the user from db
User foundUser = userService.findByEmail(currentUserEmail);

// if logged-in user didn't create the url, return error message
if (foundUser == null || !Objects.equals(foundUser.getUserId(), url.getUserId())) {
throw new AccessDeniedException("You don't have necessary permissions");
}

return new ResponseEntity<>(new StandardApiResponse<>(true, "Url found", url), HttpStatus.OK);
}

Expand Down Expand Up @@ -108,6 +128,21 @@ public ResponseEntity<StandardApiResponse<Iterable<UrlResponseDTO>>> getUrls() {

@GetMapping("/urls/{id}/statistics")
public ResponseEntity<StandardApiResponse<?>> getStatistics(@PathVariable Integer id) {
// retrieve the url by its id
Url url = urlService.getUrlById(id);

// Retrieve the currently logged-in user's email
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String currentUserEmail = (principal instanceof UserDetails) ? ((UserDetails) principal).getUsername() : principal.toString();

// retrieve the user from db
User foundUser = userService.findByEmail(currentUserEmail);

// if logged-in user didn't create the url, return error message
if (foundUser == null || !Objects.equals(foundUser.getUserId(), url.getUser().getUserId())) {
throw new AccessDeniedException("You don't have necessary permissions");
}

Iterable<UrlStat> urlStat = urlService.getUrlStats(id);
if(!urlStat.iterator().hasNext()) {
return new ResponseEntity<>(new StandardApiResponse<>(false, "Url has no stats", null), HttpStatus.NOT_FOUND);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.jsonwebtoken.JwtException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
Expand Down Expand Up @@ -46,6 +47,12 @@ public ResponseEntity<StandardApiResponse> handleUrlNotFoundException(UrlNotFoun
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
}

@ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<StandardApiResponse<?>> handleAccessDeniedException(AccessDeniedException ex) {
StandardApiResponse<?> response = new StandardApiResponse<>(false, ex.getMessage(), null);
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(response);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<StandardApiResponse> handleGenericException(Exception ex) {
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public UrlResponseDTO createUrl(String originalUrl) {
return new UrlResponseDTO(url.getId(), url.getUrlId(), url.getOriginalUrl(), url.getShortenedUrl(), url.getUser().getUserId());
}

public Url getUrlById(Integer id) {
Url url = urlRepository.findById(id).orElseThrow(() -> new UrlNotFoundException("URL not found for ID: " + id));
return url;
}

public String deleteUrl(Integer id) {
Url url = urlRepository.findById(id).orElse(null);
urlRepository.deleteById(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ public User findById(Integer id) {
return userRepository.findById(id).orElse(null);
}

public User findByEmail(String email) {
return userRepository.findByEmail(email).orElse(null);
}

public User updateUser(User user) {
return userRepository.save(user);
}
Expand Down