Skip to content
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
@@ -1,5 +1,6 @@
package com.kubuski.urlshortener.controller;

import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -21,13 +22,13 @@ final class AuthenticationController {

@PostMapping("/register")
@ResponseStatus(HttpStatus.CREATED)
public AuthenticationResponse register(@RequestBody RegisterRequest request) {
public AuthenticationResponse register(@Valid @RequestBody RegisterRequest request) {
return authenticationService.register(request);
}

@PostMapping("/authenticate")
@ResponseStatus(HttpStatus.OK)
public AuthenticationResponse authenticate(@RequestBody AuthenticationRequest request) {
public AuthenticationResponse authenticate(@Valid @RequestBody AuthenticationRequest request) {
return authenticationService.authenticate(request);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public UrlResponse getOriginalUrl(@PathVariable String shortUrl) {
@PutMapping("/{shortUrl}")
@ResponseStatus(HttpStatus.OK)
public UrlResponse updateOriginalUrl(@PathVariable String shortUrl,
@RequestBody UrlRequest urlRequest) {
@Valid @RequestBody UrlRequest urlRequest) {
return urlService.updateOriginalUrl(shortUrl, urlRequest);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.kubuski.urlshortener.dto.UserRequest;
import com.kubuski.urlshortener.dto.UserResponse;
import com.kubuski.urlshortener.service.UserService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
Expand All @@ -24,7 +25,7 @@ public UserResponse getUserByEmail(@PathVariable String email) {

@PostMapping("/register")
@ResponseStatus(HttpStatus.CREATED)
public UserResponse registerUser(@RequestBody UserRequest userRequest) {
public UserResponse registerUser(@Valid @RequestBody UserRequest userRequest) {
return userService.registerUser(userRequest);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
package com.kubuski.urlshortener.dto;

public record AuthenticationRequest(String login, String password) {
import jakarta.validation.constraints.NotBlank;

public record AuthenticationRequest(
@NotBlank(message = "Login is required") String login,
@NotBlank(message = "Password is required") String password) {

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.kubuski.urlshortener.dto;

import com.kubuski.urlshortener.entity.Roles;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record RegisterRequest(String username, String password, String email, Roles role) {
public record RegisterRequest(
@NotBlank(message = "Username is required") String username,
@NotBlank(message = "Password is required") String password,
@NotBlank(message = "Email is required") @Email(message = "Email should be valid") String email,
@NotNull(message = "Role is required") Roles role) {

}
9 changes: 7 additions & 2 deletions src/main/java/com/kubuski/urlshortener/dto/UrlRequest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.kubuski.urlshortener.dto;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;

public record UrlRequest(
@NotBlank(message = "URL is required")
@Pattern(regexp = "^https?://.*", message = "URL must start with http:// or https://")
String url) {

public record UrlRequest(@NotEmpty String url) {
}
9 changes: 8 additions & 1 deletion src/main/java/com/kubuski/urlshortener/dto/UserRequest.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package com.kubuski.urlshortener.dto;

import com.kubuski.urlshortener.entity.Roles;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

public record UserRequest(String username, String password, String email, Roles role) {
public record UserRequest(
@NotBlank(message = "Username is required") String username,
@NotBlank(message = "Password is required") String password,
@NotBlank(message = "Email is required") @Email(message = "Email should be valid") String email,
@NotNull(message = "Role is required") Roles role) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.kubuski.urlshortener.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public abstract class BaseException extends RuntimeException {
protected BaseException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UrlNotFoundException extends RuntimeException {
public class UrlNotFoundException extends BaseException {
public UrlNotFoundException(final String message) {
super(message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.CONFLICT)
public class UserAlreadyExistsException extends RuntimeException {
public class UserAlreadyExistsException extends BaseException {
public UserAlreadyExistsException(String message) {
super(message);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.NOT_FOUND)
public class UserNotFoundException extends RuntimeException {
public class UserNotFoundException extends BaseException {
public UserNotFoundException(final String message) {
super(message);
}
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/com/kubuski/urlshortener/mapper/EntityMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.kubuski.urlshortener.mapper;

import com.kubuski.urlshortener.dto.UrlResponse;
import com.kubuski.urlshortener.dto.UserResponse;
import com.kubuski.urlshortener.entity.Url;
import com.kubuski.urlshortener.entity.User;

/**
* Utility class for mapping between entities and DTOs.
* Centralizes conversion logic to reduce code duplication.
*/
public final class EntityMapper {

private EntityMapper() {
// Utility class, prevent instantiation
}

/**
* Converts a User entity to UserResponse DTO.
*
* @param user the User entity
* @return UserResponse DTO
*/
public static UserResponse toUserResponse(User user) {
return new UserResponse(
user.getId(),
user.getUsername(),
user.getEmail(),
user.getRole()
);
}

/**
* Converts a Url entity to UrlResponse DTO.
*
* @param url the Url entity
* @return UrlResponse DTO
*/
public static UrlResponse toUrlResponse(Url url) {
return new UrlResponse(
url.getId(),
url.getOriginalUrl(),
url.getShortUrl(),
url.getCreatedAt(),
url.getUpdatedAt(),
url.getExpirationDate(),
url.getAccessCount()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
import java.util.Optional;

public interface UrlRepository extends JpaRepository<Url, Long> {
Optional<Url> findById(Long id);

Optional<Url> findByShortUrlAndDeletedFalse(String shortUrl);

List<Url> findAllByDeletedTrueAndUpdatedAtBefore(Instant cutoffDate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ public interface UserRepository extends JpaRepository<User, Long> {

@Query("SELECT u FROM User u WHERE u.email = :login OR u.username = :login")
Optional<User> findByEmailOrUsername(@Param("login") String login);

@Query("SELECT COUNT(u) > 0 FROM User u WHERE u.email = :email OR u.username = :username")
boolean existsByEmailOrUsername(@Param("email") String email, @Param("username") String username);
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ private void checkIfUserExists(String email, String username) {
}

private boolean userExists(String email, String username) {
return userRepository.findByEmailOrUsername(email).isPresent()
|| userRepository.findByEmailOrUsername(username).isPresent();
return userRepository.existsByEmailOrUsername(email, username);
}
}
15 changes: 5 additions & 10 deletions src/main/java/com/kubuski/urlshortener/service/UrlService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.kubuski.urlshortener.dto.UrlResponse;
import com.kubuski.urlshortener.entity.Url;
import com.kubuski.urlshortener.exception.UrlNotFoundException;
import com.kubuski.urlshortener.mapper.EntityMapper;
import com.kubuski.urlshortener.repository.UrlRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -22,23 +23,23 @@ public UrlResponse createShortUrl(UrlRequest urlRequest) {
Url url = buildUrl(urlRequest);
Url savedUrl = urlRepository.save(url);

return toUrlResponse(savedUrl);
return EntityMapper.toUrlResponse(savedUrl);
}

@Transactional
public UrlResponse getOriginalUrl(String shortUrl) {
Url url = findUrlByShortUrl(shortUrl);
incrementAccessCount(url);

return toUrlResponse(url);
return EntityMapper.toUrlResponse(url);
}

@Transactional
public UrlResponse updateOriginalUrl(String shortUrl, UrlRequest urlRequest) {
Url url = findUrlByShortUrl(shortUrl);
updateUrl(url, urlRequest);

return toUrlResponse(url);
return EntityMapper.toUrlResponse(url);
}

@Transactional
Expand All @@ -51,7 +52,7 @@ public void deleteUrl(String shortUrl) {
public UrlResponse getUrlStats(String shortUrl) {
Url url = findUrlByShortUrl(shortUrl);

return toUrlResponse(url);
return EntityMapper.toUrlResponse(url);
}

private Url buildUrl(UrlRequest urlRequest) {
Expand Down Expand Up @@ -80,10 +81,4 @@ private void markUrlAsDeleted(Url url) {
private String generateShortCode() {
return UUID.randomUUID().toString().substring(0, 8);
}

private UrlResponse toUrlResponse(Url url) {
return new UrlResponse(url.getId(), url.getOriginalUrl(), url.getShortUrl(),
url.getCreatedAt(), url.getUpdatedAt(), url.getExpirationDate(),
url.getAccessCount());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.kubuski.urlshortener.dto.UserResponse;
import com.kubuski.urlshortener.entity.User;
import com.kubuski.urlshortener.exception.UserNotFoundException;
import com.kubuski.urlshortener.mapper.EntityMapper;
import com.kubuski.urlshortener.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
Expand All @@ -22,23 +23,19 @@ public UserResponse findByEmail(String email) {
User user = userRepository.findByEmail(email).orElseThrow(
() -> new UserNotFoundException("User " + email + " not found in Database"));

return toUserResponse(user);
return EntityMapper.toUserResponse(user);
}

public UserResponse registerUser(UserRequest userRequest) {
User user = toUser(userRequest);
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRepository.save(user);

return toUserResponse(user);
return EntityMapper.toUserResponse(user);
}

private User toUser(UserRequest userRequest) {
return User.builder().username(userRequest.username()).password(userRequest.password())
.email(userRequest.email()).role(userRequest.role()).build();
}

private UserResponse toUserResponse(User user) {
return new UserResponse(user.getId(), user.getUsername(), user.getEmail(), user.getRole());
}
}
Loading