Skip to content
Snippets Groups Projects
Commit 38c11c49 authored by Filip Kollár's avatar Filip Kollár
Browse files

Merge branch 'user-service-persistence' into 'master'

User service persistence

See merge request !31
parents f9dc1d06 128454d2
No related branches found
No related tags found
1 merge request!31User service persistence
Showing
with 180 additions and 71 deletions
server: server:
servlet: servlet:
context-path: '/api/analytics-service' context-path: '/api/analytics-service'
\ No newline at end of file port: 8080
server: server:
servlet: servlet:
context-path: '/api/currency-service' context-path: '/api/currency-service'
port: 8081
currency: currency:
auto-update: auto-update:
......
...@@ -4,6 +4,10 @@ services: ...@@ -4,6 +4,10 @@ services:
build: build:
context: analytics-service context: analytics-service
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on:
- analytics-db
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://analytics-db:5432/analytics_db
ports: ports:
- 8080:8080 - 8080:8080
analytics-db: analytics-db:
...@@ -22,8 +26,12 @@ services: ...@@ -22,8 +26,12 @@ services:
build: build:
context: currency-service context: currency-service
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on:
- currency-db
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://currency-db:5432/currency_db
ports: ports:
- 8081:8080 - 8081:8081
currency-db: currency-db:
container_name: currency-db container_name: currency-db
image: postgres:latest image: postgres:latest
...@@ -40,8 +48,12 @@ services: ...@@ -40,8 +48,12 @@ services:
build: build:
context: transaction-service context: transaction-service
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on:
- transaction-db
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://transaction-db:5432/transaction_db
ports: ports:
- 8082:8080 - 8082:8082
transaction-db: transaction-db:
container_name: transaction-db container_name: transaction-db
image: postgres:latest image: postgres:latest
...@@ -58,8 +70,12 @@ services: ...@@ -58,8 +70,12 @@ services:
build: build:
context: user-service context: user-service
dockerfile: Dockerfile dockerfile: Dockerfile
depends_on:
- user-db
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://user-db:5432/user_db
ports: ports:
- 8083:8080 - 8083:8083
user-db: user-db:
container_name: user-db container_name: user-db
image: postgres:latest image: postgres:latest
......
...@@ -36,4 +36,20 @@ In M2 we need to implement creation and execution of scheduled payments. ...@@ -36,4 +36,20 @@ In M2 we need to implement creation and execution of scheduled payments.
## Currency-service ## Currency-service
Service handles all currency related operation, it manages currencies so exchange rates are always up-to-date and Service handles all currency related operation, it manages currencies so exchange rates are always up-to-date and
provides needed services to the rest of the system. provides needed services to the rest of the system.
\ No newline at end of file
### Swagger Links
- [Analytics-service](http://localhost:8080/api/analytics-service/swagger-ui/index.html)
- [User-service](http://localhost:8083/api/user-service/swagger-ui/index.html)
- [Transaction-service](http://localhost:8082/api/transaction-service/swagger-ui/index.html)
- [Currency-service](http://localhost:8081/api/currency-service/swagger-ui/index.html)
### Adminer
Password: `changemelater`
- [Analytics-service](http://localhost:8084/?pgsql=analytics-db&username=analytics_service&db=analytics_db&)
- [User-service](http://localhost:8084/?pgsql=user-db&username=user_service&db=user_db&)
- [Transaction-service](http://localhost:8084/?pgsql=transaction-db&username=transaction_service&db=transaction_db&)
- [Currency-service](http://localhost:8084/?pgsql=currency-db&username=currency_service&db=currency_db&)
\ No newline at end of file
...@@ -12,3 +12,4 @@ public record AccountCreateDto( ...@@ -12,3 +12,4 @@ public record AccountCreateDto(
@NotBlank @NotBlank
String accountNumber) { String accountNumber) {
} }
package cz.muni.fi.obs.controller; package cz.muni.fi.obs.controller;
import static cz.muni.fi.obs.controller.AccountController.ACCOUNT_PATH;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cz.muni.fi.obs.api.AccountCreateDto; import cz.muni.fi.obs.api.AccountCreateDto;
import cz.muni.fi.obs.data.dbo.AccountDbo; import cz.muni.fi.obs.data.dbo.AccountDbo;
import cz.muni.fi.obs.exceptions.ResourceNotFoundException; import cz.muni.fi.obs.exceptions.ResourceNotFoundException;
...@@ -22,6 +8,16 @@ import io.swagger.v3.oas.annotations.Operation; ...@@ -22,6 +8,16 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cz.muni.fi.obs.controller.AccountController.ACCOUNT_PATH;
@Slf4j @Slf4j
@Validated @Validated
...@@ -72,4 +68,24 @@ public class AccountController { ...@@ -72,4 +68,24 @@ public class AccountController {
} }
); );
} }
@Operation(
summary = "Find accounts by customer id",
description = "Finds an account by its owner id",
responses = {
@ApiResponse(responseCode = "200", description = "Accounts for customer found"),
@ApiResponse(responseCode = "404", description = "Accounts not found")
}
)
@GetMapping(value = "/customer/{customerId}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<AccountDbo>> findAccountByCustomerId(@PathVariable("customerId") String customerId) {
List<AccountDbo> accounts = facade.findAccountsByCustomerId(customerId);
if (accounts.isEmpty()) {
log.info("Accounts not found for customer: {}", customerId);
throw new ResourceNotFoundException("Accounts not found for customer: " + customerId);
}
return ResponseEntity.ok(accounts);
}
} }
package cz.muni.fi.obs.data.repository; package cz.muni.fi.obs.data.repository;
import java.util.List; import cz.muni.fi.obs.data.dbo.AccountDbo;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import cz.muni.fi.obs.data.dbo.AccountDbo; import java.util.List;
import java.util.Optional;
@Repository @Repository
public interface AccountRepository extends JpaRepository<AccountDbo, String> { public interface AccountRepository extends JpaRepository<AccountDbo, String> {
...@@ -15,5 +14,6 @@ public interface AccountRepository extends JpaRepository<AccountDbo, String> { ...@@ -15,5 +14,6 @@ public interface AccountRepository extends JpaRepository<AccountDbo, String> {
Optional<AccountDbo> findAccountDboByCustomerId(String customerId); Optional<AccountDbo> findAccountDboByCustomerId(String customerId);
List<AccountDbo> findAccountDbosByCustomerId(String customerId);
List<AccountDbo> findAllByCurrencyCode(String currencyCode); List<AccountDbo> findAllByCurrencyCode(String currencyCode);
} }
package cz.muni.fi.obs.facade; package cz.muni.fi.obs.facade;
import java.math.BigDecimal;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import cz.muni.fi.obs.api.AccountCreateDto; import cz.muni.fi.obs.api.AccountCreateDto;
import cz.muni.fi.obs.api.TransactionCreateDto; import cz.muni.fi.obs.api.TransactionCreateDto;
import cz.muni.fi.obs.data.dbo.AccountDbo; import cz.muni.fi.obs.data.dbo.AccountDbo;
...@@ -15,6 +8,13 @@ import cz.muni.fi.obs.exceptions.ResourceNotFoundException; ...@@ -15,6 +8,13 @@ import cz.muni.fi.obs.exceptions.ResourceNotFoundException;
import cz.muni.fi.obs.service.AccountService; import cz.muni.fi.obs.service.AccountService;
import cz.muni.fi.obs.service.TransactionService; import cz.muni.fi.obs.service.TransactionService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
@Slf4j @Slf4j
@Component @Component
...@@ -64,5 +64,9 @@ public class TransactionManagementFacade { ...@@ -64,5 +64,9 @@ public class TransactionManagementFacade {
return new ResourceNotFoundException("Account not found"); return new ResourceNotFoundException("Account not found");
}); });
} }
public List<AccountDbo> findAccountsByCustomerId(String customerId) {
return accountService.findAccountsByCustomerId(customerId);
}
} }
package cz.muni.fi.obs.service; package cz.muni.fi.obs.service;
import java.util.Optional;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cz.muni.fi.obs.api.AccountCreateDto; import cz.muni.fi.obs.api.AccountCreateDto;
import cz.muni.fi.obs.data.dbo.AccountDbo; import cz.muni.fi.obs.data.dbo.AccountDbo;
import cz.muni.fi.obs.data.repository.AccountRepository; import cz.muni.fi.obs.data.repository.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service @Service
public class AccountService { public class AccountService {
...@@ -32,4 +32,8 @@ public class AccountService { ...@@ -32,4 +32,8 @@ public class AccountService {
public Optional<AccountDbo> findAccountByAccountNumber(String accountNumber) { public Optional<AccountDbo> findAccountByAccountNumber(String accountNumber) {
return repository.findAccountDboByAccountNumber(accountNumber); return repository.findAccountDboByAccountNumber(accountNumber);
} }
public List<AccountDbo> findAccountsByCustomerId(String customerId) {
return repository.findAccountDbosByCustomerId(customerId);
}
} }
server: server:
servlet: servlet:
context-path: '/api/transaction-service' context-path: '/api/transaction-service'
port: 8082
spring: spring:
application: application:
...@@ -35,4 +36,5 @@ resilience4j: ...@@ -35,4 +36,5 @@ resilience4j:
clients: clients:
currency-service: currency-service:
url: 'http://localhost:8080/api/currency-service' # url: 'http://localhost:8081/api/currency-service'
url: 'http://currency-service:8081/api/currency-service'
...@@ -70,6 +70,33 @@ ...@@ -70,6 +70,33 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>
......
...@@ -2,7 +2,9 @@ package cz.muni.fi.obs; ...@@ -2,7 +2,9 @@ package cz.muni.fi.obs;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients
@SpringBootApplication @SpringBootApplication
public class UserManagement { public class UserManagement {
......
...@@ -5,8 +5,8 @@ import jakarta.validation.constraints.NotBlank; ...@@ -5,8 +5,8 @@ import jakarta.validation.constraints.NotBlank;
public record AccountCreateDto( public record AccountCreateDto(
@Schema(description = "User ID of the account owner", example = "d333c127-470b-4680-8c7c-70988998b329") @Schema(description = "Account number", example = "19-2000145399/0800")
@NotBlank(message = "User ID is required") @NotBlank(message = "Account number is required")
String accountNumber, String accountNumber,
@Schema(description = "Currency code of the account", example = "CZK") @Schema(description = "Currency code of the account", example = "CZK")
......
package cz.muni.fi.obs.api; package cz.muni.fi.obs.api;
import cz.muni.fi.obs.domain.Account;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder; import lombok.Builder;
import java.util.UUID;
@Builder @Builder
public record AccountDto( public record AccountDto(
@Schema(description = "Unique ID of the account", example = "d333c127-470b-4680-8c7c-70988998b329") @Schema(description = "Unique ID of the account", example = "d333c127-470b-4680-8c7c-70988998b329")
String id, UUID id,
@Schema(description = "Account number", example = "12-1234567890/0100") @Schema(description = "Account number", example = "12-1234567890/0100")
String accountNumber, String accountNumber,
...@@ -15,10 +16,4 @@ public record AccountDto( ...@@ -15,10 +16,4 @@ public record AccountDto(
@Schema(description = "Accounts currency code", example = "CZK") @Schema(description = "Accounts currency code", example = "CZK")
String currencyCode String currencyCode
) { ) {
public static AccountDto fromAccount(Account account) {
if (account == null) {
return null;
}
return AccountDto.builder().id(account.getId()).accountNumber(account.getAccountNumber()).currencyCode(account.getCurrencyCode()).build();
}
} }
...@@ -7,7 +7,8 @@ import lombok.Builder; ...@@ -7,7 +7,8 @@ import lombok.Builder;
@Builder @Builder
public record NotFoundResponse( public record NotFoundResponse(
@Schema(description = "Message describing the error", example = "User with id: d333c127-470b-4680-8c7c-70988998b329 not found") @Schema(description = "Message describing the error",
example = "User with id: d333c127-470b-4680-8c7c-70988998b329 not found")
String message String message
) { ) {
} }
package cz.muni.fi.obs.api; package cz.muni.fi.obs.api;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import cz.muni.fi.obs.validation.nationality.Nationality;
import cz.muni.fi.obs.validation.nationalityBirthNumber.NationalityBirthNumber; import cz.muni.fi.obs.validation.nationalityBirthNumber.NationalityBirthNumber;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email; import jakarta.validation.constraints.Email;
...@@ -33,7 +32,7 @@ public record UserCreateDto( ...@@ -33,7 +32,7 @@ public record UserCreateDto(
@Past(message = "Birth date must be in the past") LocalDate birthDate, @Past(message = "Birth date must be in the past") LocalDate birthDate,
@Schema(description = "Nationality code of the user", example = "CZ") @Schema(description = "Nationality code of the user", example = "CZ")
@Nationality(message = "Nationality is not valid") String nationality, cz.muni.fi.obs.data.enums.Nationality nationality,
@Schema(description = "Birth number of the user, corresponding with the nationality", example = "010704/4267") @Schema(description = "Birth number of the user, corresponding with the nationality", example = "010704/4267")
@NotBlank(message = "Birth number is required") String birthNumber) { @NotBlank(message = "Birth number is required") String birthNumber) {
......
package cz.muni.fi.obs.api; package cz.muni.fi.obs.api;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import cz.muni.fi.obs.domain.User; import cz.muni.fi.obs.data.dbo.User;
import cz.muni.fi.obs.enums.Nationality; import cz.muni.fi.obs.data.enums.Nationality;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder; import lombok.Builder;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID;
@Builder @Builder
public record UserDto( public record UserDto(
@Schema(description = "Unique ID of the user", example = "d333c127-470b-4680-8c7c-70988998b329") @Schema(description = "Unique ID of the user", example = "d333c127-470b-4680-8c7c-70988998b329")
String id, UUID id,
@Schema(description = "First name of the user", example = "John") @Schema(description = "First name of the user", example = "John")
String firstName, String firstName,
...@@ -42,6 +43,9 @@ public record UserDto( ...@@ -42,6 +43,9 @@ public record UserDto(
if (user == null) { if (user == null) {
return null; return null;
} }
return UserDto.builder().id(user.getId()).firstName(user.getFirstName()).lastName(user.getLastName()).phoneNumber(user.getPhoneNumber()).email(user.getEmail()).birthDate(user.getBirthDate()).nationality(user.getNationality()).birthNumber(user.getBirthNumber()).active(user.getActive()).build(); return UserDto.builder().id(user.getId()).firstName(user.getFirstName()).lastName(user.getLastName())
.phoneNumber(user.getPhoneNumber()).email(user.getEmail()).birthDate(user.getBirthDate())
.nationality(user.getNationality()).birthNumber(user.getBirthNumber()).active(user.isActive())
.build();
} }
} }
\ No newline at end of file
...@@ -6,33 +6,36 @@ import lombok.Builder; ...@@ -6,33 +6,36 @@ import lombok.Builder;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.Optional;
@Builder @Builder
public record UserSearchParamsDto( public record UserSearchParamsDto(
@Schema(description = "First name of the user", example = "John") @Schema(description = "First name of the user", example = "John")
String firstName, Optional<String> firstName,
@Schema(description = "Last name of the user", example = "Doe") @Schema(description = "Last name of the user", example = "Doe")
String lastName, Optional<String> lastName,
@Schema(description = "Phone number of the user", example = "+420 123 456 789") @Schema(description = "Phone number of the user", example = "+420 123 456 789")
String phoneNumber, Optional<String> phoneNumber,
@Schema(description = "Email of the user", example = "john.doe@example.com") @Schema(description = "Email of the user", example = "john.doe@example.com")
String email, Optional<String> email,
@Schema(description = "Birth date of the user", example = "1990-01-01", format = "date") @Schema(description = "Birth date of the user", example = "1990-01-01", format = "date")
@JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd")
LocalDate birthDate, Optional<LocalDate> birthDate,
@Schema(description = "Birth number of the user", example = "010704/4267") @Schema(description = "Birth number of the user", example = "010704/4267")
String birthNumber, Optional<String> birthNumber,
@Schema(description = "Activity status of the user account", example = "true") @Schema(description = "Activity status of the user account", example = "true")
Boolean active, Optional<Boolean> active,
@Schema(description = "Pageable object for pagination") @Schema(description = "Pageable object for pagination")
Pageable pageable Pageable pageable
) { ) {
} }
package cz.muni.fi.obs.api; package cz.muni.fi.obs.api;
import cz.muni.fi.obs.validation.someOptionalPresent.SomeOptionalPresent;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder; import lombok.Builder;
import java.util.Optional;
@Builder @Builder
@SomeOptionalPresent
public record UserUpdateDto( public record UserUpdateDto(
@Schema(description = "First name of the user", example = "John") @Schema(description = "First name of the user", example = "John") Optional<String> firstName,
String firstName,
@Schema(description = "Last name of the user", example = "Doe") @Schema(description = "Last name of the user", example = "Doe") Optional<String> lastName,
String lastName,
@Schema(description = "Phone number of the user", example = "+420 123 456 789") @Schema(description = "Phone number of the user", example = "+420 123 456 789") Optional<String> phoneNumber,
String phoneNumber,
@Schema(description = "Email of the user", example = "john.doe@example.com") @Schema(description = "Email of the user", example = "john.doe@example.com") Optional<String> email) {
String email
) {
} }
...@@ -3,7 +3,11 @@ package cz.muni.fi.obs.api; ...@@ -3,7 +3,11 @@ package cz.muni.fi.obs.api;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder; import lombok.Builder;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -15,4 +19,18 @@ public record ValidationErrors( ...@@ -15,4 +19,18 @@ public record ValidationErrors(
@Schema(description = "Map of field errors", example = "{\"firstName\":\"firstName is required\"}") @Schema(description = "Map of field errors", example = "{\"firstName\":\"firstName is required\"}")
Map<String, String> fieldErrors Map<String, String> fieldErrors
) { ) {
public static ValidationErrors fromBindException(org.springframework.validation.BindException ex) {
Map<String, String> fieldErrors = new HashMap<>();
for (FieldError fieldError : ex.getFieldErrors()) {
fieldErrors.put(fieldError.getField(), fieldError.getDefaultMessage());
}
List<String> globalErrors = new ArrayList<>();
for (ObjectError error : ex.getGlobalErrors()) {
globalErrors.add(error.getDefaultMessage());
}
return ValidationErrors.builder().fieldErrors(fieldErrors).globalErrors(globalErrors).build();
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment