Merge pull request #49 from dartandrevinsky/private-event-sourcing-examples-38

Account deletion + UI improvements
This commit is contained in:
Chris Richardson
2016-09-23 06:49:31 -07:00
committed by GitHub
134 changed files with 3629 additions and 4867 deletions

View File

@@ -3,23 +3,30 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.
import io.eventuate.Event;
import io.eventuate.EventUtil;
import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitFailedDueToInsufficientFundsEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDebitedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountOpenedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Account extends ReflectiveMutableCommandProcessingAggregate<Account, AccountCommand> {
private BigDecimal balance;
private boolean deleted;
public List<Event> process(OpenAccountCommand cmd) {
return EventUtil.events(new AccountOpenedEvent(cmd.getCustomerId(), cmd.getTitle(), cmd.getInitialBalance(), cmd.getDescription()));
}
public List<Event> process(DeleteAccountCommand cmd) {
return EventUtil.events(new AccountDeletedEvent());
}
public List<Event> process(DebitAccountCommand cmd) {
if(deleted)
return new ArrayList<>();
if (balance.compareTo(cmd.getAmount()) < 0)
return EventUtil.events(new AccountDebitFailedDueToInsufficientFundsEvent(cmd.getTransactionId()));
else
@@ -27,6 +34,9 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
}
public List<Event> process(CreditAccountCommand cmd) {
if(deleted)
return new ArrayList<>();
return EventUtil.events(new AccountCreditedEvent(cmd.getAmount(), cmd.getTransactionId()));
}
@@ -34,6 +44,10 @@ public class Account extends ReflectiveMutableCommandProcessingAggregate<Account
balance = event.getInitialBalance();
}
public void apply(AccountDeletedEvent event) {
deleted = true;
}
public void apply(AccountDebitedEvent event) {
balance = balance.subtract(event.getAmount());
}

View File

@@ -19,4 +19,7 @@ public class AccountService {
return accountRepository.save(new OpenAccountCommand(customerId, title, initialBalance, description));
}
public CompletableFuture<EntityWithIdAndVersion<Account>> deleteAccount(String accountId) {
return accountRepository.update(accountId, new DeleteAccountCommand());
}
}

View File

@@ -5,6 +5,7 @@ import io.eventuate.EntityWithIdAndVersion;
import io.eventuate.EventHandlerContext;
import io.eventuate.EventHandlerMethod;
import io.eventuate.EventSubscriber;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerToAccountDeleted;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.DebitRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.MoneyTransferCreatedEvent;
@@ -22,13 +23,7 @@ public class AccountWorkflow {
String fromAccountId = event.getDetails().getFromAccountId();
return ctx.update(Account.class, fromAccountId, new DebitAccountCommand(amount, transactionId)).handle((x, e) -> {
if (e != null) {
e.printStackTrace();
}
return x;
}
);
return ctx.update(Account.class, fromAccountId, new DebitAccountCommand(amount, transactionId));
}
@EventHandlerMethod
@@ -38,13 +33,6 @@ public class AccountWorkflow {
String fromAccountId = event.getDetails().getToAccountId();
String transactionId = ctx.getEntityId();
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId)).handle((x, e) -> {
if (e != null) {
e.printStackTrace();
}
return x;
}
);
return ctx.update(Account.class, fromAccountId, new CreditAccountCommand(amount, transactionId));
}
}

View File

@@ -0,0 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts;
public class DeleteAccountCommand implements AccountCommand {
}

View File

@@ -3,12 +3,10 @@ package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.acco
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.DeleteAccountResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;
@@ -26,6 +24,12 @@ public class AccountController {
@RequestMapping(method = RequestMethod.POST)
public CompletableFuture<CreateAccountResponse> createAccount(@Validated @RequestBody CreateAccountRequest request) {
return accountService.openAccount(request.getCustomerId(), request.getTitle(), request.getInitialBalance(), request.getDescription())
.thenApply(entityAndEventInfo -> new CreateAccountResponse(entityAndEventInfo.getEntityId()));
.thenApply(entityAndEventInfo -> new CreateAccountResponse(entityAndEventInfo.getEntityId()));
}
@RequestMapping(value = "{accountId}", method = RequestMethod.DELETE)
public CompletableFuture<DeleteAccountResponse> deleteAccount(@PathVariable String accountId) {
return accountService.deleteAccount(accountId)
.thenApply(entityAndEventInfo -> new DeleteAccountResponse(accountId));
}
}

View File

