feat: implement complete CRUD for Users API
- Add UsersService with create, read (getById, getByUsername, getByEmail, getAll), update (using mapper.partialUpdate), and delete methods - Add UsersController with endpoints for all CRUD operations - Add UsersRepository with custom queries for username and email - Add UserDTO and NewUserDTO data transfer objects - Add UsersMapper for entity-DTO conversions - Add email domain validation (hittelco.com, accesscommunications.com) - Add email uniqueness validation - Create Postman collection for API testing with sample data - Update Users model with timestamps and builder patternmaster
parent
06ba25eabd
commit
348f9faa7d
|
|
@ -30,10 +30,12 @@ dependencies {
|
|||
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
|
||||
implementation 'org.mapstruct:mapstruct:1.6.3'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.mapstruct:mapstruct-processor:1.6.3'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-data-jdbc-test'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-validation-test'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
{
|
||||
"info": {
|
||||
"name": "Users API - Server Manager",
|
||||
"description": "Collection de testes para a API de Usuários",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "Users",
|
||||
"item": [
|
||||
{
|
||||
"name": "Create User",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"firstName\": \"João\",\n \"lastName\": \"Silva\",\n \"email\": \"joao.silva@hittelco.com\",\n \"password\": \"senha123\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create User - Test 2",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"firstName\": \"Maria\",\n \"lastName\": \"Santos\",\n \"email\": \"maria.santos@accesscommunications.com\",\n \"password\": \"senha456\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Create User - Invalid Domain",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"firstName\": \"Carlos\",\n \"lastName\": \"Oliveira\",\n \"email\": \"carlos@gmail.com\",\n \"password\": \"senha789\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get All Users",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get User by ID",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users/{{user_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users",
|
||||
"{{user_id}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get User by Username",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users/username/joao.silva@hittelco.com",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users",
|
||||
"username",
|
||||
"joao.silva@hittelco.com"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Get User by Email",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users/email/maria.santos@accesscommunications.com",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users",
|
||||
"email",
|
||||
"maria.santos@accesscommunications.com"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Update User",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"firstName\": \"João Paulo\",\n \"lastName\": \"Silva Santos\",\n \"email\": \"joao.paulo@hittelco.com\",\n \"password\": \"novaSenha123\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users/{{user_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users",
|
||||
"{{user_id}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Delete User",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/users/{{user_id}}",
|
||||
"host": [
|
||||
"{{base_url}}"
|
||||
],
|
||||
"path": [
|
||||
"api",
|
||||
"users",
|
||||
"{{user_id}}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "base_url",
|
||||
"value": "http://localhost:8080",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "user_id",
|
||||
"value": "",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -17,4 +17,4 @@ public class BackendApplication {
|
|||
public String home() {
|
||||
return "Hello, World!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package com.hitcommunications.servermanager.controllers;
|
||||
|
||||
import com.hitcommunications.servermanager.model.dtos.NewUserDTO;
|
||||
import com.hitcommunications.servermanager.model.dtos.UserDTO;
|
||||
import com.hitcommunications.servermanager.services.UsersService;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/users")
|
||||
@RequiredArgsConstructor
|
||||
public class UsersController {
|
||||
|
||||
private final UsersService usersService;
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<UserDTO> create(@RequestBody @Valid NewUserDTO crateDTO) throws IllegalAccessException {
|
||||
return ResponseEntity.ok().body(usersService.create(crateDTO));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<UserDTO> getById(@PathVariable UUID id) {
|
||||
return ResponseEntity.ok().body(usersService.getById(id));
|
||||
}
|
||||
|
||||
@GetMapping("/username/{username}")
|
||||
public ResponseEntity<UserDTO> getByUsername(@PathVariable String username) {
|
||||
return ResponseEntity.ok().body(usersService.getByUsername(username));
|
||||
}
|
||||
|
||||
@GetMapping("/email/{email}")
|
||||
public ResponseEntity<UserDTO> getByEmail(@PathVariable String email) {
|
||||
return ResponseEntity.ok().body(usersService.getByEmail(email));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<UserDTO>> getAll() {
|
||||
return ResponseEntity.ok().body(usersService.getAll());
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<UserDTO> update(@PathVariable UUID id, @RequestBody @Valid NewUserDTO updateDTO) throws IllegalAccessException {
|
||||
return ResponseEntity.ok().body(usersService.update(id, updateDTO));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> delete(@PathVariable UUID id) {
|
||||
usersService.delete(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.hitcommunications.servermanager.mappers;
|
||||
|
||||
import com.hitcommunications.servermanager.model.Users;
|
||||
import com.hitcommunications.servermanager.model.dtos.NewUserDTO;
|
||||
import com.hitcommunications.servermanager.model.dtos.UserDTO;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.NullValuePropertyMappingStrategy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
public interface UsersMapper {
|
||||
Users toEntity(NewUserDTO createDTO);
|
||||
UserDTO toDTO(Users entity);
|
||||
List<Users> toDTO(List<Users> entities);
|
||||
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
Users partialUpdate(NewUserDTO updateDTO, @MappingTarget Users entity);
|
||||
}
|
||||
|
|
@ -58,6 +58,6 @@ public class Users {
|
|||
String firstPart = this.firstName != null && this.firstName.length() >= 3 ? this.firstName.substring(0, 3) : (this.firstName != null ? this.firstName : "");
|
||||
String secondPart = this.lastName != null && this.lastName.length() >= 3 ? this.lastName.substring(0, 3) : (this.lastName != null ? this.lastName : "");
|
||||
this.username = firstPart + secondPart;
|
||||
return this.username;
|
||||
return this.username.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.hitcommunications.servermanager.model.dtos;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public record NewUserDTO(
|
||||
@NotBlank String firstName,
|
||||
@NotBlank String lastName,
|
||||
@NotBlank @Email String email,
|
||||
@NotBlank String password
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.hitcommunications.servermanager.model.dtos;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
public record UserDTO(
|
||||
String id,
|
||||
String username,
|
||||
String firstName,
|
||||
String lastName,
|
||||
String email,
|
||||
Timestamp createdAt,
|
||||
Timestamp updatedAt,
|
||||
Timestamp lastLogin
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.hitcommunications.servermanager.repositories;
|
||||
|
||||
import com.hitcommunications.servermanager.model.Users;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface UsersRepository extends JpaRepository<Users, UUID> {
|
||||
Optional<Users> findByUsername(String username);
|
||||
Optional<Users> findByEmail(String email);
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.hitcommunications.servermanager.services;
|
||||
|
||||
import com.hitcommunications.servermanager.mappers.UsersMapper;
|
||||
import com.hitcommunications.servermanager.model.Users;
|
||||
import com.hitcommunications.servermanager.model.dtos.NewUserDTO;
|
||||
import com.hitcommunications.servermanager.model.dtos.UserDTO;
|
||||
import com.hitcommunications.servermanager.repositories.UsersRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class UsersService {
|
||||
private final UsersMapper mapper;
|
||||
private final UsersRepository repo;
|
||||
|
||||
private final String ALLOWED_DOMAIN = Arrays.asList("hittelco.com", "accesscommunications.com").toString();
|
||||
|
||||
public UserDTO create(NewUserDTO createDTO) throws IllegalAccessException {
|
||||
String domain = getDomain(createDTO.email());
|
||||
if (!ALLOWED_DOMAIN.contains(domain)) {
|
||||
throw new IllegalAccessException("Email domain not allowed: " + domain);
|
||||
}
|
||||
|
||||
repo.findByEmail(createDTO.email()).ifPresent(entity -> {
|
||||
throw new RuntimeException("Email already exists: " + createDTO.email());
|
||||
});
|
||||
|
||||
Users entity = mapper.toEntity(createDTO);
|
||||
entity = repo.save(entity);
|
||||
return mapper.toDTO(entity);
|
||||
}
|
||||
|
||||
public UserDTO getById(UUID id) {
|
||||
return repo.findById(id)
|
||||
.map(mapper::toDTO)
|
||||
.orElseThrow(() -> new RuntimeException("User not found with id: " + id));
|
||||
}
|
||||
|
||||
public UserDTO getByUsername(String username) {
|
||||
return repo.findByUsername(username)
|
||||
.map(mapper::toDTO)
|
||||
.orElseThrow(() -> new RuntimeException("User not found with username: " + username));
|
||||
}
|
||||
|
||||
public UserDTO getByEmail(String email) {
|
||||
return repo.findByEmail(email)
|
||||
.map(mapper::toDTO)
|
||||
.orElseThrow(() -> new RuntimeException("User not found with email: " + email));
|
||||
}
|
||||
|
||||
public List<UserDTO> getAll() {
|
||||
return repo.findAll()
|
||||
.stream()
|
||||
.map(mapper::toDTO)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public UserDTO update(UUID id, NewUserDTO updateDTO) throws IllegalAccessException {
|
||||
Users entity = repo.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("User not found with id: " + id));
|
||||
|
||||
String domain = getDomain(updateDTO.email());
|
||||
if (!ALLOWED_DOMAIN.contains(domain)) {
|
||||
throw new IllegalAccessException("Email domain not allowed: " + domain);
|
||||
}
|
||||
|
||||
// Check if email already exists (excluding current user)
|
||||
repo.findByEmail(updateDTO.email()).ifPresent(existingUser -> {
|
||||
if (!existingUser.getId().equals(id)) {
|
||||
throw new RuntimeException("Email already exists: " + updateDTO.email());
|
||||
}
|
||||
});
|
||||
|
||||
mapper.partialUpdate(updateDTO, entity);
|
||||
entity = repo.save(entity);
|
||||
return mapper.toDTO(entity);
|
||||
}
|
||||
|
||||
public void delete(UUID id) {
|
||||
if (!repo.existsById(id)) {
|
||||
throw new RuntimeException("User not found with id: " + id);
|
||||
}
|
||||
repo.deleteById(id);
|
||||
}
|
||||
|
||||
private String getDomain(String email) {
|
||||
return email.substring(email.indexOf("@") + 1);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue