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
35 changes: 33 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
Expand All @@ -45,14 +52,38 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package net.hackyourfuture.tickettrackingsystem.controller;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import net.hackyourfuture.tickettrackingsystem.dto.request.CreateUserRequest;
import net.hackyourfuture.tickettrackingsystem.dto.request.UpdateUserRequest;
import net.hackyourfuture.tickettrackingsystem.dto.response.UserResponse;
import net.hackyourfuture.tickettrackingsystem.model.User;
import net.hackyourfuture.tickettrackingsystem.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
@RequiredArgsConstructor
public class UserController {

private final UserService userService;

private UserResponse toUserResponse(User user) {
return new UserResponse(
user.id(),
user.name(),
user.email()
);
}
@GetMapping
public List<UserResponse> getAllUsers() {
return userService.getAllUsers()
.stream()
.map(this::toUserResponse).
toList();
}

@GetMapping("/{id}")
public UserResponse getUserById(@PathVariable Long id) {
return toUserResponse(
userService.getUserById(id)
);
}

@PostMapping
public ResponseEntity<UserResponse> createUser( @Valid @RequestBody CreateUserRequest request) {
User user = userService.createUser(request);

return ResponseEntity.status(HttpStatus.CREATED)
.body(toUserResponse(user));
}

@PutMapping("/{id}")
public ResponseEntity<UserResponse> updateUser(@PathVariable Long id, @Valid @RequestBody UpdateUserRequest request) {
User user = userService.updateUser(id, request);

return ResponseEntity.ok(toUserResponse(user));
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);

return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.hackyourfuture.tickettrackingsystem.dto.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record CreateUserRequest(
@NotBlank
@Size(min = 3)
String name,
@NotBlank
@Email
String email) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package net.hackyourfuture.tickettrackingsystem.dto.request;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record UpdateUserRequest(

@NotBlank
@Size(min = 3)
String name,

@NotBlank
@Email
String email

) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.hackyourfuture.tickettrackingsystem.dto.response;

public record UserResponse(
Long id,
String name,
String email
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.hackyourfuture.tickettrackingsystem.exception;

public class DuplicateEmailException extends RuntimeException {

public DuplicateEmailException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.hackyourfuture.tickettrackingsystem.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<Map<String, String>> handleResourceNotFound(
ResourceNotFoundException ex) {

return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(Map.of("message", ex.getMessage()));
}

@ExceptionHandler(DuplicateEmailException.class)
public ResponseEntity<Map<String, String>> handleDuplicateEmail(
DuplicateEmailException ex) {

return ResponseEntity.status(HttpStatus.CONFLICT)
.body(Map.of("message", ex.getMessage()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.hackyourfuture.tickettrackingsystem.exception;

public class ResourceNotFoundException extends RuntimeException {

public ResourceNotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package net.hackyourfuture.tickettrackingsystem.model;

public record User(Long id, String name, String email) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package net.hackyourfuture.tickettrackingsystem.repository;

import lombok.RequiredArgsConstructor;
import net.hackyourfuture.tickettrackingsystem.model.User;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
@RequiredArgsConstructor
public class UserRepository {

private final JdbcTemplate jdbcTemplate;

private final RowMapper<User> userRowMapper = (rs, rowNum) ->
new User(
rs.getLong("id"),
rs.getString("name"),
rs.getString("email")
);

public List<User> findAll() {
String sql = "SELECT id, name, email FROM users";
return jdbcTemplate.query(sql, userRowMapper);
}

public Optional<User> findById(Long id) {
String sql = "SELECT id, name, email FROM users WHERE id = ?";

try {
User user = jdbcTemplate.queryForObject(sql, userRowMapper, id);
return Optional.of(user);
} catch (EmptyResultDataAccessException e) {
return Optional.empty();
}
}

public Optional<User> findByEmail(String email) {
String sql = "SELECT id, name, email FROM users WHERE email = ?";

try {
User user = jdbcTemplate.queryForObject(sql, userRowMapper, email);
return Optional.of(user);
} catch (EmptyResultDataAccessException e) {
return Optional.empty();
}
}

public User create(User user) {
String sql = """
INSERT INTO users (name, email)
VALUES (?, ?)
RETURNING id
""";

Long id = jdbcTemplate.queryForObject(
sql,
Long.class,
user.name(),
user.email()
);

return new User(id, user.name(), user.email());
}

public void update(User user) {
String sql = """
UPDATE users
SET name = ?, email = ?
WHERE id = ?
""";

jdbcTemplate.update(
sql,
user.name(),
user.email(),
user.id()
);
}

public boolean deleteById(Long id) {
String sql = "DELETE FROM users WHERE id = ?";

int rowsAffected = jdbcTemplate.update(sql, id);

return rowsAffected > 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package net.hackyourfuture.tickettrackingsystem.service;


import lombok.RequiredArgsConstructor;
import net.hackyourfuture.tickettrackingsystem.dto.request.CreateUserRequest;
import net.hackyourfuture.tickettrackingsystem.dto.request.UpdateUserRequest;
import net.hackyourfuture.tickettrackingsystem.exception.DuplicateEmailException;
import net.hackyourfuture.tickettrackingsystem.model.User;
import net.hackyourfuture.tickettrackingsystem.repository.UserRepository;
import org.springframework.stereotype.Service;
import net.hackyourfuture.tickettrackingsystem.exception.ResourceNotFoundException;

import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;

public User createUser(CreateUserRequest request) {
if (userRepository.findByEmail(request.email()).isPresent()) {
throw new DuplicateEmailException(
"Email " + request.email() + " already exists");
}
return userRepository.create(new User(
null,
request.name(),
request.email()
));
}

public List<User> getAllUsers() {
return userRepository.findAll();
}

public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() ->
new ResourceNotFoundException(
"User with id " + id + " not found"));
}

public void deleteUser(Long id) {
boolean deleted = userRepository.deleteById(id);

if (!deleted) {
throw new ResourceNotFoundException(
"User with id " + id + " not found");
}
}

public User updateUser(Long id, UpdateUserRequest request) {

User existingUser = userRepository.findById(id)
.orElseThrow(() ->
new ResourceNotFoundException(
"User with id " + id + " not found"));

Optional<User> userWithSameEmail =
userRepository.findByEmail(request.email());

if (userWithSameEmail.isPresent()
&& !userWithSameEmail.get().id().equals(id)) {
throw new DuplicateEmailException(
"Email " + request.email() + " already exists");
}

User updatedUser = new User(
existingUser.id(),
request.name(),
request.email()
);

userRepository.update(updatedUser);

return updatedUser;
}
}
Loading
Loading