@@ -53,6 +53,10 @@ public class AccountInfoUpdateService {
}
}
public void delete(String accountId) {
accountInfoRepository.delete(accountId);
}
public void addTransaction(String accountId, AccountTransactionInfo ti) {
mongoTemplate.upsert(new Query(where("id").is(accountId)),

View File

@@ -45,6 +45,12 @@ public class AccountQueryWorkflow {
accountInfoUpdateService.create(id, customerId, title, initialBalance, description, eventId);
}
@EventHandlerMethod
public void delete(DispatchedEvent<AccountDeletedEvent> de) {
String id = de.getEntityId();
accountInfoUpdateService.delete(id);
}
@EventHandlerMethod
public void recordTransfer(DispatchedEvent<MoneyTransferCreatedEvent> de) {
String eventId = de.getEventId().asString();

View File

@@ -5,9 +5,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
/**
* Created by popikyardo on 15.01.16.
*/
@ConfigurationProperties(prefix = "api.gateway")
public class ApiGatewayProperties {

View File

@@ -20,9 +20,6 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
import java.util.Collections;
/**
* Created by popikyardo on 15.01.16.
*/
@Configuration
@ComponentScan
@EnableAutoConfiguration

View File

@@ -2,9 +2,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.apigateway;
import org.springframework.http.HttpStatus;
/**
* Created by popikyardo on 07.12.15.
*/
public class RestUtil {
public static boolean isError(HttpStatus status) {

View File

@@ -16,7 +16,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@@ -32,12 +31,8 @@ import java.net.URISyntaxException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
import static org.springframework.web.bind.annotation.RequestMethod.*;
/**
* Created by popikyardo on 15.01.16.
*/
@RestController
public class GatewayController {
@@ -57,7 +52,7 @@ public class GatewayController {
.build();
}
@RequestMapping(value = "/api/**", method = {GET, POST})
@RequestMapping(value = "/api/**", method = {GET, POST, DELETE})
@ResponseBody
public ResponseEntity<String> proxyRequest(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, IOException, URISyntaxException {
HttpUriRequest proxiedRequest = createHttpUriRequest(request);

View File

@@ -10,9 +10,6 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.util.stream.Collectors;
/**
* Created by popikyardo on 21.01.16.
*/
public class ContentRequestTransformer extends ProxyRequestTransformer {
@Override

View File

@@ -8,9 +8,6 @@ import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Enumeration;
/**
* Created by popikyardo on 21.01.16.
*/
public class HeadersRequestTransformer extends ProxyRequestTransformer {
@Override

View File

@@ -7,9 +7,6 @@ import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URISyntaxException;
/**
* Created by popikyardo on 21.01.16.
*/
public abstract class ProxyRequestTransformer {
protected ProxyRequestTransformer predecessor;

View File

@@ -9,9 +9,6 @@ import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Created by popikyardo on 21.01.16.
*/
public class URLRequestTransformer extends ProxyRequestTransformer {
private ApiGatewayProperties apiGatewayProperties;

View File

@@ -22,4 +22,10 @@ api.gateway.endpoints[4].method=POST
api.gateway.endpoints[4].location=http://${customers.commandside.service.host}:8080
api.gateway.endpoints[5].path=[/]*api/transfers.*
api.gateway.endpoints[5].method=POST
api.gateway.endpoints[5].location=http://${transfers.commandside.service.host}:8080
api.gateway.endpoints[5].location=http://${transfers.commandside.service.host}:8080
api.gateway.endpoints[6].path=[/]*api/customers.*
api.gateway.endpoints[6].method=DELETE
api.gateway.endpoints[6].location=http://${customers.commandside.service.host}:8080
api.gateway.endpoints[7].path=[/]*api/accounts.*
api.gateway.endpoints[7].method=DELETE
api.gateway.endpoints[7].location=http://${accounts.commandside.service.host}:8080

View File

@@ -61,7 +61,7 @@ public class CustomerQuerySideIntegrationTest {
public void verify(QuerySideCustomer querySideCustomer) {
Assert.assertEquals(customerInfo.getName(), querySideCustomer.getName());
Assert.assertEquals(customerInfo.getSsn(), querySideCustomer.getSsn());
Assert.assertEquals(customerInfo.getEmail(), querySideCustomer.getEmail());
Assert.assertEquals(customerInfo.getUserCredentials().getEmail(), querySideCustomer.getEmail());
Assert.assertEquals(customerInfo.getPhoneNumber(), querySideCustomer.getPhoneNumber());
Assert.assertEquals(customerInfo.getAddress(), querySideCustomer.getAddress());

View File

@@ -1,4 +1,5 @@
#! /bin/bash
export JAVA_OPTS="-Xmx128m -Xms128m"
export EXTRA_INFRASTRUCTURE_SERVICES=cdcservice
../_build-and-test-all.sh -f docker-compose-eventuate-local.yml -P eventuateLocal=1 $*

View File

@@ -1,3 +1,4 @@
#! /bin/bash
export JAVA_OPTS="-Xmx128m -Xms128m"
../_build-and-test-all.sh $*

View File

@@ -2,6 +2,7 @@ package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.controlle
import com.fasterxml.jackson.databind.ObjectMapper;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.UserCredentials;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.CustomerAuthService;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.AuthRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.ErrorResponse;
@@ -23,9 +24,6 @@ import java.io.IOException;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.POST;
/**
* Created by popikyardo on 21.09.15.
*/
@RestController
@Validated
@RequestMapping("/api")
@@ -40,8 +38,8 @@ public class AuthController {
private static ObjectMapper objectMapper = new ObjectMapper();
@RequestMapping(value = "/login", method = POST)
public ResponseEntity<QuerySideCustomer> doAuth(@RequestBody @Valid AuthRequest request) throws IOException {
QuerySideCustomer customer = customerAuthService.findByEmail(request.getEmail());
public ResponseEntity<QuerySideCustomer> doAuth(@RequestBody @Valid UserCredentials request) throws IOException {
QuerySideCustomer customer = customerAuthService.findByEmailAndPassword(request.getEmail(), request.getPassword());
Token token = tokenService.allocateToken(objectMapper.writeValueAsString(new User(request.getEmail())));
return ResponseEntity.status(HttpStatus.OK).header("access-token", token.getKey())

View File

@@ -3,20 +3,21 @@ package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
/**
* Created by popikyardo on 19.10.15.
*/
public class AuthRequest {
@NotBlank
@Email
private String email;
@NotBlank
private String password;
public AuthRequest() {
}
public AuthRequest(String email) {
public AuthRequest(String email, String password) {
this.email = email;
this.password = password;
}
public String getEmail() {
@@ -26,4 +27,12 @@ public class AuthRequest {
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -1,5 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.filter.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -22,9 +23,6 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationFi
import java.security.SecureRandom;
/**
* Created by popikyardo on 21.09.15.
*/
@Configuration
@ComponentScan
@EnableWebSecurity
@@ -43,21 +41,14 @@ public class AuthConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.inMemoryAuthentication();
auth.userDetailsService(userDetailsServiceBean());
}
@Override
public UserDetailsService userDetailsServiceBean() {
return email -> {
/* QuerySideCustomer customer = customerAuthService.findByEmail(email);
if (customer != null) {
return new User(email);
} else {
throw new UsernameNotFoundException(String.format("could not find the customer '%s'", email));
}*/
//authorize everyone with basic authentication
return new User(email, "", true, true, true, true,
QuerySideCustomer customer = customerAuthService.findByEmail(email);
return new User(email, customer.getPassword(), true, true, true, true,
AuthorityUtils.createAuthorityList("USER"));
};
}

View File

@@ -2,9 +2,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Created by popikyardo on 21.09.15.
*/
@ConfigurationProperties(locations = "classpath:auth.properties", ignoreUnknownFields = false, prefix = "auth")
public class AuthProperties {
private String serverSecret;

View File

@@ -8,4 +8,6 @@ import java.util.List;
interface CustomerAuthRepository extends MongoRepository<QuerySideCustomer, String> {
List<QuerySideCustomer> findByEmail(String email);
List<QuerySideCustomer> findByEmailAndPassword(String email, String password);
}

View File

@@ -1,9 +1,7 @@
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import org.springframework.dao.EmptyResultDataAccessException;
import java.util.List;
import org.springframework.dao.support.DataAccessUtils;
/**
* Created by Main on 15.02.2016.
@@ -16,13 +14,10 @@ public class CustomerAuthService {
}
public QuerySideCustomer findByEmail(String email) {
List<QuerySideCustomer> customers = customerAuthRepository.findByEmail(email);
if (customers.isEmpty())
throw new EmptyResultDataAccessException(1);
//TODO: add unique email constraint
/* else if(customers.size()>1)
throw new IncorrectResultSizeDataAccessException(1, customers.size());*/
else
return customers.get(0);
return DataAccessUtils.uniqueResult(customerAuthRepository.findByEmail(email));
}
public QuerySideCustomer findByEmailAndPassword(String email, String password) {
return DataAccessUtils.uniqueResult(customerAuthRepository.findByEmailAndPassword(email, password));
}
}

View File

@@ -12,9 +12,6 @@ import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by popikyardo on 23.09.15.
*/
@Service
public class TokenAuthenticationService {

View File

@@ -11,9 +11,6 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* Created by popikyardo on 23.09.15.
*/
public class StatelessAuthenticationFilter extends GenericFilterBean {
private final TokenAuthenticationService tokenAuthenticationService;

View File

@@ -10,9 +10,6 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Created by popikyardo on 23.09.15.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class User implements UserDetails {

View File

@@ -5,9 +5,6 @@ import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* Created by popikyardo on 23.09.15.
*/
public class UserAuthentication implements Authentication {
private final User user;

View File

@@ -0,0 +1,8 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts;
import io.eventuate.Event;
import java.util.Date;
public class AccountDeletedEvent implements Event {
}

View File

@@ -2,9 +2,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.custo
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
/**
* Created by popikyardo on 02.02.16.
*/
public class CustomerCreatedEvent extends CustomerEvent {
private CustomerInfo customerInfo;

View File

@@ -0,0 +1,21 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers;
public class CustomerToAccountDeleted extends CustomerEvent {
private String accountId;
public CustomerToAccountDeleted() {
}
public CustomerToAccountDeleted(String accountId) {
this.accountId = accountId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}

View File

@@ -3,6 +3,7 @@ apply plugin: 'java'
dependencies {
compile "commons-lang:commons-lang:2.6"
compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion"
testCompile group: 'junit', name: 'junit', version: '4.11'
}

View File

@@ -5,9 +5,6 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
import java.util.Date;
/**
* Created by popikyardo on 9/1/16.
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "entryType")

View File

@@ -2,9 +2,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import java.util.List;
/**
* Created by popikyardo on 9/1/16.
*/
public class AccountHistoryResponse {
private List<AccountHistoryEntry> transactionsHistory;

View File

@@ -2,9 +2,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import java.util.Date;
/**
* Created by popikyardo on 9/1/16.
*/
public class AccountOpenInfo extends AccountHistoryEntry {
private long initialBalance;

View File

@@ -0,0 +1,22 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
public class DeleteAccountResponse {
private String accountId;
public DeleteAccountResponse() {
}
public DeleteAccountResponse(String accountId) {
this.accountId = accountId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}

View File

@@ -2,9 +2,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import java.util.List;
/**
* Created by popikyardo on 9/1/16.
*/
public class GetAccountsResponse {
private List<GetAccountResponse> accounts;

View File

@@ -1,8 +1,5 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
/**
* Created by popikyardo on 24.03.16.
*/
public class AddToAccountResponse {
private String version;

View File

@@ -5,9 +5,6 @@ import org.apache.commons.lang.builder.HashCodeBuilder;
import javax.validation.constraints.NotNull;
/**
* Created by popikyardo on 02.02.16.
*/
public class Address {
@NotNull
private String street1;

View File

@@ -1,29 +1,26 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import javax.validation.constraints.NotNull;
/**
* Created by popikyardo on 03.02.16.
*/
public class CustomerInfo {
private Name name;
@NotNull
protected String email;
@JsonUnwrapped
protected UserCredentials userCredentials;
@NotNull
protected String ssn;
@NotNull
protected String phoneNumber;
protected Address address;
public CustomerInfo() {
}
public CustomerInfo(Name name, String email, String ssn, String phoneNumber, Address address) {
public CustomerInfo(Name name, UserCredentials userCredentials, String ssn, String phoneNumber, Address address) {
this.name = name;
this.email = email;
this.userCredentials = userCredentials;
this.ssn = ssn;
this.phoneNumber = phoneNumber;
this.address = address;
@@ -33,8 +30,8 @@ public class CustomerInfo {
return name;
}
public String getEmail() {
return email;
public UserCredentials getUserCredentials() {
return userCredentials;
}
public String getSsn() {

View File

@@ -3,9 +3,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
/**
* Created by popikyardo on 03.02.16.
*/
public class CustomerResponse {
private String id;

View File

@@ -1,14 +1,19 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
import org.springframework.data.mongodb.core.index.Indexed;
import java.util.Map;
/**
* Created by Main on 05.02.2016.
*/
public class QuerySideCustomer {
private String id;
private Name name;
@Indexed(unique=true)
private String email;
private String password;
private String ssn;
private String phoneNumber;
private Address address;
@@ -17,10 +22,11 @@ public class QuerySideCustomer {
public QuerySideCustomer() {
}
public QuerySideCustomer(String id, Name name, String email, String ssn, String phoneNumber, Address address, Map<String, ToAccountInfo> toAccounts) {
public QuerySideCustomer(String id, Name name, String email, String password, String ssn, String phoneNumber, Address address, Map<String, ToAccountInfo> toAccounts) {
this.id = id;
this.name = name;
this.email = email;
this.password = password;
this.ssn = ssn;
this.phoneNumber = phoneNumber;
this.address = address;
@@ -51,6 +57,14 @@ public class QuerySideCustomer {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSsn() {
return ssn;
}

View File

@@ -0,0 +1,49 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.customers;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.validator.constraints.Email;
import javax.validation.constraints.NotNull;
public class UserCredentials {
@NotNull
@Email
private String email;
@NotNull
private String password;
public UserCredentials() {
}
public UserCredentials(String email, String password) {
this.email = email;
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -2,9 +2,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
/**
* Created by popikyardo on 02.02.16.
*/
public class CreateCustomerCommand implements CustomerCommand {
private CustomerInfo customerInfo;

View File

@@ -3,15 +3,13 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.
import io.eventuate.Event;
import io.eventuate.EventUtil;
import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerToAccountDeleted;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerAddedToAccount;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerCreatedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import java.util.List;
/**
* Created by popikyardo on 02.02.16.
*/
public class Customer extends ReflectiveMutableCommandProcessingAggregate<Customer, CustomerCommand> {
private CustomerInfo customerInfo;
@@ -23,6 +21,9 @@ public class Customer extends ReflectiveMutableCommandProcessingAggregate<Custom
public List<Event> process(AddToAccountCommand cmd) {
return EventUtil.events(new CustomerAddedToAccount(cmd.getToAccountInfo()));
}
public List<Event> process(DeleteToAccountCommand cmd) {
return EventUtil.events(new CustomerToAccountDeleted(cmd.getAccountId()));
}
public void apply(CustomerCreatedEvent event) {
customerInfo = event.getCustomerInfo();
@@ -30,6 +31,8 @@ public class Customer extends ReflectiveMutableCommandProcessingAggregate<Custom
public void apply(CustomerAddedToAccount event) {
}
public void apply(CustomerToAccountDeleted event) {
}
public CustomerInfo getCustomerInfo() {
return customerInfo;

View File

@@ -23,4 +23,8 @@ public class CustomerService {
public CompletableFuture<EntityWithIdAndVersion<Customer>> addToAccount(String customerId, ToAccountInfo toAccountInfo) {
return accountRepository.update(customerId, new AddToAccountCommand(toAccountInfo));
}
public CompletableFuture<EntityWithIdAndVersion<Customer>> deleteToAccount(String customerId, String accountId) {
return accountRepository.update(customerId, new DeleteToAccountCommand(accountId));
}
}

View File

@@ -0,0 +1,21 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers;
public class DeleteToAccountCommand implements CustomerCommand {
private String accountId;
public DeleteToAccountCommand() {
}
public DeleteToAccountCommand(String accountId) {
this.accountId = accountId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
}

View File

@@ -1,6 +1,8 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.customers.CustomerService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.CreateAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.DeleteAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.AddToAccountResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
@@ -11,9 +13,6 @@ import org.springframework.web.bind.annotation.*;
import java.util.concurrent.CompletableFuture;
/**
* Created by popikyardo on 03.02.16.
*/
@RestController
@RequestMapping("/api/customers")
public class CustomerController {
@@ -37,4 +36,10 @@ public class CustomerController {
.thenApply(entityAndEventInfo -> new AddToAccountResponse(entityAndEventInfo.getEntityVersion().toString()));
}
@RequestMapping(value = "/{customerId}/toaccounts/{accountId}", method = RequestMethod.DELETE)
public CompletableFuture<DeleteAccountResponse> deleteToAccount(@PathVariable String customerId, @PathVariable String accountId) {
return customerService.deleteToAccount(customerId, accountId)
.thenApply(entityAndEventInfo -> new DeleteAccountResponse(accountId));
}
}

View File

@@ -6,9 +6,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Created by popikyardo on 03.02.16.
*/
@Configuration
@Import({CustomerConfiguration.class})
@ComponentScan

View File

@@ -6,46 +6,65 @@ import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAc
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import java.util.Collections;
import static org.springframework.data.mongodb.core.query.Criteria.where;
/**
* Created by Main on 04.02.2016.
*/
public class CustomerInfoUpdateService {
private Logger logger = LoggerFactory.getLogger(getClass());
private Logger logger = LoggerFactory.getLogger(getClass());
private QuerySideCustomerRepository querySideCustomerRepository;
private QuerySideCustomerRepository querySideCustomerRepository;
private MongoTemplate mongoTemplate;
public CustomerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository) {
this.querySideCustomerRepository = querySideCustomerRepository;
public CustomerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository, MongoTemplate mongoTemplate) {
this.querySideCustomerRepository = querySideCustomerRepository;
this.mongoTemplate = mongoTemplate;
}
public void create(String id, CustomerInfo customerInfo) {
try {
querySideCustomerRepository.save(new QuerySideCustomer(id,
customerInfo.getName(),
customerInfo.getUserCredentials().getEmail(),
customerInfo.getUserCredentials().getPassword(),
customerInfo.getSsn(),
customerInfo.getPhoneNumber(),
customerInfo.getAddress(),
Collections.<String, ToAccountInfo>emptyMap()
)
);
logger.info("Saved in mongo");
} catch (DuplicateKeyException t) {
logger.warn("When saving ", t);
}
}
public void create(String id, CustomerInfo customerInfo) {
try {
querySideCustomerRepository.save(new QuerySideCustomer(id,
customerInfo.getName(),
customerInfo.getEmail(),
customerInfo.getSsn(),
customerInfo.getPhoneNumber(),
customerInfo.getAddress(),
Collections.<String, ToAccountInfo>emptyMap()
)
);
logger.info("Saved in mongo");
} catch (DuplicateKeyException t) {
logger.warn("When saving ", t);
} catch (Throwable t) {
logger.error("Error during saving: ", t);
throw new RuntimeException(t);
}
}
public void addToAccount(String id, ToAccountInfo accountInfo) {
mongoTemplate.upsert(new Query(where("id").is(id)),
new Update().
set("toAccounts." + accountInfo.getId(), accountInfo),
QuerySideCustomer.class);
}
public void addToAccount(String id, ToAccountInfo accountInfo) {
QuerySideCustomer customer = querySideCustomerRepository.findOne(id);
customer.getToAccounts().put(accountInfo.getId(), accountInfo);
querySideCustomerRepository.save(customer);
}
public void deleteToAccountFromAllCustomers(String accountId) {
mongoTemplate.updateMulti(new Query(where("toAccounts." + accountId).exists(true)),
new Update().
unset("toAccounts." + accountId),
QuerySideCustomer.class);
}
public void deleteToAccount(String customerId, String accountId) {
mongoTemplate.upsert(new Query(where("id").is(customerId)),
new Update().
unset("toAccounts." + accountId),
QuerySideCustomer.class);
}
}

View File

@@ -3,6 +3,8 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.cu
import io.eventuate.DispatchedEvent;
import io.eventuate.EventHandlerMethod;
import io.eventuate.EventSubscriber;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountDeletedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerToAccountDeleted;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerAddedToAccount;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.customers.CustomerCreatedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
@@ -42,4 +44,19 @@ public class CustomerQueryWorkflow {
customerInfoUpdateService.addToAccount(id, toAccountInfo);
}
@EventHandlerMethod
public void deleteToAccount(DispatchedEvent<CustomerToAccountDeleted> de) {
String id = de.getEntityId();
CustomerToAccountDeleted event = de.getEvent();
String accountId = event.getAccountId();
customerInfoUpdateService.deleteToAccount(id, accountId);
}
@EventHandlerMethod
public void deleteToAccounts(DispatchedEvent<AccountDeletedEvent> de) {
String accountId = de.getEntityId();
customerInfoUpdateService.deleteToAccountFromAllCustomers(accountId);
}
}

View File

@@ -21,8 +21,8 @@ public class QuerySideCustomerConfiguration {
}
@Bean
public CustomerInfoUpdateService customerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository) {
return new CustomerInfoUpdateService(querySideCustomerRepository);
public CustomerInfoUpdateService customerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository, MongoTemplate mongoTemplate) {
return new CustomerInfoUpdateService(querySideCustomerRepository, mongoTemplate);
}
@Bean

View File

@@ -0,0 +1,90 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
import io.eventuate.javaclient.spring.jdbc.IdGenerator;
import io.eventuate.javaclient.spring.jdbc.IdGeneratorImpl;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountChangeInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.math.BigDecimal;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateCustomerInfo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = CustomerInfoUpdateServiceTest.CustomerInfoUpdateServiceTestConfiguration.class)
@IntegrationTest
public class CustomerInfoUpdateServiceTest {
@Configuration
@EnableAutoConfiguration
@Import({QuerySideCustomerConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
public static class CustomerInfoUpdateServiceTestConfiguration {
}
@Autowired
private CustomerInfoUpdateService customerInfoUpdateService;
@Autowired
private CustomerQueryService customerQueryService;
@Test
public void shouldSaveQuerysideCustomer() throws ExecutionException, InterruptedException {
IdGenerator x = new IdGeneratorImpl();
String customerId = x.genId().asString();
CustomerInfo customerInfo = generateCustomerInfo();
customerInfoUpdateService.create(customerId, customerInfo);
QuerySideCustomer querySideCustomer = customerQueryService.findByCustomerId(customerId).get();
assertEquals(customerInfo.getName(), querySideCustomer.getName());
assertEquals(customerInfo.getAddress(), querySideCustomer.getAddress());
assertEquals(customerInfo.getUserCredentials().getEmail(), querySideCustomer.getEmail());
assertEquals(customerInfo.getPhoneNumber(), querySideCustomer.getPhoneNumber());
assertEquals(customerInfo.getSsn(), querySideCustomer.getSsn());
}
@Test
public void shouldAddAndDeleteToAccount() throws ExecutionException, InterruptedException {
IdGenerator x = new IdGeneratorImpl();
String customerId = x.genId().asString();
String accountId = x.genId().asString();
CustomerInfo customerInfo = generateCustomerInfo();
customerInfoUpdateService.create(customerId, customerInfo);
ToAccountInfo toAccountInfo = new ToAccountInfo(accountId, "title", "owner", "description");
customerInfoUpdateService.addToAccount(customerId, toAccountInfo);
QuerySideCustomer querySideCustomer = customerQueryService.findByCustomerId(customerId).get();
assertTrue(querySideCustomer.getToAccounts().containsKey(accountId));
assertEquals(toAccountInfo, querySideCustomer.getToAccounts().get(accountId));
customerInfoUpdateService.deleteToAccountFromAllCustomers(accountId);
querySideCustomer = customerQueryService.findByCustomerId(customerId).get();
assertFalse(querySideCustomer.getToAccounts().containsKey(accountId));
}
}

View File

@@ -14,6 +14,7 @@ dependencies {
testCompile project(":testutil")
testCompile project(":customers-command-side-service")
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}
test {

View File

@@ -1,6 +1,9 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers.CustomersCommandSideWebConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.customers.queryside.CustomersQuerySideWebConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
@@ -14,7 +17,7 @@ import java.util.Arrays;
import java.util.List;
@Configuration
@Import({CustomersCommandSideServiceConfiguration.class, CustomersQuerySideServiceConfiguration.class, AuthConfiguration.class})
@Import({CustomersCommandSideWebConfiguration.class, CustomersQuerySideWebConfiguration.class, EventuateJdbcEventStoreConfiguration.class, AuthConfiguration.class})
@EnableAutoConfiguration
public class CustomersQuerySideServiceTestConfiguration {

View File

@@ -1,6 +1,6 @@
apigateway:
image: java:openjdk-8u91-jdk
command: java -jar /app/api-gateway-service.jar --accounts.commandside.service.host=accountscommandside --transfers.commandside.service.host=transactionscommandside --accounts.queryside.service.host=accountsqueryside --customers.commandside.service.host=customerscommandside --customers.queryside.service.host=customersqueryside
command: java ${JAVA_OPTS} -jar /app/api-gateway-service.jar --accounts.commandside.service.host=accountscommandside --transfers.commandside.service.host=transactionscommandside --accounts.queryside.service.host=accountsqueryside --customers.commandside.service.host=customerscommandside --customers.queryside.service.host=customersqueryside
environment:
SPRING_DATA_MONGODB_URI: mongodb://mongodb/mydb
ports:
@@ -8,19 +8,19 @@ apigateway:
accountscommandside:
image: java:openjdk-8u91-jdk
command: java -jar /app/accounts-command-side-service.jar
command: java ${JAVA_OPTS} -jar /app/accounts-command-side-service.jar
ports:
- "8085:8080"
transactionscommandside:
image: java:openjdk-8u91-jdk
command: java -jar /app/transactions-command-side-service.jar
command: java ${JAVA_OPTS} -jar /app/transactions-command-side-service.jar
ports:
- "8082:8080"
accountsqueryside:
image: java:openjdk-8u91-jdk
command: java -jar /app/accounts-query-side-service.jar
command: java ${JAVA_OPTS} -jar /app/accounts-query-side-service.jar
environment:
SPRING_DATA_MONGODB_URI: mongodb://mongodb/mydb
ports:
@@ -28,13 +28,13 @@ accountsqueryside:
customerscommandside:
image: java:openjdk-8u91-jdk
command: java -jar /app/customers-command-side-service.jar
command: java ${JAVA_OPTS} -jar /app/customers-command-side-service.jar
ports:
- "8083:8080"
customersqueryside:
image: java:openjdk-8u91-jdk
command: java -jar /app/customers-query-side-service.jar
command: java ${JAVA_OPTS} -jar /app/customers-query-side-service.jar
ports:
- "8084:8080"
environment:

View File

@@ -9,6 +9,7 @@ dependencies {
testCompile project(":common-auth")
testCompile "junit:junit:4.11"
testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
testCompile "org.jsoup:jsoup:1.9.2"
}
test {

View File

@@ -2,12 +2,21 @@ package net.chrisrichardson.eventstore.examples.bank.web;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.AbstractRestAPITest;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.AuthenticatedRestTemplate;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.junit.Test;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class EndToEndTest extends AbstractRestAPITest {
private String indexUrl = "http://" + getenv("SERVICE_HOST", "localhost") + ":" + 8080 + "/index.html";
private String getenv(String name, String defaultValue) {
String x = System.getenv(name);
return x == null ? defaultValue : x;
@@ -21,14 +30,16 @@ public class EndToEndTest extends AbstractRestAPITest {
return "http://" + getenv("SERVICE_HOST", "localhost") + ":" + 8080 + "/api" + path;
}
@Override
public CustomersTestUtils getCustomersTestUtils() {
return customersTestUtils;
@Test
public void shouldLoadStaticPage() throws IOException {
Document doc = Jsoup.connect(indexUrl).get();
assertNotNull(doc.title());
assertEquals("Money Transfer App", doc.title());
}
@Override
public AuthenticatedRestTemplate getAuthenticatedRestTemplate() {
return new AuthenticatedRestTemplate(restTemplate);
public CustomersTestUtils getCustomersTestUtils() {
return customersTestUtils;
}
@Override

View File

@@ -3,6 +3,7 @@ package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.UserCredentials;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.model.AuthRequest;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils;
import org.junit.Assert;
@@ -48,8 +49,7 @@ public class BankingAuthTest {
@Test
public void shouldCreateCustomerAndLogin() {
String email = uniqueEmail();
CustomerInfo customerInfo = generateCustomerInfo(email);
CustomerInfo customerInfo = generateCustomerInfo();
final CustomerResponse customerResponse = restTemplate.postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
final String customerId = customerResponse.getId();
@@ -57,23 +57,10 @@ public class BankingAuthTest {
Assert.assertNotNull(customerId);
Assert.assertEquals(customerInfo, customerResponse.getCustomerInfo());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
customersTestUtils.assertCustomerResponse(customerId, customerInfo);
AuthRequest authRequest = new AuthRequest(email);
final QuerySideCustomer loginQuerySideCustomer = restTemplate.postForEntity(baseUrl("/login"), authRequest, QuerySideCustomer.class).getBody();
final QuerySideCustomer loginQuerySideCustomer = restTemplate.postForEntity(baseUrl("/login"), customerInfo.getUserCredentials(), QuerySideCustomer.class).getBody();
customersTestUtils.assertQuerySideCustomerEqualscCustomerInfo(loginQuerySideCustomer, customerResponse.getCustomerInfo());
}
private String uniqueEmail() {
return System.currentTimeMillis() + "@email.com";
}
}

View File

@@ -43,11 +43,6 @@ public class BankingWebIntegrationTest extends AbstractRestAPITest {
@Autowired
RestTemplate restTemplate;
@Override
public AuthenticatedRestTemplate getAuthenticatedRestTemplate() {
return new AuthenticatedRestTemplate(restTemplate);
}
@Override
public RestTemplate getRestTemplate() {
return restTemplate;

View File

@@ -1,31 +1,36 @@
package net.chrisrichardson.eventstorestore.javaexamples.testutil;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.*;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.CustomerResponse;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.QuerySideCustomer;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.*;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.CreateMoneyTransferRequest;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.CreateMoneyTransferResponse;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateCustomerInfo;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils.generateToAccountInfo;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
public abstract class AbstractRestAPITest {
@Test
public void shouldCreateAccountsAndTransferMoney() {
CustomerInfo customerInfo = generateCustomerInfo();
AuthenticatedRestTemplate authenticatedRestTemplate = getAuthenticatedRestTemplate(customerInfo.getUserCredentials());
final CustomerResponse customerResponse = getRestTemplate().postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
final String customerId = customerResponse.getId();
getCustomersTestUtils().assertCustomerResponse(customerId, customerInfo);
BigDecimal initialFromAccountBalance = new BigDecimal(500);
BigDecimal initialToAccountBalance = new BigDecimal(100);
BigDecimal amountToTransfer = new BigDecimal(150);
@@ -33,14 +38,14 @@ public abstract class AbstractRestAPITest {
BigDecimal finalFromAccountBalance = initialFromAccountBalance.subtract(amountToTransfer);
BigDecimal finalToAccountBalance = initialToAccountBalance.add(amountToTransfer);
final CreateAccountResponse fromAccount = getAuthenticatedRestTemplate().postForEntity(baseUrl("/accounts"),
new CreateAccountRequest("00000000-00000000", "My 1 Account", "", initialFromAccountBalance),
final CreateAccountResponse fromAccount = authenticatedRestTemplate.postForEntity(baseUrl("/accounts"),
new CreateAccountRequest(customerId, "My 1 Account", "", initialFromAccountBalance),
CreateAccountResponse.class);
final String fromAccountId = fromAccount.getAccountId();
CreateAccountResponse toAccount = getAuthenticatedRestTemplate().postForEntity(baseUrl("/accounts"),
new CreateAccountRequest("00000000-00000000", "My 2 Account", "", initialToAccountBalance),
CreateAccountResponse toAccount = authenticatedRestTemplate.postForEntity(baseUrl("/accounts"),
new CreateAccountRequest(customerId, "My 2 Account", "", initialToAccountBalance),
CreateAccountResponse.class);
String toAccountId = toAccount.getAccountId();
@@ -48,46 +53,39 @@ public abstract class AbstractRestAPITest {
Assert.assertNotNull(fromAccountId);
Assert.assertNotNull(toAccountId);
assertAccountBalance(fromAccountId, initialFromAccountBalance);
assertAccountBalance(toAccountId, initialToAccountBalance);
assertAccountBalance(authenticatedRestTemplate, fromAccountId, initialFromAccountBalance);
assertAccountBalance(authenticatedRestTemplate, toAccountId, initialToAccountBalance);
final CreateMoneyTransferResponse moneyTransfer = getAuthenticatedRestTemplate().postForEntity(baseUrl("/transfers"),
final CreateMoneyTransferResponse moneyTransfer = authenticatedRestTemplate.postForEntity(baseUrl("/transfers"),
new CreateMoneyTransferRequest(fromAccountId, toAccountId, amountToTransfer, ""),
CreateMoneyTransferResponse.class);
assertAccountBalance(fromAccountId, finalFromAccountBalance);
assertAccountBalance(toAccountId, finalToAccountBalance);
assertAccountBalance(authenticatedRestTemplate, fromAccountId, finalFromAccountBalance);
assertAccountBalance(authenticatedRestTemplate, toAccountId, finalToAccountBalance);
eventually(
new Producer<AccountHistoryResponse>() {
@Override
public CompletableFuture<AccountHistoryResponse> produce() {
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/accounts/" + fromAccountId + "/history"),
AccountHistoryResponse.class));
}
},
new Verifier<AccountHistoryResponse>() {
@Override
public void verify(AccountHistoryResponse accountHistoryResponse) {
Optional<AccountHistoryEntry> first = accountHistoryResponse.getTransactionsHistory().stream().filter( ahe -> ahe.getEntryType() == AccountHistoryEntry.EntryType.transaction && ((AccountTransactionInfo)ahe).getTransactionId().equals(moneyTransfer.getMoneyTransferId())).findFirst();
() -> CompletableFuture.completedFuture(authenticatedRestTemplate.getForEntity(baseUrl("/accounts/" + fromAccountId + "/history"),
AccountHistoryResponse.class)),
accountHistoryResponse -> {
Optional<AccountHistoryEntry> first = accountHistoryResponse.getTransactionsHistory().stream().filter(ahe -> ahe.getEntryType() == AccountHistoryEntry.EntryType.transaction && ((AccountTransactionInfo) ahe).getTransactionId().equals(moneyTransfer.getMoneyTransferId())).findFirst();
assertTrue(first.isPresent());
assertTrue(first.isPresent());
AccountTransactionInfo ti = (AccountTransactionInfo)first.get();
AccountTransactionInfo ti = (AccountTransactionInfo) first.get();
assertEquals(fromAccountId, ti.getFromAccountId());
assertEquals(toAccountId, ti.getToAccountId());
assertEquals(toAccountId, ti.getToAccountId());
assertEquals(fromAccountId, ti.getFromAccountId());
assertEquals(toCents(amountToTransfer).longValue(), ti.getAmount());
}
assertEquals(fromAccountId, ti.getFromAccountId());
assertEquals(toAccountId, ti.getToAccountId());
assertEquals(toAccountId, ti.getToAccountId());
assertEquals(fromAccountId, ti.getFromAccountId());
assertEquals(toCents(amountToTransfer).longValue(), ti.getAmount());
});
}
@Test
public void shouldCreateAccountsAndGetByCustomer() {
public void shouldCreateAndDeleteAccountsAndGetByCustomer() {
BigDecimal initialFromAccountBalance = new BigDecimal(500);
CustomerInfo customerInfo = generateCustomerInfo();
AuthenticatedRestTemplate authenticatedRestTemplate = getAuthenticatedRestTemplate(customerInfo.getUserCredentials());
final CustomerResponse customerResponse = getRestTemplate().postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
final String customerId = customerResponse.getId();
@@ -97,7 +95,7 @@ public abstract class AbstractRestAPITest {
getCustomersTestUtils().assertCustomerResponse(customerId, customerInfo);
final CreateAccountResponse account = getAuthenticatedRestTemplate().postForEntity(baseUrl("/accounts"),
final CreateAccountResponse account = authenticatedRestTemplate.postForEntity(baseUrl("/accounts"),
new CreateAccountRequest(customerId, "My 1 Account", "", initialFromAccountBalance),
CreateAccountResponse.class);
@@ -105,27 +103,26 @@ public abstract class AbstractRestAPITest {
Assert.assertNotNull(accountId);
assertAccountBalance(accountId, initialFromAccountBalance);
assertAccountBalance(authenticatedRestTemplate, accountId, initialFromAccountBalance);
eventually(
new Producer<GetAccountsResponse>() {
@Override
public CompletableFuture<GetAccountsResponse> produce() {
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/customers/"+customerId+"/accounts"),
GetAccountsResponse.class));
}
},
new Verifier<GetAccountsResponse>() {
@Override
public void verify(GetAccountsResponse accountResponses) {
assertTrue(accountResponses.getAccounts().stream().filter(acc -> acc.getAccountId().equals(accountId)).findFirst().isPresent());
}
});
() -> CompletableFuture.completedFuture(authenticatedRestTemplate.getForEntity(baseUrl("/customers/" + customerId + "/accounts"),
GetAccountsResponse.class)),
accountResponses -> assertTrue(accountResponses.getAccounts().stream().filter(acc -> acc.getAccountId().equals(accountId)).findFirst().isPresent()));
authenticatedRestTemplate.deleteEntity(baseUrl("/accounts/" + accountId),
DeleteAccountResponse.class);
eventually(
() -> CompletableFuture.completedFuture(authenticatedRestTemplate.getForEntity(baseUrl("/customers/" + customerId + "/accounts"),
GetAccountsResponse.class)),
accountResponses -> assertFalse(accountResponses.getAccounts().stream().filter(acc -> acc.getAccountId().equals(accountId)).findFirst().isPresent()));
}
@Test
public void shouldCreateCustomersAndAddToAccount() {
CustomerInfo customerInfo = generateCustomerInfo();
AuthenticatedRestTemplate authenticatedRestTemplate = getAuthenticatedRestTemplate(customerInfo.getUserCredentials());
final CustomerResponse customerResponse = getRestTemplate().postForEntity(baseUrl("/customers"), customerInfo, CustomerResponse.class).getBody();
final String customerId = customerResponse.getId();
@@ -137,55 +134,41 @@ public abstract class AbstractRestAPITest {
ToAccountInfo toAccountInfo = generateToAccountInfo();
getAuthenticatedRestTemplate().postForEntity(baseUrl("/customers/" + customerId + "/toaccounts"),
authenticatedRestTemplate.postForEntity(baseUrl("/customers/" + customerId + "/toaccounts"),
toAccountInfo,
null);
assertToAccountsContains(customerId, toAccountInfo);
assertToAccountsContains(customerId, authenticatedRestTemplate, toAccountInfo);
}
private BigDecimal toCents(BigDecimal dollarAmount) {
return dollarAmount.multiply(new BigDecimal(100));
}
private void assertAccountBalance(final String fromAccountId, final BigDecimal expectedBalanceInDollars) {
private void assertAccountBalance(AuthenticatedRestTemplate authenticatedRestTemplate, final String fromAccountId, final BigDecimal expectedBalanceInDollars) {
final BigDecimal inCents = toCents(expectedBalanceInDollars);
eventually(
new Producer<GetAccountResponse>() {
@Override
public CompletableFuture<GetAccountResponse> produce() {
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/accounts/" + fromAccountId),
GetAccountResponse.class));
}
},
new Verifier<GetAccountResponse>() {
@Override
public void verify(GetAccountResponse accountInfo) {
assertEquals(fromAccountId, accountInfo.getAccountId());
assertEquals(inCents, accountInfo.getBalance());
}
() -> CompletableFuture.completedFuture(authenticatedRestTemplate.getForEntity(baseUrl("/accounts/" + fromAccountId),
GetAccountResponse.class)),
accountInfo -> {
assertEquals(fromAccountId, accountInfo.getAccountId());
assertEquals(inCents, accountInfo.getBalance());
});
}
private void assertToAccountsContains(final String customerId, final ToAccountInfo toAccountInfo) {
private void assertToAccountsContains(final String customerId, AuthenticatedRestTemplate authenticatedRestTemplate, final ToAccountInfo toAccountInfo) {
eventually(
new Producer<QuerySideCustomer>() {
@Override
public CompletableFuture<QuerySideCustomer> produce() {
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/customers/" + customerId),
QuerySideCustomer.class));
}
},
new Verifier<QuerySideCustomer>() {
@Override
public void verify(QuerySideCustomer customerResponse) {
assertEquals(customerId, customerResponse.getId());
assertTrue(customerResponse.getToAccounts().values().stream().anyMatch(t -> t.equals(toAccountInfo)));
}
() -> CompletableFuture.completedFuture(authenticatedRestTemplate.getForEntity(baseUrl("/customers/" + customerId),
QuerySideCustomer.class)),
customerResponse -> {
assertEquals(customerId, customerResponse.getId());
assertTrue(customerResponse.getToAccounts().values().stream().anyMatch(t -> t.equals(toAccountInfo)));
});
}
public abstract AuthenticatedRestTemplate getAuthenticatedRestTemplate();
public AuthenticatedRestTemplate getAuthenticatedRestTemplate(UserCredentials userCredentials) {
return new AuthenticatedRestTemplate(getRestTemplate(), userCredentials);
}
public abstract RestTemplate getRestTemplate();

View File

@@ -1,21 +1,25 @@
package net.chrisrichardson.eventstorestore.javaexamples.testutil;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.UserCredentials;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
public class AuthenticatedRestTemplate {
private RestTemplate restTemplate;
private UserCredentials userCredentials;
public AuthenticatedRestTemplate(RestTemplate restTemplate) {
public AuthenticatedRestTemplate(RestTemplate restTemplate, UserCredentials userCredentials) {
this.restTemplate = restTemplate;
this.userCredentials = userCredentials;
}
public <T> T getForEntity(String url, Class<T> clazz) {
return BasicAuthUtils.doBasicAuthenticatedRequest(restTemplate,
url,
HttpMethod.GET,
clazz);
clazz,
userCredentials);
}
public <T> T postForEntity(String url, Object requestObject, Class<T> clazz) {
@@ -23,7 +27,17 @@ public class AuthenticatedRestTemplate {
url,
HttpMethod.POST,
clazz,
requestObject
requestObject,
userCredentials
);
}
public <T> T deleteEntity(String url, Class<T> clazz) {
return BasicAuthUtils.doBasicAuthenticatedRequest(restTemplate,
url,
HttpMethod.DELETE,
clazz,
userCredentials
);
}
}

View File

@@ -1,5 +1,6 @@
package net.chrisrichardson.eventstorestore.javaexamples.testutil;
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.UserCredentials;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.http.*;
import org.springframework.util.Assert;
@@ -12,10 +13,10 @@ import java.nio.charset.Charset;
*/
public class BasicAuthUtils {
public static HttpHeaders basicAuthHeaders(String username) {
public static HttpHeaders basicAuthHeaders(UserCredentials userCredentials) {
return new HttpHeaders() {
{
String auth = username + ":";
String auth = userCredentials.getEmail() + ":" + userCredentials.getPassword();
byte[] encodedAuth = Base64.encodeBase64(
auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
@@ -24,16 +25,16 @@ public class BasicAuthUtils {
};
}
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType) {
return doBasicAuthenticatedRequest(restTemplate, url, httpMethod, responseType, null);
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType, UserCredentials userCredentials) {
return doBasicAuthenticatedRequest(restTemplate, url, httpMethod, responseType, null, userCredentials);
}
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType, Object requestObject) {
public static <T> T doBasicAuthenticatedRequest(RestTemplate restTemplate, String url, HttpMethod httpMethod, Class<T> responseType, Object requestObject, UserCredentials userCredentials) {
HttpEntity httpEntity;
if (requestObject != null) {
httpEntity = new HttpEntity<>(requestObject, BasicAuthUtils.basicAuthHeaders("test_user@mail.com"));
httpEntity = new HttpEntity<>(requestObject, BasicAuthUtils.basicAuthHeaders(userCredentials));
} else {
httpEntity = new HttpEntity(BasicAuthUtils.basicAuthHeaders("test_user@mail.com"));
httpEntity = new HttpEntity(BasicAuthUtils.basicAuthHeaders(userCredentials));
}
ResponseEntity<T> responseEntity = restTemplate.exchange(url,

View File

@@ -8,9 +8,6 @@ import java.util.concurrent.CompletableFuture;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
/**
* Created by popikyardo on 02.03.16.
*/
public class CustomersTestUtils {
private RestTemplate restTemplate;
@@ -22,39 +19,31 @@ public class CustomersTestUtils {
}
public void assertCustomerResponse(final String customerId, final CustomerInfo customerInfo) {
AuthenticatedRestTemplate art = new AuthenticatedRestTemplate(restTemplate);
AuthenticatedRestTemplate art = new AuthenticatedRestTemplate(restTemplate, customerInfo.getUserCredentials());
eventually(
new Producer<QuerySideCustomer>() {
@Override
public CompletableFuture<QuerySideCustomer> produce() {
return CompletableFuture.completedFuture(art.getForEntity(customersBaseUrl + customerId, QuerySideCustomer.class));
}
},
new Verifier<QuerySideCustomer>() {
@Override
public void verify(QuerySideCustomer querySideCustomer) {
Assert.assertEquals(customerId, querySideCustomer.getId());
assertQuerySideCustomerEqualscCustomerInfo(querySideCustomer, customerInfo);
}
() -> CompletableFuture.completedFuture(art.getForEntity(customersBaseUrl + customerId, QuerySideCustomer.class)),
querySideCustomer -> {
Assert.assertEquals(customerId, querySideCustomer.getId());
assertQuerySideCustomerEqualscCustomerInfo(querySideCustomer, customerInfo);
});
}
public void assertQuerySideCustomerEqualscCustomerInfo(QuerySideCustomer querySideCustomer, CustomerInfo customerInfo) {
Assert.assertEquals(querySideCustomer.getName(), customerInfo.getName());
Assert.assertEquals(querySideCustomer.getEmail(), customerInfo.getEmail());
Assert.assertEquals(querySideCustomer.getEmail(), customerInfo.getUserCredentials().getEmail());
Assert.assertEquals(querySideCustomer.getPhoneNumber(), customerInfo.getPhoneNumber());
Assert.assertEquals(querySideCustomer.getSsn(), customerInfo.getSsn());
Assert.assertEquals(querySideCustomer.getAddress(), customerInfo.getAddress());
}
public static CustomerInfo generateCustomerInfo() {
return generateCustomerInfo("current@email.com");
return generateCustomerInfo(uniqueEmail());
}
public static CustomerInfo generateCustomerInfo(String email) {
return new CustomerInfo(
new Name("John", "Doe"),
email,
new UserCredentials(email, "simple_password"),
"000-00-0000",
"1-111-111-1111",
new Address("street 1",
@@ -68,4 +57,8 @@ public class CustomersTestUtils {
public static ToAccountInfo generateToAccountInfo() {
return new ToAccountInfo("11111111-11111111", "New Account", "John Doe", "");
}
private static String uniqueEmail() {
return System.currentTimeMillis() + "@email.com";
}
}

View File

@@ -56,7 +56,7 @@ public class TestUtil {
public static <T> void eventually(Producer<T> producer, Verifier<T> predicate) {
Throwable laste = null;
for (int i = 0; i < 30 ; i++) {
for (int i = 0; i < 50 ; i++) {
try {
T x = producer.produce().get(30, TimeUnit.SECONDS);
predicate.verify(x);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -12,8 +12,8 @@
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap.min.css">
<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap-theme.min.css"><link href="/style.6d7a32b1405ea1bb2bdf.css" rel="stylesheet"></head>
<body><div id="root"></div><script src="/manifest.087a5454fa0c34daf3c9.js"></script><script src="/vendor.c882d66445aebc52c21b.js"></script><script src="/style.6d7a32b1405ea1bb2bdf.js"></script><script src="/app.fcbedf54f0345474ccc1.js"></script><script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/latest/css/bootstrap-theme.min.css"><link href="/style.b588c60da106277d78c8.css" rel="stylesheet"></head>
<body><div id="root"></div><script src="/manifest.15c5d6172500c36b3280.js"></script><script src="/vendor.85781b28c9410377534e.js"></script><script src="/style.b588c60da106277d78c8.js"></script><script src="/app.497ed0e9fa8411cbbf1d.js"></script><script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
@@ -27,5 +27,5 @@
ga('send', 'pageview');
</script><!--{"files":{"publicPath":"/","chunks":{"manifest":{"size":0,"entry":"/manifest.087a5454fa0c34daf3c9.js","hash":"087a5454fa0c34daf3c9","css":[]},"vendor":{"size":1670874,"entry":"/vendor.c882d66445aebc52c21b.js","hash":"c882d66445aebc52c21b","css":[]},"style":{"size":122,"entry":"/style.6d7a32b1405ea1bb2bdf.js","hash":"6d7a32b1405ea1bb2bdf","css":["/style.6d7a32b1405ea1bb2bdf.css"]},"app":{"size":352314,"entry":"/app.fcbedf54f0345474ccc1.js","hash":"fcbedf54f0345474ccc1","css":[]}},"js":["/manifest.087a5454fa0c34daf3c9.js","/vendor.c882d66445aebc52c21b.js","/style.6d7a32b1405ea1bb2bdf.js","/app.fcbedf54f0345474ccc1.js"],"css":["/style.6d7a32b1405ea1bb2bdf.css"]},"options":{"template":"/Users/andrew/dev/clients/ES/code/event-sourcing-examples/js-frontend/node_modules/html-webpack-plugin/lib/loader.js!/Users/andrew/dev/clients/ES/code/event-sourcing-examples/js-frontend/public/index.ejs","filename":"index.html","hash":false,"inject":false,"compile":true,"favicon":false,"minify":false,"cache":true,"showErrors":true,"chunks":"all","excludeChunks":[],"title":"Money Transfer App","xhtml":false,"description":"ES Money Transfer App","appMountId":"root","googleAnalytics":{"trackingId":"UA-XXXX-XX","pageViewOnLoad":true},"mobile":true}}--></body>
</script><!--{"files":{"publicPath":"/","chunks":{"manifest":{"size":0,"entry":"/manifest.15c5d6172500c36b3280.js","hash":"15c5d6172500c36b3280","css":[]},"vendor":{"size":1654379,"entry":"/vendor.85781b28c9410377534e.js","hash":"85781b28c9410377534e","css":[]},"style":{"size":122,"entry":"/style.b588c60da106277d78c8.js","hash":"b588c60da106277d78c8","css":["/style.b588c60da106277d78c8.css"]},"app":{"size":350105,"entry":"/app.497ed0e9fa8411cbbf1d.js","hash":"497ed0e9fa8411cbbf1d","css":[]}},"js":["/manifest.15c5d6172500c36b3280.js","/vendor.85781b28c9410377534e.js","/style.b588c60da106277d78c8.js","/app.497ed0e9fa8411cbbf1d.js"],"css":["/style.b588c60da106277d78c8.css"]},"options":{"template":"/Users/andrew/dev/clients/ES/code/event-sourcing-examples/js-frontend/node_modules/html-webpack-plugin/lib/loader.js!/Users/andrew/dev/clients/ES/code/event-sourcing-examples/js-frontend/public/index.ejs","filename":"index.html","hash":false,"inject":false,"compile":true,"favicon":false,"minify":false,"cache":true,"showErrors":true,"chunks":"all","excludeChunks":[],"title":"Money Transfer App","xhtml":false,"description":"ES Money Transfer App","appMountId":"root","googleAnalytics":{"trackingId":"UA-XXXX-XX","pageViewOnLoad":true},"mobile":true}}--></body>
</html>

View File

@@ -76,7 +76,7 @@
/******/ script.charset = 'utf-8';
/******/ script.async = true;
/******/
/******/ script.src = __webpack_require__.p + "" + {"0":"fcbedf54f0345474ccc1","1":"6d7a32b1405ea1bb2bdf","2":"c882d66445aebc52c21b"}[chunkId] + ".js";
/******/ script.src = __webpack_require__.p + "" + {"0":"497ed0e9fa8411cbbf1d","1":"b588c60da106277d78c8","2":"85781b28c9410377534e"}[chunkId] + ".js";
/******/ head.appendChild(script);
/******/ }
/******/ };
@@ -92,4 +92,4 @@
/******/ })
/************************************************************************/
/******/ ([]);
//# sourceMappingURL=manifest.087a5454fa0c34daf3c9.js.map
//# sourceMappingURL=manifest.15c5d6172500c36b3280.js.map

View File

@@ -1 +1 @@
{"version":3,"sources":["webpack:///webpack/bootstrap edd3ecd6d3192330eb69?"],"names":[],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAQ,oBAAoB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,uBAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,YAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,uDAA+C,iFAAiF;AAChI;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA","file":"manifest.087a5454fa0c34daf3c9.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n \t\twhile(callbacks.length)\n \t\t\tcallbacks.shift().call(null, __webpack_require__);\n \t\tif(moreModules[0]) {\n \t\t\tinstalledModules[0] = 0;\n \t\t\treturn __webpack_require__(0);\n \t\t}\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// \"0\" means \"already loaded\"\n \t// Array means \"loading\", array contains callbacks\n \tvar installedChunks = {\n \t\t3:0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n \t\t// \"0\" is the signal for \"already loaded\"\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn callback.call(null, __webpack_require__);\n\n \t\t// an array means \"currently loading\".\n \t\tif(installedChunks[chunkId] !== undefined) {\n \t\t\tinstalledChunks[chunkId].push(callback);\n \t\t} else {\n \t\t\t// start chunk loading\n \t\t\tinstalledChunks[chunkId] = [callback];\n \t\t\tvar head = document.getElementsByTagName('head')[0];\n \t\t\tvar script = document.createElement('script');\n \t\t\tscript.type = 'text/javascript';\n \t\t\tscript.charset = 'utf-8';\n \t\t\tscript.async = true;\n\n \t\t\tscript.src = __webpack_require__.p + \"\" + {\"0\":\"fcbedf54f0345474ccc1\",\"1\":\"6d7a32b1405ea1bb2bdf\",\"2\":\"c882d66445aebc52c21b\"}[chunkId] + \".js\";\n \t\t\thead.appendChild(script);\n \t\t}\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap edd3ecd6d3192330eb69\n **/"],"sourceRoot":""}
{"version":3,"sources":["webpack:///webpack/bootstrap ee51b0824572a7d4b9ec?"],"names":[],"mappings":";AAAA;AACA;AACA;AACA;AACA;AACA;AACA,gBAAQ,oBAAoB;AAC5B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,uBAAe;AACf;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,YAAI;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,uDAA+C,iFAAiF;AAChI;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA","file":"manifest.15c5d6172500c36b3280.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, callbacks = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId])\n \t\t\t\tcallbacks.push.apply(callbacks, installedChunks[chunkId]);\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);\n \t\twhile(callbacks.length)\n \t\t\tcallbacks.shift().call(null, __webpack_require__);\n \t\tif(moreModules[0]) {\n \t\t\tinstalledModules[0] = 0;\n \t\t\treturn __webpack_require__(0);\n \t\t}\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// \"0\" means \"already loaded\"\n \t// Array means \"loading\", array contains callbacks\n \tvar installedChunks = {\n \t\t3:0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n \t// This file contains only the entry chunk.\n \t// The chunk loading function for additional chunks\n \t__webpack_require__.e = function requireEnsure(chunkId, callback) {\n \t\t// \"0\" is the signal for \"already loaded\"\n \t\tif(installedChunks[chunkId] === 0)\n \t\t\treturn callback.call(null, __webpack_require__);\n\n \t\t// an array means \"currently loading\".\n \t\tif(installedChunks[chunkId] !== undefined) {\n \t\t\tinstalledChunks[chunkId].push(callback);\n \t\t} else {\n \t\t\t// start chunk loading\n \t\t\tinstalledChunks[chunkId] = [callback];\n \t\t\tvar head = document.getElementsByTagName('head')[0];\n \t\t\tvar script = document.createElement('script');\n \t\t\tscript.type = 'text/javascript';\n \t\t\tscript.charset = 'utf-8';\n \t\t\tscript.async = true;\n\n \t\t\tscript.src = __webpack_require__.p + \"\" + {\"0\":\"497ed0e9fa8411cbbf1d\",\"1\":\"b588c60da106277d78c8\",\"2\":\"85781b28c9410377534e\"}[chunkId] + \".js\";\n \t\t\thead.appendChild(script);\n \t\t}\n \t};\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap ee51b0824572a7d4b9ec\n **/"],"sourceRoot":""}

View File

@@ -438,4 +438,4 @@ body {
h1 {
margin-top: .5em;
}
/*# sourceMappingURL=style.6d7a32b1405ea1bb2bdf.css.map*/
/*# sourceMappingURL=style.b588c60da106277d78c8.css.map*/

View File

@@ -1 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"style.6d7a32b1405ea1bb2bdf.css","sourceRoot":""}
{"version":3,"sources":[],"names":[],"mappings":"","file":"style.b588c60da106277d78c8.css","sourceRoot":""}

View File

@@ -3,20 +3,20 @@ webpackJsonp([1,3],{
/***/ 0:
/***/ function(module, exports, __webpack_require__) {
__webpack_require__(614);
module.exports = __webpack_require__(618);
__webpack_require__(612);
module.exports = __webpack_require__(616);
/***/ },
/***/ 614:
/***/ 612:
/***/ function(module, exports) {
// removed by extract-text-webpack-plugin
/***/ },
/***/ 618:
/***/ 616:
/***/ function(module, exports) {
// removed by extract-text-webpack-plugin
@@ -24,4 +24,4 @@ webpackJsonp([1,3],{
/***/ }
});
//# sourceMappingURL=style.6d7a32b1405ea1bb2bdf.js.map
//# sourceMappingURL=style.b588c60da106277d78c8.js.map

View File

@@ -1 +1 @@
{"version":3,"sources":["webpack:///./~/react-select/dist/react-select.css?","webpack:///./src/main.less?"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,0C;;;;;;;ACAA,0C","file":"style.6d7a32b1405ea1bb2bdf.js","sourcesContent":["// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./~/react-select/dist/react-select.css\n ** module id = 614\n ** module chunks = 1\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/main.less\n ** module id = 618\n ** module chunks = 1\n **/"],"sourceRoot":""}
{"version":3,"sources":["webpack:///./~/react-select/dist/react-select.css?","webpack:///./src/main.less?"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,0C;;;;;;;ACAA,0C","file":"style.b588c60da106277d78c8.js","sourcesContent":["// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./~/react-select/dist/react-select.css\n ** module id = 612\n ** module chunks = 1\n **/","// removed by extract-text-webpack-plugin\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/main.less\n ** module id = 616\n ** module chunks = 1\n **/"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,127 +0,0 @@
import del from "del";
import path from "path";
import gulp from "gulp";
import open from "open";
import gulpLoadPlugins from "gulp-load-plugins";
import packageJson from "./package.json";
import runSequence from "run-sequence";
import webpack from "webpack";
import webpackConfig from "./webpack.config";
import WebpackDevServer from "webpack-dev-server";
const PORT = process.env.PORT || 3000;
const $ = gulpLoadPlugins({camelize: true});
// MyAccounts tasks
gulp.task('serve', () => runSequence('serve:clean', 'serve:index', 'serve:start'));
gulp.task('dist', () => runSequence('dist:clean', 'dist:build', 'dist:index'));
gulp.task('clean', ['dist:clean', 'serve:clean']);
gulp.task('open', () => open('http://localhost:3000'));
gulp.task('export', () => runSequence(/*'dist:clean', 'dist:build', 'dist:index',*/ 'export:clean', 'export:copy'));
// Remove all built files
gulp.task('serve:clean', cb => del('build', {dot: true}, cb));
gulp.task('dist:clean', cb => del(['dist', 'dist-intermediate'], {dot: true}, cb));
gulp.task('export:clean', cb => del(['../prebuilt-web-client/**'], {dot: true, force: true}, cb));
// Copy static files across to our final directory
gulp.task('serve:static', () =>
gulp.src([
'src/static/**'
])
.pipe($.changed('build'))
.pipe(gulp.dest('build'))
.pipe($.size({title: 'static'}))
);
gulp.task('dist:static', () =>
gulp.src([
'src/static/**'
])
.pipe(gulp.dest('dist'))
.pipe($.size({title: 'static'}))
);
gulp.task('export:copy', () => {
return gulp.src(['dist/**'])
.pipe(gulp.dest('../prebuilt-web-client'));
});
// Copy our index file and inject css/script imports for this build
gulp.task('serve:index', () => {
return gulp
.src('src/index.html')
.pipe($.injectString.after('<!-- inject:app:js -->', '<script src="generated/main.js"></script>'))
.pipe(gulp.dest('build'));
});
// Copy our index file and inject css/script imports for this build
gulp.task('dist:index', () => {
const app = gulp
.src(["*.{css,js}"], {cwd: 'dist-intermediate/generated'})
.pipe(gulp.dest('dist'));
// Build the index.html using the names of compiled files
return gulp.src('src/index.html')
.pipe($.inject(app, {
ignorePath: 'dist',
starttag: '<!-- inject:app:{{ext}} -->'
}))
.on("error", $.util.log)
.pipe(gulp.dest('dist'));
});
// Start a livereloading development server
gulp.task('serve:start', ['serve:static'], () => {
const config = webpackConfig(true, 'build', PORT);
// https://webpack.github.io/docs/webpack-dev-server.html
return new WebpackDevServer(webpack(config), {
contentBase: 'build',
publicPath: config.output.publicPath,
watchDelay: 100,
historyApiFallback: true,
//proxy: {
// "*": "http://localhost:8080"
//}
proxy: {
'/user*' : {
target: 'http://localhost:8080'
},
'/login' : {
target: 'http://localhost:8080'
},
'/customers*' : {
target: 'http://localhost:8080'
},
'/accounts*' : {
target: 'http://localhost:8080'
},
'/transfers*' : {
target: 'http://localhost:8080'
}
}
})
.listen(PORT, '0.0.0.0', (err) => {
if (err) throw new $.util.PluginError('webpack-dev-server', err);
$.util.log(`[${packageJson.name} serve]`, `Listening at 0.0.0.0:${PORT}`);
});
});
// Create a distributable package
gulp.task('dist:build', ['dist:static'], cb => {
const config = webpackConfig(false, 'dist-intermediate');
webpack(config, (err, stats) => {
if (err) throw new $.util.PluginError('dist', err);
$.util.log(`[${packageJson.name} dist]`, stats.toString({colors: true}));
cb();
});
});

View File

@@ -18,7 +18,6 @@
"autoprefixer-loader": "^2.0.0",
"babel-plugin-add-module-exports": "^0.1.2",
"babel-plugin-transform-runtime": "^6.12.0",
"babel-cli": "^6.7.7",
"babel-core": "^6.10.4",
"babel-eslint": "^4.1.6",
@@ -34,13 +33,6 @@
"extract-text-webpack-plugin": "^0.8.1",
"file-loader": "^0.8.4",
"fill-range": "^2.2.2",
"gulp": "^3.9.0",
"gulp-changed": "^1.2.1",
"gulp-inject": "^1.3.1",
"gulp-inject-string": "0.0.2",
"gulp-load-plugins": "^0.10.0",
"gulp-size": "^1.2.1",
"gulp-util": "^3.0.5",
"html-webpack-plugin": "^2.22.0",
"json-loader": "^0.5.4",
"less": "^2.5.3",
@@ -65,13 +57,11 @@
"invariant": "^2.1.1",
"isomorphic-fetch": "^2.2.1",
"js-cookie": "^2.1.0",
"object-pick": "^0.1.1",
"querystring": "^0.2.0",
"react": "^0.14.7",
"react-bootstrap": "^0.28.3",
"react-dom": "^0.14.0",
"react-loader": "^2.4.0",
"react-pacomo": "^0.5.1",
"react-redux": "^4.4.0",
"react-router": "^2.7.0",
"react-router-bootstrap": "^0.20.1",
@@ -83,7 +73,6 @@
"redux-logger": "^2.6.0",
"redux-multi": "^0.1.91",
"redux-router": "^1.0.0-beta7",
"redux-thunk": "^1.0.3",
"uniloc": "^0.2.0"
"redux-thunk": "^1.0.3"
}
}

View File

@@ -8,6 +8,7 @@ import { Provider, connect} from "react-redux";
import thunk from "redux-thunk";
import createLogger from 'redux-logger';
import { Route, IndexRoute, Link, IndexLink } from "react-router";
import { RouterContext } from 'react-router';
import { ReduxRouter} from "redux-router";
import { createHistory, createHashHistory, createMemoryHistory } from "history";
import { pushState, routerStateReducer, reduxReactRouter as clientRouter} from "redux-router";
@@ -16,6 +17,7 @@ import { reduxReactRouter as serverRouter } from "redux-router/server";
import mainReducer from './reducers';
import { configure as endpointsConfig } from './actions/configure';
import { visitLocation } from './actions/navigate';
import { requireAuthentication } from './components/AuthComponent';
import Container from "./components/partials/Container";
import MyAccounts from "./views/MyAccounts";
@@ -25,7 +27,8 @@ import SignUp from "./views/SignUp";
class App extends React.Component {
render() {
return (<Container>
return (
<Container>
{this.props.children}
</Container>);
}
@@ -38,12 +41,19 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
router: routerStateReducer
});
let dispatch = null;
const onEnter = (nextState) => {
const { location } = nextState;
dispatch && dispatch(visitLocation(location));
};
const routes = (
<Route path="/" component={App}>
<IndexRoute component={requireAuthentication(MyAccounts)} />
<Route path="signin" component={SignIn} />
<Route path="register" component={SignUp} />
<Route path="account/:accountId" component={requireAuthentication(Account)} />
<Route path="/" component={ App }>
<IndexRoute component={ requireAuthentication(MyAccounts) } />
<Route path="signin" component={ SignIn } onEnter={ onEnter } />
<Route path="register" component={ SignUp } onEnter={ onEnter } />
<Route path="account/:accountId" component={ requireAuthentication(Account) } />
</Route>
);
@@ -60,6 +70,7 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
})
)(createStore)(reducer);
dispatch = store.dispatch;
/**
* The React Router 1.0 routes for both the server and the client.
@@ -85,13 +96,11 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
},
handleLoginResponse: function(resp) {
debugger;
return resp.data;
},
handleAccountUpdateResponse: function(resp) {
debugger;
return resp.data;
},

View File

@@ -1,36 +0,0 @@
import React, {PropTypes} from 'react'
import ApplicationLayout from './components/ApplicationLayout'
import DocumentContainer from './containers/DocumentContainer'
import DocumentListContainer from './containers/DocumentListContainer'
// Application is the root component for your application.
export default function Application(props) {
return (
<ApplicationLayout locationName={props.state.navigation.location.name}>
{selectChildContainer(props)}
</ApplicationLayout>
)
}
Application.propTypes = {
state: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
}
// Define this as a separate function to allow us to use the switch statement
// with `return` statements instead of `break`
const selectChildContainer = props => {
const location = props.state.navigation.location
let child
switch (location.name) {
case 'documentEdit':
child = <DocumentContainer {...props} id={location.options.id} />
case 'documentList':
return <DocumentListContainer {...props} id={location.options.id}>{child}</DocumentListContainer>
default:
return "Not Found"
}
}

View File

@@ -1,27 +0,0 @@
/**
* Created by andrew on 25/02/16.
*/
export function configureStart({...props} = {}) {
return {
...props,
type: T.AUTH.CONFIGURE_START
};
}
export function configureComplete({config, ...props} = {}) {
return {
...props,
type: T.AUTH.CONFIGURE_COMPLETE,
config
};
}
export function configureError({errors, ...props} = {}) {
return {
...props,
type: T.AUTH.CONFIGURE_ERROR,
error: errors
};
}

View File

@@ -4,7 +4,6 @@
import T from '../constants/ACTION_TYPES';
import { makeActionCreator } from '../utils/actions';
import * as U from '../utils/sessionStorage';
import { apiGetCurrentUser } from '../utils/api';
import { entityReceived } from './entities';

View File

@@ -1,48 +1,24 @@
/**
* Created by andrew on 26/02/16.
*/
import * as C from "../utils/constants";
import {
authenticate,
authenticateStart,
authenticateComplete,
authenticateError
} from "./authenticate";
import {
retrieveData,
} from "../utils/sessionStorage";
import {applyConfig} from "../utils/clientSettings";
//import {
// showFirstTimeLoginSuccessModal,
// showFirstTimeLoginErrorModal,
// showPasswordResetSuccessModal,
// showPasswordResetErrorModal
//} from "./ui";
import getRedirectInfo from "../utils/parseUrl";
import { pushState } from "redux-router";
import root from '../utils/root';
import { authenticate } from "./authenticate";
import { applyConfig } from "../utils/clientSettings";
export const SET_ENDPOINT_KEYS = "SET_ENDPOINT_KEYS";
export const STORE_CURRENT_ENDPOINT_KEY = "STORE_CURRENT_ENDPOINT_KEY";
export function setEndpointKeys(endpoints, currentEndpointKey, defaultEndpointKey) {
return { type: SET_ENDPOINT_KEYS, endpoints, currentEndpointKey, defaultEndpointKey };
}
export function storeCurrentEndpointKey(currentEndpointKey) {
return { type: STORE_CURRENT_ENDPOINT_KEY, currentEndpointKey };
return {
type: SET_ENDPOINT_KEYS,
endpoints,
currentEndpointKey,
defaultEndpointKey
};
}
export function configure(endpoint={}, settings={}) {
return dispatch => {
return applyConfig({ dispatch, endpoint, settings })
.then(() => {
return dispatch(authenticate());

View File

@@ -26,17 +26,17 @@ export const accountRefCreateComplete = makeActionCreator(T.ACCOUNTS.CREATE_REF_
export const accountRefCreateError = makeActionCreator(T.ACCOUNTS.CREATE_REF_ERROR, 'error');
export const accountRefCreateFormUpdate = makeActionCreator(T.ACCOUNTS.CREATE_REF_FORM_UPDATE, 'key', 'value');
export const accountRequested = makeActionCreator(T.ACCOUNT.SINGLE_START);
export const accountComplete = makeActionCreator(T.ACCOUNT.SINGLE_COMPLETE, 'payload');
export const accountError = makeActionCreator(T.ACCOUNT.SINGLE_ERROR, 'error');
export const accountRequested = makeActionCreator(T.ACCOUNT.SINGLE_START, 'id');
export const accountComplete = makeActionCreator(T.ACCOUNT.SINGLE_COMPLETE, 'id', 'payload');
export const accountError = makeActionCreator(T.ACCOUNT.SINGLE_ERROR, 'id', 'error');
export function accountsList(userId) {
export function accountsList(customerId) {
return dispatch => {
dispatch(accountsListRequested());
return api.apiRetrieveAccounts(userId)
.then(list => {
dispatch(accountsListReceived(list));
return api.apiRetrieveAccounts(customerId)
.then(({ accounts = []}) => {
dispatch(accountsListReceived(accounts));
})
.catch(err => {
dispatch(accountsListError(err));
@@ -45,21 +45,23 @@ export function accountsList(userId) {
};
}
function readUntilChanged(initialData, customerId) {
function readUntilChanged(initialData, promisedFn, leftCalls) {
if (!leftCalls) {
return Promise.reject('Data not changed')
}
const initialDataFlat = root['JSON'].stringify(initialData);
debugger;
return new Promise((rs, rj) => {
setTimeout(() => {
api.apiRetrieveAccounts(customerId)
promisedFn()
.then(data => {
debugger;
if (initialDataFlat == root['JSON'].stringify(data)) {
return readUntilChanged.call(this, data, customerId).then(rs, rj); // Promise
return readUntilChanged.call(this, data, promisedFn, leftCalls - 1).then(rs, rj); // Promise
}
rs(data);
})
.catch(rj)
}, 500);
}, 500 * Math.pow(2, 4 - leftCalls));
})
}
@@ -78,8 +80,9 @@ export function accountCreate(customerId, payload) {
dispatch(authenticate(true));
return accountId;
} else {
return readUntilChanged(data, customerId)
return readUntilChanged(data, () => api.apiRetrieveAccounts(customerId), 4)
.then(() => {
debugger;
dispatch(accountCreateComplete({
id: ''
}));
@@ -130,13 +133,13 @@ export function fetchOwnAccounts(customerId) {
export function fetchAccount(accountId) {
return dispatch => {
dispatch(accountRequested());
dispatch(accountRequested(accountId));
return api.apiRetrieveAccount(accountId)
.then(data => {
dispatch(accountComplete(data));
dispatch(accountComplete(accountId, data));
})
.catch(err => {
dispatch(accountError(err));
dispatch(accountError(accountId, err));
});
};
}
@@ -145,17 +148,21 @@ export const deleteAccountRequested = makeActionCreator(T.ACCOUNT.DELETE_START);
export const deleteAccountComplete = makeActionCreator(T.ACCOUNT.DELETE_COMPLETE);
export const deleteAccountError = makeActionCreator(T.ACCOUNT.DELETE_ERROR);
export function deleteAccount(customerId, accountId) {
export function deleteAccount(customerId, accountId, isRef) {
return dispatch => {
dispatch(deleteAccountRequested());
return api.apiDeleteAccount(accountId)
const deleteApiAction = (isRef ?
api.apiDeleteRefAccount(customerId, accountId) :
api.apiDeleteAccount(customerId, accountId));
return deleteApiAction
.then(data => {
//debugger;
dispatch(deleteAccountComplete());
return Promise.resolve('ok');
dispatch(deleteAccountComplete(data));
return Promise.resolve(data);
})
.catch(err => {
dispatch(deleteAccountError());
dispatch(deleteAccountError(err));
return Promise.reject(err);
})
};
@@ -208,7 +215,7 @@ export const createRefOwnerLookup = lookup => {
export const createRefAccountLookup = customerId => {
return dispatch => {
dispatch(createRefAccountLookupStart());
dispatch(createRefAccountLookupStart(customerId));
return api.apiRetrieveAccounts(customerId)
.then(({ accounts }) => {
const arr = accounts.map(({ accountId, title }) => ({
@@ -256,7 +263,7 @@ export const getTransfers = (accountId) => {
dispatch(getTransfersRequested(accountId));
return api.apiRetrieveTransfers(accountId)
.then(data => {
dispatch(getTransfersComplete(accountId, data.transactionsHistory));
dispatch(getTransfersComplete(accountId, data['transactionsHistory']));
return data;
})
.catch(err => {

View File

@@ -0,0 +1,7 @@
/**
* Created by andrew on 26/02/16.
*/
import T from '../constants/ACTION_TYPES';
import { makeActionCreator } from '../utils/actions';
export const visitLocation = makeActionCreator(T.LOCATION.ENTER, 'location');

View File

@@ -1,29 +0,0 @@
import T from '../constants/ACTION_TYPES'
import ROUTES from '../constants/ROUTES'
// `navigate` is used to facilitate changing routes within another action
// without rendering any other changes first
export function start(name, options) {
return dispatch => {
const currentURI = window.location.hash.substr(1)
const newURI = ROUTES.generate(name, options)
if (currentURI != newURI) {
dispatch({
type: T.NAVIGATION.START,
})
window.location.replace(
window.location.pathname + window.location.search + '#' + newURI
)
}
}
}
export function complete() {
return {
type: T.NAVIGATION.COMPLETE,
location: ROUTES.lookup(window.location.hash.substr(1)),
}
}

View File

@@ -1,23 +1,11 @@
/**
* Created by andrew on 26/02/16.
*/
import {
setCurrentEndpointKey,
getCurrentEndpointKey,
persistUserData
} from "../utils/sessionStorage";
import { entityReceived } from './entities';
import { storeCurrentEndpointKey } from "./configure";
//import { parseResponse } from "../utils/handleFetchResponse";
//import fetch from "../utils/fetch";
import { apiSignIn } from '../utils/api';
import { makeActionCreator } from '../utils/actions';
import T from '../constants/ACTION_TYPES';
//import root from '../utils/root';
import { makeActionCreator } from '../utils/actions';
import { persistUserData } from "../utils/sessionStorage";
import { entityReceived } from './entities';
import { apiSignIn } from '../utils/api';
export const emailSignInFormUpdate = makeActionCreator(T.AUTH.SIGN_IN_FORM_UPDATE, 'key', 'value');
export const emailSignInStart = makeActionCreator(T.AUTH.SIGN_IN_START);

View File

@@ -1,40 +1,18 @@
/**
* Created by andrew on 11/03/16.
*/
import {
getEmailSignInUrl,
setCurrentEndpointKey,
getCurrentEndpointKey
} from "../utils/sessionStorage";
import {destroySession} from "../utils/sessionStorage";
import { entityReceived } from './entities';
import { storeCurrentEndpointKey } from "./configure";
import { parseResponse } from "../utils/handleFetchResponse";
import fetch from "../utils/fetch";
import T from '../constants/ACTION_TYPES';
import { makeActionCreator } from '../utils/actions';
import { destroySession } from "../utils/sessionStorage";
import root from '../utils/root';
export function signOutStart() {
return { type: T.AUTH.SIGN_OUT_START };
}
export function signOutComplete() {
return { type: T.AUTH.SIGN_OUT_COMPLETE };
}
export function signOut() {
return dispatch => {
export const signOutStart = makeActionCreator(T.AUTH.SIGN_OUT_START);
export const signOutComplete = makeActionCreator(T.AUTH.SIGN_OUT_COMPLETE);
export const signOut = () =>
dispatch => {
dispatch(signOutStart());
destroySession();
dispatch(signOutComplete());
};
}
};

View File

@@ -1,48 +1,34 @@
/**
* Created by andrew on 11/03/16.
*/
import {
getEmailSignUpUrl
} from "../utils/sessionStorage";
import { entityReceived } from './entities';
import { storeCurrentEndpointKey } from "./configure";
//import { parseResponse } from "../utils/handleFetchResponse";
import { push } from 'redux-router';
import T from '../constants/ACTION_TYPES';
import { makeActionCreator } from '../utils/actions';
import { apiSignUp } from "../utils/api";
import { emailSignInFormUpdate } from './signIn';
import { push } from 'redux-router';
import T from '../constants/ACTION_TYPES';
export const emailSignUpFormUpdate = makeActionCreator(T.AUTH.SIGN_UP_FORM_UPDATE, 'key', 'value');
export const emailSignUpStart = makeActionCreator(T.AUTH.SIGN_UP_START);
export const emailSignUpComplete = makeActionCreator(T.AUTH.SIGN_UP_COMPLETE, 'user');
export const emailSignUpError = makeActionCreator(T.AUTH.SIGN_UP_ERROR, 'error');
export function emailSignUpFormUpdate(key, value) {
return { type: T.AUTH.SIGN_UP_FORM_UPDATE, key, value };
}
export function emailSignUpStart() {
return { type: T.AUTH.SIGN_UP_START };
}
export function emailSignUpComplete(user) {
return { type: T.AUTH.SIGN_UP_COMPLETE, user };
}
export function emailSignUpError(errors) {
return { type: T.AUTH.SIGN_UP_ERROR, errors };
}
export function emailSignUp(body) {
return dispatch => {
dispatch(emailSignUpStart());
return apiSignUp(body)
.then(({data}) => {
.then(({ data }) => {
dispatch(emailSignUpComplete(data));
const { email } = body;
dispatch(emailSignInFormUpdate('email', email));
dispatch(push('/signin'));
})
.catch(({errors}) => dispatch(emailSignUpError(errors)));
.catch(({ errors }) => {
dispatch(emailSignUpError({
errors
}))
});
};
}

View File

@@ -1,7 +0,0 @@
import redirector from './redirector'
import renderer from './renderer'
export default [
redirector,
renderer,
]

View File

@@ -1,20 +0,0 @@
import * as navigation from '../actions/navigation'
import ROUTES from '../constants/ROUTES'
export default function redirector(state, dispatch) {
const {name, options} = state.navigation.location || {}
const currentURI = window.location.hash.substr(1)
const canonicalURI = name && ROUTES.generate(name, options)
if (canonicalURI && canonicalURI !== currentURI) {
// If the URL entered includes extra `/` characters, or otherwise
// differs from the canonical URL, navigate the user to the
// canonical URL (which will result in `complete` being called again)
dispatch(navigation.start(name, options))
}
else if (name == 'root') {
// If we've hit the root location, redirect the user to the main page
dispatch(navigation.start('documentList'))
}
}

View File

@@ -1,18 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import Application from '../App'
// Store a reference to our application's root DOM node to prevent repeating
// this on every state update
const APP_NODE = document.getElementById('react-app')
export default function renderer(state, dispatch) {
// Don't re-render if we're in the process of navigating to a new page
if (!state.navigation.transitioning) {
ReactDOM.render(
<Application state={state} dispatch={dispatch} />,
APP_NODE
)
}
}

View File

@@ -7,8 +7,8 @@ import { initialize } from "./app";
/**
* Fire-up React Router.
*/
const reactRoot = window.document.getElementById("root");
initialize().then(({ provider }) => {
const reactRoot = window.document.getElementById("root");
ReactDOM.render(provider, reactRoot);
});
@@ -17,6 +17,7 @@ initialize().then(({ provider }) => {
* Detect whether the server-side render has been discarded due to an invalid checksum.
*/
if (process.env.NODE_ENV !== "production") {
const reactRoot = window.document.getElementById("root");
if (!reactRoot.firstChild || !reactRoot.firstChild.attributes ||
!reactRoot.firstChild.attributes["data-react-checksum"]) {
console.error("Server-side React render was discarded. Make sure that your initial render does not contain any client-side code.");

View File

@@ -31,8 +31,12 @@ export class AccountInfo extends React.Component {
const account = entities[accountId];
if (!account || !accountId) {
return (<div title={ `${accountId}` }>{ accountId } <Spinner loaded={false} /></div>);
// {/*return (<Link to={ `/account/${accountId}` }>{ accountId } <Spinner loaded={false} /></Link>)*/}
return (<div className="text-info" title={ `${accountId}` }>Loading.. <Spinner loaded={false} /></div>);
}
const { errors } = account;
if (errors) {
return (<div className="text-danger">{ errors }</div>);
}
const { title, owner } = account;

View File

@@ -1,35 +0,0 @@
//import './ApplicationLayout.less'
import React, {PropTypes} from 'react'
//import { pacomoTransformer } from '../utils/pacomo'
import Link from './Link'
const ApplicationLayout = ({
children,
locationName,
}) =>
(<div>
<nav className='navbar'>
<Link
name='documentList'
className={{
'link': true,
'link-active': locationName == 'documentList' || locationName == 'documentEdit',
}}
>
Documents
</Link>
</nav>
<main className='content'>
{children}
</main>
</div>);
ApplicationLayout.propTypes = {
children: PropTypes.element.isRequired,
locationName: PropTypes.string
};
export default ApplicationLayout;
//export default pacomoTransformer(ApplicationLayout)

View File

@@ -1,26 +0,0 @@
.app-ApplicationLayout {
position: relative;
height: 100%;
&-navbar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 192px;
border-right: 1px solid black;
z-index: 2;
}
&-content {
position: relative;
padding-left: 192px;
width: 100%;
height: 100%;
}
&-link-active {
font-weight: bold;
}
}

View File

@@ -4,7 +4,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { pushState } from 'redux-router';
import read from '../utils/readProp';
export function requireAuthentication(Component) {

Some files were not shown because too many files have changed in this diff Show More