1 Commits

Author SHA1 Message Date
Chris Richardson
d78f88337a Migrated to new Eventuate Java Client 2016-08-30 19:15:50 -07:00
114 changed files with 807 additions and 66371 deletions

View File

@@ -6,12 +6,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
DOCKER_COMPOSE="docker-compose -p event-sourcing-examples"
if [ "$1" = "-f" ] ; then
shift;
DOCKER_COMPOSE="$DOCKER_COMPOSE -f ${1?}"
shift
fi
if [ "$1" = "--use-existing" ] ; then
shift;
else
@@ -19,13 +13,6 @@ else
${DOCKER_COMPOSE?} rm -v --force
fi
NO_RM=false
if [ "$1" = "--no-rm" ] ; then
NO_RM=true
shift
fi
${DOCKER_COMPOSE?} up -d mongodb
if [ -z "$DOCKER_HOST_IP" ] ; then
@@ -60,7 +47,5 @@ set -e
./gradlew -a $* :e2e-test:cleanTest :e2e-test:test -P ignoreE2EFailures=false
if [ $NO_RM = false ] ; then
${DOCKER_COMPOSE?} stop
${DOCKER_COMPOSE?} rm -v --force
fi
${DOCKER_COMPOSE?} stop
${DOCKER_COMPOSE?} rm -v --force

View File

@@ -1,6 +1,5 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: EventuateDependencyPlugin
apply plugin: 'spring-boot'
@@ -11,6 +10,9 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:$eventuateClientVersion"
testCompile "org.springframework.boot:spring-boot-starter-test"
}

View File

@@ -25,7 +25,7 @@ public class AccountsCommandSideServiceIntegrationTest {
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/api" + path;
return "http://localhost:" + port + "/" + path;
}
@Autowired

View File

@@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/accounts")
@RequestMapping("/accounts")
public class AccountController {
private AccountService accountService;

View File

@@ -35,7 +35,7 @@ public class AccountControllerIntegrationTest {
@Test
public void shouldCreateAccount() throws Exception {
mockMvc.perform(post("/api/accounts")
mockMvc.perform(post("/accounts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"customerId\" : \"00000000-00000000\", \"initialBalance\" : 500}")
.accept(MediaType.APPLICATION_JSON))
@@ -44,7 +44,7 @@ public class AccountControllerIntegrationTest {
@Test
public void shouldRejectBadRequest() throws Exception {
mockMvc.perform(post("/api/accounts")
mockMvc.perform(post("/accounts")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"initialBalanceXXX\" : 500}")
.accept(MediaType.APPLICATION_JSON))

View File

@@ -0,0 +1,18 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
public class AccountChangeInfo {
private String changeId;
private String transactionId;
private String transactionType;
private long amount;
private long balanceDelta;
public AccountChangeInfo(String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
this.changeId = changeId;
this.transactionId = transactionId;
this.transactionType = transactionType;
this.amount = amount;
this.balanceDelta = balanceDelta;
}
}

View File

@@ -1,10 +1,8 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
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.transactions.TransferState;
import java.util.*;
import java.util.List;
/**
* Created by cer on 11/21/14.
@@ -17,19 +15,13 @@ public class AccountInfo {
private String description;
private long balance;
private List<AccountChangeInfo> changes;
private Map<String, AccountTransactionInfo> transactions;
private Map<String, TransferState> transferStates;
private List<AccountTransactionInfo> transactions;
private String version;
private Date date;
private AccountInfo() {
}
public AccountInfo(String id, String customerId, String title, String description, long balance, List<AccountChangeInfo> changes, Map<String, AccountTransactionInfo> transactions, String version) {
this(id, customerId, title, description, balance, changes, transactions, version, new Date());
}
public AccountInfo(String id, String customerId, String title, String description, long balance, List<AccountChangeInfo> changes, Map<String, AccountTransactionInfo> transactions, String version, Date date) {
public AccountInfo(String id, String customerId, String title, String description, long balance, List<AccountChangeInfo> changes, List<AccountTransactionInfo> transactions, String version) {
this.id = id;
this.customerId = customerId;
@@ -39,7 +31,6 @@ public class AccountInfo {
this.changes = changes;
this.transactions = transactions;
this.version = version;
this.date = date;
}
public String getId() {
@@ -63,26 +54,14 @@ public class AccountInfo {
}
public List<AccountChangeInfo> getChanges() {
return changes == null ? Collections.EMPTY_LIST : changes;
return changes;
}
public List<AccountTransactionInfo> getTransactions() {
return transactions == null ? Collections.EMPTY_LIST : new ArrayList<>(transactions.values());
return transactions;
}
public String getVersion() {
return version;
}
public Date getDate() {
return date;
}
public Map<String, TransferState> getTransferStates() {
return transferStates;
}
public void setTransferStates(Map<String, TransferState> transferStates) {
this.transferStates = transferStates;
}
}

View File

@@ -1,18 +1,15 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import com.mongodb.WriteResult;
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.transactions.TransferState;
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.math.BigDecimal;
import java.util.Date;
import java.util.Collections;
import static net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.MoneyUtil.toIntegerRepr;
import static org.springframework.data.mongodb.core.query.Criteria.where;
@@ -31,33 +28,29 @@ public class AccountInfoUpdateService {
public void create(String accountId, String customerId, String title, BigDecimal initialBalance, String description, String version) {
try {
AccountChangeInfo ci = new AccountChangeInfo();
ci.setAmount(toIntegerRepr(initialBalance));
WriteResult x = mongoTemplate.upsert(new Query(where("id").is(accountId).and("version").exists(false)),
new Update()
.set("customerId", customerId)
.set("title", title)
.set("description", description)
.set("balance", toIntegerRepr(initialBalance))
.push("changes", ci)
.set("date", new Date())
.set("version", version),
AccountInfo.class);
accountInfoRepository.save(new AccountInfo(
accountId,
customerId,
title,
description,
toIntegerRepr(initialBalance),
Collections.<AccountChangeInfo>emptyList(),
Collections.<AccountTransactionInfo>emptyList(),
version));
logger.info("Saved in mongo");
} catch (DuplicateKeyException t) {
logger.warn("When saving ", t);
} catch (Throwable t) {
logger.error("Error during saving: ");
logger.error("Error during saving: ", t);
throw new RuntimeException(t);
}
}
public void addTransaction(String accountId, AccountTransactionInfo ti) {
mongoTemplate.upsert(new Query(where("id").is(accountId)),
public void addTransaction(String eventId, String fromAccountId, AccountTransactionInfo ti) {
mongoTemplate.updateMulti(new Query(where("id").is(fromAccountId)), /* wrong .and("version").lt(eventId) */
new Update().
set("transactions." + ti.getTransactionId(), ti),
push("transactions", ti).
set("version", eventId),
AccountInfo.class);
}
@@ -71,10 +64,5 @@ public class AccountInfoUpdateService {
AccountInfo.class);
}
public void updateTransactionStatus(String accountId, String transactionId, TransferState status) {
mongoTemplate.upsert(new Query(where("id").is(accountId)),
new Update().
set("transferStates." + transactionId, status),
AccountInfo.class);
}
}

View File

@@ -1,6 +1,9 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
import io.eventuate.CompletableFutureUtil;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class AccountQueryService {
@@ -10,17 +13,15 @@ public class AccountQueryService {
this.accountInfoRepository = accountInfoRepository;
}
public AccountInfo findByAccountId(String accountId) {
public CompletableFuture<AccountInfo> findByAccountId(String accountId) {
AccountInfo account = accountInfoRepository.findOne(accountId);
if (account == null)
throw new AccountNotFoundException(accountId);
return CompletableFutureUtil.failedFuture(new AccountNotFoundException(accountId));
else
if(account.getTransferStates()!=null)
account.getTransactions().stream().forEach(ati -> ati.setStatus(account.getTransferStates().get(ati.getTransactionId())));
return account;
return CompletableFuture.completedFuture(account);
}
public List<AccountInfo> findByCustomerId(String customerId) {
return accountInfoRepository.findByCustomerId(customerId);
public CompletableFuture<List<AccountInfo>> findByCustomerId(String customerId) {
return CompletableFuture.completedFuture(accountInfoRepository.findByCustomerId(customerId));
}
}

View File

@@ -3,14 +3,12 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.ac
import io.eventuate.DispatchedEvent;
import io.eventuate.EventHandlerMethod;
import io.eventuate.EventSubscriber;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.*;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.CreditRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.DebitRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.FailedDebitRecordedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountChangedEvent;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.accounts.AccountCreditedEvent;
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.transactions.MoneyTransferCreatedEvent;
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.transactions.TransferState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -59,36 +57,20 @@ public class AccountQueryWorkflow {
de.getEvent().getDetails().getDate(),
de.getEvent().getDetails().getDescription());
accountInfoUpdateService.addTransaction(fromAccountId, ti);
accountInfoUpdateService.addTransaction(toAccountId, ti);
accountInfoUpdateService.addTransaction(eventId, fromAccountId, ti);
accountInfoUpdateService.addTransaction(eventId, toAccountId, ti);
}
@EventHandlerMethod
public void recordDebit(DispatchedEvent<AccountDebitedEvent> de) {
String accountId = de.getEntityId();
String transactionId = de.getEvent().getTransactionId();
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.DEBITED);
saveChange(de, -1);
}
@EventHandlerMethod
public void recordCredit(DispatchedEvent<AccountCreditedEvent> de) {
String accountId = de.getEntityId();
String transactionId = de.getEvent().getTransactionId();
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.COMPLETED);
saveChange(de, +1);
}
@EventHandlerMethod
public void recordFailed(DispatchedEvent<AccountDebitFailedDueToInsufficientFundsEvent> de) {
String accountId = de.getEntityId();
String transactionId = de.getEvent().getTransactionId();
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.FAILED_DUE_TO_INSUFFICIENT_FUNDS);
}
public <T extends AccountChangedEvent> void saveChange(DispatchedEvent<T> de, int delta) {
String changeId = de.getEventId().asString();
String transactionId = de.getEvent().getTransactionId();

View File

@@ -27,6 +27,8 @@ public class QuerySideAccountConfiguration {
return new AccountQueryService(accountInfoRepository);
}
@Bean
public QuerySideDependencyChecker querysideDependencyChecker(MongoTemplate mongoTemplate) {
return new QuerySideDependencyChecker(mongoTemplate);

View File

@@ -1,149 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts;
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.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 org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = AccountInfoUpdateServiceTest.AccountInfoUpdateServiceTestConfiguration.class)
@IntegrationTest
public class AccountInfoUpdateServiceTest {
@Configuration
@EnableAutoConfiguration
@Import({QuerySideAccountConfiguration.class, EventuateJdbcEventStoreConfiguration.class})
public static class AccountInfoUpdateServiceTestConfiguration {
}
@Autowired
private AccountInfoUpdateService accountInfoUpdateService;
@Autowired
private AccountQueryService accountQueryService;
@Test
public void shouldSaveAccountInfo() throws ExecutionException, InterruptedException {
IdGenerator x = new IdGeneratorImpl();
String accountId = x.genId().asString();
String customerId = x.genId().asString();
String version = x.genId().asString();
String title = "Checking account";
BigDecimal initialBalance = new BigDecimal("1345");
String description = "Some account";
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
AccountInfo accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(accountId, accountInfo.getId());
assertEquals(customerId, accountInfo.getCustomerId());
assertEquals(title, accountInfo.getTitle());
assertEquals(description, accountInfo.getDescription());
assertEquals(initialBalance.longValue() * 100, accountInfo.getBalance());
assertEquals(1, accountInfo.getChanges().size());
assertTrue(accountInfo.getTransactions().isEmpty());
assertEquals(version, accountInfo.getVersion());
String changeId = x.genId().asString();
String transactionId = x.genId().asString();
AccountChangeInfo change = new AccountChangeInfo(changeId, transactionId, AccountCreditedEvent.class.getSimpleName(),
500, +1);
accountInfoUpdateService.updateBalance(accountId, changeId, 500,
change);
accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(initialBalance.add(new BigDecimal(5)).longValue() * 100, accountInfo.getBalance());
assertFalse(accountInfo.getChanges().isEmpty());
assertEquals(change, accountInfo.getChanges().get(1));
String eventId = x.genId().asString();
AccountTransactionInfo ti = new AccountTransactionInfo(transactionId, accountId, accountId, 5, new Date(), "A transfer");
accountInfoUpdateService.addTransaction(accountId, ti);
accountInfo = accountQueryService.findByAccountId(accountId);
assertFalse(accountInfo.getTransactions().isEmpty());
assertEquals(ti, accountInfo.getTransactions().get(0));
}
@Test
public void shouldHandleDuplicateSaveAccountInfo() throws ExecutionException, InterruptedException {
IdGenerator x = new IdGeneratorImpl();
String accountId = x.genId().asString();
String customerId = x.genId().asString();
String version = x.genId().asString();
String title = "Checking account";
BigDecimal initialBalance = new BigDecimal("1345");
String description = "Some account";
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
}
@Test
public void shouldUpdateTransactionStatus() {
IdGenerator x = new IdGeneratorImpl();
String accountId = x.genId().asString();
String customerId = x.genId().asString();
String version = x.genId().asString();
String title = "Checking account";
BigDecimal initialBalance = new BigDecimal("1345");
String description = "Some account";
accountInfoUpdateService.create(accountId, customerId, title, initialBalance, description, version);
String transactionId = x.genId().asString();
AccountTransactionInfo transactionInfo = new AccountTransactionInfo();
transactionInfo.setTransactionId(transactionId);
transactionInfo.setStatus(TransferState.INITIAL);
accountInfoUpdateService.addTransaction(accountId, transactionInfo);
AccountInfo accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(accountId, accountInfo.getId());
assertFalse(accountInfo.getTransactions().isEmpty());
assertEquals(1, accountInfo.getTransactions().size());
assertEquals(TransferState.INITIAL, accountInfo.getTransactions().get(0).getStatus());
accountInfoUpdateService.updateTransactionStatus(accountId, transactionId, TransferState.COMPLETED);
accountInfo = accountQueryService.findByAccountId(accountId);
assertEquals(accountId, accountInfo.getId());
assertFalse(accountInfo.getTransactions().isEmpty());
assertEquals(1, accountInfo.getTransactions().size());
assertEquals(TransferState.COMPLETED, accountInfo.getTransactions().get(0).getStatus());
}
}

View File

@@ -1,6 +1,5 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: EventuateDependencyPlugin
apply plugin: 'spring-boot'
@@ -11,6 +10,8 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:$eventuateClientVersion"
testCompile project(":testutil")
testCompile "org.springframework.boot:spring-boot-starter-test"

View File

@@ -30,7 +30,7 @@ public class AccountsQuerySideServiceIntegrationTest {
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/api" + path;
return "http://localhost:" + port + "/" + path;
}
@Autowired

View File

@@ -4,19 +4,18 @@ import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.acc
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountNotFoundException;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.accounts.AccountQueryService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.*;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.GetAccountResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api")
public class AccountQueryController {
private AccountQueryService accountInfoQueryService;
@@ -27,35 +26,21 @@ public class AccountQueryController {
}
@RequestMapping(value = "/accounts/{accountId}", method = RequestMethod.GET)
public ResponseEntity<GetAccountResponse> get(@PathVariable String accountId) {
AccountInfo accountInfo = accountInfoQueryService.findByAccountId(accountId);
return ResponseEntity.ok().body(new GetAccountResponse(accountInfo.getId(), new BigDecimal(accountInfo.getBalance()), accountInfo.getTitle(), accountInfo.getDescription()));
public CompletableFuture<GetAccountResponse> get(@PathVariable String accountId) {
return accountInfoQueryService.findByAccountId(accountId)
.thenApply(accountInfo -> new GetAccountResponse(accountInfo.getId(), new BigDecimal(accountInfo.getBalance()), accountInfo.getTitle(), accountInfo.getDescription()));
}
@RequestMapping(value = "/customers/{customerId}/accounts", method = RequestMethod.GET)
public ResponseEntity<GetAccountsResponse> getAccountsForCustomer(@PathVariable("customerId") String customerId) {
return ResponseEntity.ok().body(
new GetAccountsResponse(
accountInfoQueryService.findByCustomerId(customerId)
.stream()
.map(accountInfo -> new GetAccountResponse(
accountInfo.getId(),
new BigDecimal(accountInfo.getBalance()),
accountInfo.getTitle(),
accountInfo.getDescription()))
.collect(Collectors.toList())
)
);
@RequestMapping(value = "/accounts", method = RequestMethod.GET)
public CompletableFuture<List<GetAccountResponse>> getAccountsForCustomer(@RequestParam("customerId") String customerId) {
return accountInfoQueryService.findByCustomerId(customerId)
.thenApply(accountInfoList -> accountInfoList.stream().map(accountInfo -> new GetAccountResponse(accountInfo.getId(), new BigDecimal(accountInfo.getBalance()), accountInfo.getTitle(), accountInfo.getDescription())).collect(Collectors.toList()));
}
@RequestMapping(value = "/accounts/{accountId}/history", method = RequestMethod.GET)
public ResponseEntity<AccountHistoryResponse> getTransactionsHistory(@PathVariable String accountId) {
AccountInfo accountInfo = accountInfoQueryService.findByAccountId(accountId);
List<AccountHistoryEntry> historyEntries = new ArrayList<>();
historyEntries.add(new AccountOpenInfo(accountInfo.getDate(), AccountHistoryEntry.EntryType.account, accountInfo.getChanges().get(0).getAmount()));
accountInfo.getTransactions().forEach(historyEntries::add);
return ResponseEntity.ok().body(new AccountHistoryResponse(historyEntries));
public CompletableFuture<List<AccountTransactionInfo>> getTransactionsHistory(@PathVariable String accountId) {
return accountInfoQueryService.findByAccountId(accountId)
.thenApply(AccountInfo::getTransactions);
}
@ResponseStatus(value= HttpStatus.NOT_FOUND, reason="account not found")

View File

@@ -1,6 +1,5 @@
apply plugin: 'java'
apply plugin: 'spring-boot'
apply plugin: EventuateDependencyPlugin
dependencies {
compile project(":common-auth-web")
@@ -15,7 +14,7 @@ dependencies {
}
task copyWebStatic(type: Copy) {
from "../../js-frontend/build"
from "../../prebuilt-web-client"
into "build/resources/main/static"
}

View File

@@ -4,7 +4,6 @@ import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.ApiGateway
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.ContentRequestTransformer;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.HeadersRequestTransformer;
import net.chrisrichardson.eventstore.javaexamples.banking.apigateway.utils.URLRequestTransformer;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
@@ -13,12 +12,9 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
@@ -30,7 +26,6 @@ import java.io.InputStream;
import java.io.InputStreamReader;
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;
@@ -57,20 +52,13 @@ public class GatewayController {
.build();
}
@RequestMapping(value = "/api/**", method = {GET, POST})
@ResponseBody
@RequestMapping(value = "/**", method = {GET, POST})
public ResponseEntity<String> proxyRequest(HttpServletRequest request) throws NoSuchRequestHandlingMethodException, IOException, URISyntaxException {
HttpUriRequest proxiedRequest = createHttpUriRequest(request);
logger.info("request: {}", proxiedRequest);
HttpResponse proxiedResponse = httpClient.execute(proxiedRequest);
logger.info("Response {}", proxiedResponse.getStatusLine().getStatusCode());
return new ResponseEntity<>(read(proxiedResponse.getEntity().getContent()), processHeaders(proxiedResponse.getAllHeaders()), HttpStatus.valueOf(proxiedResponse.getStatusLine().getStatusCode()));
}
private HttpHeaders processHeaders(Header[] headers) {
HttpHeaders result = new HttpHeaders();
Stream.of(headers).filter(h -> h.getName().equalsIgnoreCase("Content-Type")).forEach( h -> result.set(h.getName(), h.getValue()));
return result;
return new ResponseEntity<>(read(proxiedResponse.getEntity().getContent()), HttpStatus.valueOf(proxiedResponse.getStatusLine().getStatusCode()));
}
private HttpUriRequest createHttpUriRequest(HttpServletRequest request) throws URISyntaxException, NoSuchRequestHandlingMethodException, IOException {

View File

@@ -5,21 +5,18 @@ customers.queryside.service.host=localhost
transfers.commandside.service.host=localhost
api.gateway.endpoints[0].path=[/]*api/accounts.*
api.gateway.endpoints[0].path=[/]*accounts.*
api.gateway.endpoints[0].method=GET
api.gateway.endpoints[0].location=http://${accounts.queryside.service.host}:8080
api.gateway.endpoints[1].path=[/]*api/customers.*/accounts
api.gateway.endpoints[1].method=GET
api.gateway.endpoints[1].location=http://${accounts.queryside.service.host}:8080
api.gateway.endpoints[2].path=[/]*api/accounts.*
api.gateway.endpoints[2].method=POST
api.gateway.endpoints[2].location=http://${accounts.commandside.service.host}:8080
api.gateway.endpoints[3].path=[/]*api/customers.*
api.gateway.endpoints[3].method=GET
api.gateway.endpoints[3].location=http://${customers.queryside.service.host}:8080
api.gateway.endpoints[4].path=[/]*api/customers.*
api.gateway.endpoints[1].path=[/]*accounts.*
api.gateway.endpoints[1].method=POST
api.gateway.endpoints[1].location=http://${accounts.commandside.service.host}:8080
api.gateway.endpoints[2].path=[/]*customers.*
api.gateway.endpoints[2].method=GET
api.gateway.endpoints[2].location=http://${customers.queryside.service.host}:8080
api.gateway.endpoints[3].path=[/]*customers.*
api.gateway.endpoints[3].method=POST
api.gateway.endpoints[3].location=http://${customers.commandside.service.host}:8080
api.gateway.endpoints[4].path=[/]*transfers.*
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[4].location=http://${transfers.commandside.service.host}:8080

View File

@@ -3,7 +3,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend;
import io.eventuate.javaclient.spring.jdbc.EventuateJdbcEventStoreConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

View File

@@ -6,7 +6,7 @@ import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.a
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.TransferState;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails;
import org.junit.Assert;
import org.junit.Test;

View File

@@ -6,7 +6,7 @@ import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.a
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.accounts.AccountService;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransfer;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.MoneyTransferService;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions.TransferState;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.TransferDetails;
import org.junit.Assert;
import org.junit.Test;
@@ -17,7 +17,6 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.await;
import static net.chrisrichardson.eventstorestore.javaexamples.testutil.TestUtil.eventually;
@@ -56,10 +55,10 @@ public class AccountQuerySideIntegrationTest {
updatedTransaction -> Assert.assertEquals(TransferState.COMPLETED, updatedTransaction.getEntity().getState()));
eventually(
() -> CompletableFuture.completedFuture(accountQueryService.findByAccountId(fromAccount.getEntityId())),
() -> accountQueryService.findByAccountId(fromAccount.getEntityId()),
accountInfo -> Assert.assertEquals(70 * 100, accountInfo.getBalance()));
eventually(
() -> CompletableFuture.completedFuture(accountQueryService.findByAccountId(toAccount.getEntityId())),
() -> accountQueryService.findByAccountId(toAccount.getEntityId()),
accountInfo -> Assert.assertEquals(380 * 100, accountInfo.getBalance()));
}
}

View File

@@ -1,16 +0,0 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
class EventuateDependencyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.dependencies {
if (project.hasProperty("eventuateLocal")) {
compile "io.eventuate.local.java:eventuate-local-java-jdbc:${project.eventuateLocalVersion}"
compile "io.eventuate.local.java:eventuate-local-java-embedded-cdc-autoconfigure:${project.eventuateLocalVersion}"
} else
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:${project.eventuateClientVersion}"
}
}
}

View File

@@ -28,7 +28,6 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST;
*/
@RestController
@Validated
@RequestMapping("/api")
public class AuthController {
@Autowired

View File

@@ -80,7 +80,7 @@ public class AuthConfiguration extends WebSecurityConfigurerAdapter {
.authorizeRequests()
.antMatchers("/index.html", "/", "/**.js", "/**.css").permitAll()
.antMatchers("/swagger-ui.html", "/v2/api-docs").permitAll()
.antMatchers(HttpMethod.POST, "/api/customers", "/api/login").permitAll()
.antMatchers(HttpMethod.POST, "/customers", "/login").permitAll()
.anyRequest().authenticated().and()
.addFilterAfter(new StatelessAuthenticationFilter(tokenAuthenticationService), BasicAuthenticationFilter.class);
}

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstorestore.javaexamples.testutil;
package net.chrisrichardson.eventstore.javaexamples.banking.commonauth.utils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.http.*;

View File

@@ -16,6 +16,9 @@
<logger name="org.springframework" level='info'>
</logger>
<logger name="net.chrisrichardson.eventstore.javaexamples.banking" level='info'>
</logger>
<logger name="io.eventuate" level='debug'>
</logger>

View File

@@ -1,80 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import java.util.Date;
public class AccountChangeInfo {
private String changeId;
private String transactionId;
private String transactionType;
private long amount;
private long balanceDelta;
public AccountChangeInfo() {
}
public AccountChangeInfo(String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
this(new Date(), changeId, transactionId, transactionType, amount, balanceDelta);
}
public AccountChangeInfo(Date date, String changeId, String transactionId, String transactionType, long amount, long balanceDelta) {
this.changeId = changeId;
this.transactionId = transactionId;
this.transactionType = transactionType;
this.amount = amount;
this.balanceDelta = balanceDelta;
}
public String getChangeId() {
return changeId;
}
public void setChangeId(String changeId) {
this.changeId = changeId;
}
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
public String getTransactionType() {
return transactionType;
}
public void setTransactionType(String transactionType) {
this.transactionType = transactionType;
}
public long getAmount() {
return amount;
}
public void setAmount(long amount) {
this.amount = amount;
}
public long getBalanceDelta() {
return balanceDelta;
}
public void setBalanceDelta(long balanceDelta) {
this.balanceDelta = balanceDelta;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
@Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}

View File

@@ -1,50 +0,0 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import com.fasterxml.jackson.annotation.JsonSubTypes;
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")
@JsonSubTypes({
@JsonSubTypes.Type(value = AccountTransactionInfo.class, name = "transaction"),
@JsonSubTypes.Type(value = AccountOpenInfo.class, name = "account")
})
public class AccountHistoryEntry {
protected Date date;
protected EntryType entryType;
public AccountHistoryEntry() {
}
public AccountHistoryEntry(Date date, EntryType entryType) {
this.date = date;
this.entryType = entryType;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public EntryType getEntryType() {
return entryType;
}
public void setEntryType(EntryType entryType) {
this.entryType = entryType;
}
public enum EntryType {
transaction, account
}
}

View File

@@ -1,26 +0,0 @@
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;
public AccountHistoryResponse() {
}
public AccountHistoryResponse(List<AccountHistoryEntry> transactionsHistory) {
this.transactionsHistory = transactionsHistory;
}
public List<AccountHistoryEntry> getTransactionsHistory() {
return transactionsHistory;
}
public void setTransactionsHistory(List<AccountHistoryEntry> transactionsHistory) {
this.transactionsHistory = transactionsHistory;
}
}

View File

@@ -1,27 +0,0 @@
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;
public AccountOpenInfo() {
}
public AccountOpenInfo(Date date, EntryType entryType, long initialBalance) {
super(date, entryType);
this.initialBalance=initialBalance;
}
public long getInitialBalance() {
return initialBalance;
}
public void setInitialBalance(long initialBalance) {
this.initialBalance = initialBalance;
}
}

View File

@@ -1,20 +1,18 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.accounts;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;
import java.util.Date;
public class AccountTransactionInfo extends AccountHistoryEntry{
public class AccountTransactionInfo {
private String transactionId;
private String fromAccountId;
private String toAccountId;
private long amount;
private Date date;
private String description;
private TransferState status = TransferState.INITIAL;
public AccountTransactionInfo() {
}
@@ -23,12 +21,6 @@ public class AccountTransactionInfo extends AccountHistoryEntry{
this(transactionId, fromAccountId, toAccountId, amount, new Date(), "");
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
public AccountTransactionInfo(String transactionId, String fromAccountId, String toAccountId, long amount, Date date, String description) {
this.transactionId = transactionId;
this.fromAccountId = fromAccountId;
@@ -36,7 +28,6 @@ public class AccountTransactionInfo extends AccountHistoryEntry{
this.amount = amount;
this.date = date;
this.description = description;
this.entryType = EntryType.transaction;
}
public String getTransactionId() {
@@ -71,6 +62,14 @@ public class AccountTransactionInfo extends AccountHistoryEntry{
this.amount = amount;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getDescription() {
return description;
}
@@ -79,14 +78,6 @@ public class AccountTransactionInfo extends AccountHistoryEntry{
this.description = description;
}
public TransferState getStatus() {
return status;
}
public void setStatus(TransferState status) {
this.status = status;
}
@Override
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);

View File

@@ -1,25 +0,0 @@
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;
public GetAccountsResponse() {
}
public GetAccountsResponse(List<GetAccountResponse> accounts) {
this.accounts = accounts;
}
public List<GetAccountResponse> getAccounts() {
return accounts;
}
public void setAccounts(List<GetAccountResponse> accounts) {
this.accounts = accounts;
}
}

View File

@@ -4,12 +4,10 @@ import io.eventuate.AggregateRepository;
import io.eventuate.EventuateAggregateStore;
import io.eventuate.javaclient.spring.EnableEventHandlers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableEventHandlers
@ComponentScan
public class CustomerConfiguration {
@Bean

View File

@@ -2,7 +2,6 @@ apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: 'spring-boot'
apply plugin: EventuateDependencyPlugin
dependencies {
compile project(":customers-command-side-web")
@@ -11,7 +10,8 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:$eventuateClientVersion"
testCompile project(":testutil")
testCompile "org.springframework.boot:spring-boot-starter-test"
testCompile "io.eventuate.client.java:eventuate-client-java-jdbc:$eventuateClientVersion"
}

View File

@@ -6,6 +6,7 @@ import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.custo
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.converter.HttpMessageConverter;
@@ -14,6 +15,7 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert
@Configuration
@Import({CustomersCommandSideWebConfiguration.class, EventuateHttpStompClientConfiguration.class, CommonSwaggerConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class CustomersCommandSideServiceConfiguration {

View File

@@ -25,7 +25,7 @@ public class CustomersCommandSideServiceIntegrationTest {
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/api" + path;
return "http://localhost:" + port + "/" + path;
}
@Autowired

View File

@@ -1,10 +1,6 @@
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.commonswagger.CommonSwaggerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.web.commandside.customers.CustomersCommandSideWebConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -17,16 +13,9 @@ import java.util.Arrays;
import java.util.List;
@Configuration
@Import({CustomersCommandSideWebConfiguration.class, EventuateJdbcEventStoreConfiguration.class, CommonSwaggerConfiguration.class, AuthConfiguration.class})
@EnableAutoConfiguration
@Import({CustomersCommandSideServiceConfiguration.class, AuthConfiguration.class})
public class CustomersCommandSideServiceTestConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = new MappingJackson2HttpMessageConverter();
return new HttpMessageConverters(additional);
}
@Bean
public RestTemplate restTemplate(HttpMessageConverters converters) {
RestTemplate restTemplate = new RestTemplate();

View File

@@ -15,7 +15,7 @@ import java.util.concurrent.CompletableFuture;
* Created by popikyardo on 03.02.16.
*/
@RestController
@RequestMapping("/api/customers")
@RequestMapping("/customers")
public class CustomerController {
private CustomerService customerService;

View File

@@ -5,7 +5,6 @@ import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.Quer
import net.chrisrichardson.eventstore.javaexamples.banking.common.customers.ToAccountInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import java.util.Collections;
@@ -14,38 +13,36 @@ import java.util.Collections;
*/
public class CustomerInfoUpdateService {
private Logger logger = LoggerFactory.getLogger(getClass());
private Logger logger = LoggerFactory.getLogger(getClass());
private QuerySideCustomerRepository querySideCustomerRepository;
private QuerySideCustomerRepository accountInfoRepository;
public CustomerInfoUpdateService(QuerySideCustomerRepository querySideCustomerRepository) {
this.querySideCustomerRepository = querySideCustomerRepository;
public CustomerInfoUpdateService(QuerySideCustomerRepository accountInfoRepository) {
this.accountInfoRepository = accountInfoRepository;
}
public void create(String id, CustomerInfo customerInfo) {
try {
accountInfoRepository.save(new QuerySideCustomer(id,
customerInfo.getName(),
customerInfo.getEmail(),
customerInfo.getSsn(),
customerInfo.getPhoneNumber(),
customerInfo.getAddress(),
Collections.<String, ToAccountInfo>emptyMap()
)
);
logger.info("Saved in mongo");
} catch (Throwable t) {
logger.error("Error during saving: ", t);
throw new RuntimeException(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) {
QuerySideCustomer customer = querySideCustomerRepository.findOne(id);
customer.getToAccounts().put(accountInfo.getId(), accountInfo);
querySideCustomerRepository.save(customer);
}
public void addToAccount(String id, ToAccountInfo accountInfo) {
QuerySideCustomer customer = accountInfoRepository.findOne(id);
customer.getToAccounts().put(accountInfo.getId(), accountInfo);
accountInfoRepository.save(customer);
}
}

View File

@@ -2,7 +2,6 @@ package net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.cu
import io.eventuate.javaclient.spring.EnableEventHandlers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
@@ -13,7 +12,6 @@ import org.springframework.data.mongodb.repository.config.EnableMongoRepositorie
@Configuration
@EnableMongoRepositories
@EnableEventHandlers
@ComponentScan
public class QuerySideCustomerConfiguration {
@Bean
public CustomerQueryWorkflow customerQueryWorkflow(CustomerInfoUpdateService accountInfoUpdateService) {

View File

@@ -1,6 +1,5 @@
apply plugin: VerifyMongoDBConfigurationPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
apply plugin: EventuateDependencyPlugin
apply plugin: 'spring-boot'
@@ -11,6 +10,8 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:$eventuateClientVersion"
testCompile project(":testutil")
testCompile project(":customers-command-side-service")
testCompile "org.springframework.boot:spring-boot-starter-test"

View File

@@ -1,19 +1,21 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import io.eventuate.javaclient.spring.httpstomp.EventuateHttpStompClientConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.queryside.customers.QuerySideCustomerConfiguration;
import net.chrisrichardson.eventstore.javaexamples.banking.commonswagger.CommonSwaggerConfiguration;
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;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
@Configuration
@Import({CustomersQuerySideWebConfiguration.class, EventuateHttpStompClientConfiguration.class, CommonSwaggerConfiguration.class})
@Import({QuerySideCustomerConfiguration.class, EventuateHttpStompClientConfiguration.class, CommonSwaggerConfiguration.class})
@EnableAutoConfiguration
@ComponentScan
public class CustomersQuerySideServiceConfiguration {

View File

@@ -1,8 +1,8 @@
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.*;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@@ -12,6 +12,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;
import rx.Observable;
import javax.annotation.PostConstruct;
@@ -27,7 +28,7 @@ public class CustomersQuerySideServiceIntegrationTest {
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/api" + path;
return "http://localhost:" + port + "/" + path;
}
@Autowired

View File

@@ -1,7 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.AuthConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -14,8 +13,7 @@ import java.util.Arrays;
import java.util.List;
@Configuration
@Import({CustomersCommandSideServiceConfiguration.class, CustomersQuerySideServiceConfiguration.class, AuthConfiguration.class})
@EnableAutoConfiguration
@Import({CustomersQuerySideServiceConfiguration.class, CustomersQuerySideServiceConfiguration.class, AuthConfiguration.class})
public class CustomersQuerySideServiceTestConfiguration {
@Bean

View File

@@ -14,7 +14,6 @@ import java.util.concurrent.CompletableFuture;
* Created by Main on 05.02.2016.
*/
@RestController
@RequestMapping("/api")
public class CustomerQueryController {
private CustomerQueryService customerQueryService;

View File

@@ -1,129 +0,0 @@
apigateway:
image: java:openjdk-8u91-jdk
working_dir: /app
volumes:
- ./api-gateway-service/build/libs:/app
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
ports:
- "8080:8080"
links:
- accountscommandside
- transactionscommandside
- accountsqueryside
- customerscommandside
- customersqueryside
- mongodb
environment:
SPRING_DATASOURCE_URL:
SPRING_DATASOURCE_USERNAME:
SPRING_DATASOURCE_PASSWORD:
SPRING_DATASOURCE_DRIVER_CLASS_NAME:
EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS:
EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING:
SPRING_DATA_MONGODB_URI: mongodb://mongodb/mydb
EVENTUATELOCAL_EMBEDDED_CDC_DB_USER_NAME:
EVENTUATELOCAL_EMBEDDED_CDC_DB_PASSWORD:
accountscommandside:
image: java:openjdk-8u91-jdk
working_dir: /app
volumes:
- ./accounts-command-side-service/build/libs:/app
command: java -jar /app/accounts-command-side-service.jar
ports:
- "8085:8080"
environment:
SPRING_DATASOURCE_URL:
SPRING_DATASOURCE_USERNAME:
SPRING_DATASOURCE_PASSWORD:
SPRING_DATASOURCE_DRIVER_CLASS_NAME:
EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS:
EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING:
EVENTUATELOCAL_EMBEDDED_CDC_DB_USER_NAME:
EVENTUATELOCAL_EMBEDDED_CDC_DB_PASSWORD:
transactionscommandside:
image: java:openjdk-8u91-jdk
working_dir: /app
volumes:
- ./transactions-command-side-service/build/libs:/app
command: java -jar /app/transactions-command-side-service.jar
ports:
- "8082:8080"
environment:
SPRING_DATASOURCE_URL:
SPRING_DATASOURCE_USERNAME:
SPRING_DATASOURCE_PASSWORD:
SPRING_DATASOURCE_DRIVER_CLASS_NAME:
EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS:
EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING:
EVENTUATELOCAL_EMBEDDED_CDC_DB_USER_NAME:
EVENTUATELOCAL_EMBEDDED_CDC_DB_PASSWORD:
accountsqueryside:
image: java:openjdk-8u91-jdk
working_dir: /app
volumes:
- ./accounts-query-side-service/build/libs:/app
command: java -jar /app/accounts-query-side-service.jar
ports:
- "8081:8080"
links:
- mongodb
environment:
SPRING_DATASOURCE_URL:
SPRING_DATASOURCE_USERNAME:
SPRING_DATASOURCE_PASSWORD:
SPRING_DATASOURCE_DRIVER_CLASS_NAME:
EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS:
SPRING_DATA_MONGODB_URI: mongodb://mongodb/mydb
EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING:
EVENTUATELOCAL_EMBEDDED_CDC_DB_USER_NAME:
EVENTUATELOCAL_EMBEDDED_CDC_DB_PASSWORD:
customerscommandside:
image: java:openjdk-8u91-jdk
working_dir: /app
volumes:
- ./customers-command-side-service/build/libs:/app
command: java -jar /app/customers-command-side-service.jar
ports:
- "8083:8080"
environment:
SPRING_DATASOURCE_URL:
SPRING_DATASOURCE_USERNAME:
SPRING_DATASOURCE_PASSWORD:
SPRING_DATASOURCE_DRIVER_CLASS_NAME:
EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS:
EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING:
EVENTUATELOCAL_EMBEDDED_CDC_DB_USER_NAME:
EVENTUATELOCAL_EMBEDDED_CDC_DB_PASSWORD:
customersqueryside:
image: java:openjdk-8u91-jdk
working_dir: /app
volumes:
- ./customers-query-side-service/build/libs:/app
command: java -jar /app/customers-query-side-service.jar
ports:
- "8084:8080"
links:
- mongodb
environment:
SPRING_DATASOURCE_URL:
SPRING_DATASOURCE_USERNAME:
SPRING_DATASOURCE_PASSWORD:
SPRING_DATASOURCE_DRIVER_CLASS_NAME:
EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS:
EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING:
SPRING_DATA_MONGODB_URI: mongodb://mongodb/mydb
EVENTUATELOCAL_EMBEDDED_CDC_DB_USER_NAME:
EVENTUATELOCAL_EMBEDDED_CDC_DB_PASSWORD:
mongodb:
image: mongo:3.0.4
hostname: mongodb
command: mongod --smallfiles
ports:
- "27017:27017"

View File

@@ -18,7 +18,7 @@ public class EndToEndTest extends AbstractRestAPITest {
CustomersTestUtils customersTestUtils = new CustomersTestUtils(restTemplate, baseUrl("/customers/"));
public String baseUrl(String path) {
return "http://" + getenv("SERVICE_HOST", "localhost") + ":" + 8080 + "/api" + path;
return "http://" + getenv("SERVICE_HOST", "localhost") + ":" + 8080 + "/" + path;
}
@Override

View File

@@ -6,5 +6,4 @@ eventuateMavenRepoUrl=http://mavenrepo.eventuate.io/release
springBootVersion=1.3.5.RELEASE
eventuateClientVersion=0.8.0.RELEASE
eventuateLocalVersion=0.2.0.RELEASE

View File

@@ -14,8 +14,8 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:$eventuateClientVersion"
compile project(":common-swagger")
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:$eventuateClientVersion"
testCompile project(":testutil")
testCompile "org.springframework.boot:spring-boot-starter-test"
@@ -23,7 +23,7 @@ dependencies {
}
task copyWebStatic(type: Copy) {
from "../../js-frontend/build"
from "../../prebuilt-web-client"
into "build/resources/main/static"
}

View File

@@ -11,10 +11,8 @@ import net.chrisrichardson.eventstore.javaexamples.banking.web.queryside.QuerySi
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
@@ -32,12 +30,6 @@ public class BankingWebConfiguration extends WebMvcConfigurerAdapter {
return new HttpMessageConverters(additional);
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
System.setProperty("vertx.disableFileCPResolving", "true");
return new PropertySourcesPlaceholderConfigurer();
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("forward:/index.html");

View File

@@ -43,7 +43,7 @@ public class BankingAuthTest {
}
private String baseUrl(String path) {
return "http://localhost:" + port + "/api" + path;
return "http://localhost:" + port + "/" + path;
}
@Test
@@ -57,12 +57,6 @@ 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);

View File

@@ -1,7 +1,6 @@
package net.chrisrichardson.eventstore.javaexamples.banking.web;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.AbstractRestAPITest;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.AuthenticatedRestTemplate;
import net.chrisrichardson.eventstorestore.javaexamples.testutil.CustomersTestUtils;
import org.junit.runner.RunWith;
@@ -12,6 +11,7 @@ import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.client.RestTemplate;
import javax.annotation.PostConstruct;
@RunWith(SpringJUnit4ClassRunner.class)
@@ -32,7 +32,7 @@ public class BankingWebIntegrationTest extends AbstractRestAPITest {
@Override
public String baseUrl(String path) {
return "http://localhost:" + port + "/api" + path;
return "http://localhost:" + port + "/" + path;
}
@Override

View File

@@ -1,6 +1,9 @@
package net.chrisrichardson.eventstorestore.javaexamples.testutil;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.*;
import net.chrisrichardson.eventstore.javaexamples.banking.common.accounts.AccountTransactionInfo;
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.GetAccountResponse;
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;
@@ -59,21 +62,21 @@ public abstract class AbstractRestAPITest {
assertAccountBalance(toAccountId, finalToAccountBalance);
eventually(
new Producer<AccountHistoryResponse>() {
new Producer<AccountTransactionInfo[]>() {
@Override
public CompletableFuture<AccountHistoryResponse> produce() {
public CompletableFuture<AccountTransactionInfo[]> produce() {
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/accounts/" + fromAccountId + "/history"),
AccountHistoryResponse.class));
AccountTransactionInfo[].class));
}
},
new Verifier<AccountHistoryResponse>() {
new Verifier<AccountTransactionInfo[]>() {
@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();
public void verify(AccountTransactionInfo[] transactionInfos) {
Optional<AccountTransactionInfo> first = Arrays.asList(transactionInfos).stream().filter(ti -> ti.getTransactionId().equals(moneyTransfer.getMoneyTransferId())).findFirst();
assertTrue(first.isPresent());
AccountTransactionInfo ti = (AccountTransactionInfo)first.get();
AccountTransactionInfo ti = first.get();
assertEquals(fromAccountId, ti.getFromAccountId());
assertEquals(toAccountId, ti.getToAccountId());
@@ -108,17 +111,17 @@ public abstract class AbstractRestAPITest {
assertAccountBalance(accountId, initialFromAccountBalance);
eventually(
new Producer<GetAccountsResponse>() {
new Producer<GetAccountResponse[]>() {
@Override
public CompletableFuture<GetAccountsResponse> produce() {
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/customers/"+customerId+"/accounts"),
GetAccountsResponse.class));
public CompletableFuture<GetAccountResponse[]> produce() {
return CompletableFuture.completedFuture(getAuthenticatedRestTemplate().getForEntity(baseUrl("/accounts?customerId=" + customerId),
GetAccountResponse[].class));
}
},
new Verifier<GetAccountsResponse>() {
new Verifier<GetAccountResponse[]>() {
@Override
public void verify(GetAccountsResponse accountResponses) {
assertTrue(accountResponses.getAccounts().stream().filter(acc -> acc.getAccountId().equals(accountId)).findFirst().isPresent());
public void verify(GetAccountResponse[] accountResponses) {
assertTrue(Arrays.asList(accountResponses).stream().filter(acc -> acc.getAccountId().equals(accountId)).findFirst().isPresent());
}
});
}

View File

@@ -1,5 +1,6 @@
package net.chrisrichardson.eventstorestore.javaexamples.testutil;
import net.chrisrichardson.eventstore.javaexamples.banking.commonauth.utils.BasicAuthUtils;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;

View File

@@ -37,7 +37,7 @@ public class TestUtil {
}
static class Success<T> implements Outcome<T> {
static class Success<T> implements Outcome<T> {
T value;
@@ -54,26 +54,51 @@ public class TestUtil {
}
}
public static <T> void eventually(Producer<T> producer, Verifier<T> predicate) {
Throwable laste = null;
for (int i = 0; i < 30 ; i++) {
try {
T x = producer.produce().get(30, TimeUnit.SECONDS);
predicate.verify(x);
return;
} catch (Throwable t) {
laste = t;
public static <T> void eventually(final Producer<T> producer, final Verifier<T> verifier) {
final int n = 50;
Object possibleException = Observable.timer(0, 200, TimeUnit.MILLISECONDS).flatMap(new Func1<Long, Observable<Outcome<T>>>() {
@Override
public Observable<Outcome<T>> call(Long aLong) {
try {
return fromCompletableFuture(producer.produce()).map(new Func1<T, Outcome<T>>() {
@Override
public Outcome<T> call(T t) {
return new Success<T>(t);
}
});
} catch (Exception e) {
Outcome<T> value = new Failure<T>(e);
return Observable.just(value);
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}).map(new Func1<Outcome<T>, Throwable>() {
@Override
public Throwable call(Outcome<T> t) {
try {
if (t instanceof Success) {
verifier.verify(((Success<T>) t).value);
return null;
} else
return ((Failure<T>) t).t;
} catch (Throwable e) {
return e;
}
}
}
if (laste != null)
throw new RuntimeException("Last exception was", laste);
else
throw new RuntimeException("predicate never satisfied");
}).take(n).zipWith(Observable.range(0, n), new Func2<Throwable, Integer, Tuple2<Throwable, Integer>>() {
@Override
public Tuple2<Throwable, Integer> call(Throwable e, Integer idx) {
return new Tuple2<Throwable, Integer>(e, idx);
}
}).skipWhile(new Func1<Tuple2<Throwable, Integer>, Boolean>() {
@Override
public Boolean call(Tuple2<Throwable, Integer> tuple2) {
return tuple2.first != null && tuple2.second < n - 1;
}
}).first().toBlocking().getIterator().next().first;
if (possibleException != null)
throw new RuntimeException((Throwable) possibleException);
}
private static <T> Observable<T> fromCompletableFuture(CompletableFuture<T> future) {

View File

@@ -4,7 +4,6 @@ import io.eventuate.Event;
import io.eventuate.EventUtil;
import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
import net.chrisrichardson.eventstore.javaexamples.banking.backend.common.transactions.*;
import net.chrisrichardson.eventstore.javaexamples.banking.common.transactions.TransferState;
import java.util.List;

View File

@@ -1,4 +1,4 @@
package net.chrisrichardson.eventstore.javaexamples.banking.common.transactions;
package net.chrisrichardson.eventstore.javaexamples.banking.backend.commandside.transactions;
public enum TransferState {
NEW, INITIAL, DEBITED, COMPLETED, FAILED_DUE_TO_INSUFFICIENT_FUNDS

View File

@@ -1,5 +1,4 @@
apply plugin: 'spring-boot'
apply plugin: EventuateDependencyPlugin
apply plugin: VerifyEventStoreEnvironmentPlugin
@@ -10,6 +9,8 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-web"
compile "org.springframework.boot:spring-boot-starter-actuator"
compile "io.eventuate.client.java:eventuate-client-java-http-stomp-spring:$eventuateClientVersion"
testCompile "org.springframework.boot:spring-boot-starter-test"
}

View File

@@ -20,7 +20,7 @@ public class TransactionsCommandSideServiceIntegrationTest {
private int port;
private String baseUrl(String path) {
return "http://localhost:" + port + "/api" + path;
return "http://localhost:" + port + "/" + path;
}
@Autowired

View File

@@ -16,7 +16,7 @@ import java.util.Date;
import java.util.concurrent.CompletableFuture;
@RestController
@RequestMapping("/api/transfers")
@RequestMapping("/transfers")
public class MoneyTransferController {
private final MoneyTransferService moneyTransferService;

View File

@@ -36,7 +36,7 @@ public class MoneyTransferControllerIntegrationTest {
@Test
public void shouldCreateAccount() throws Exception {
mockMvc.perform(post("/api/transfers")
mockMvc.perform(post("/transfers")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"fromAccountId\" : \"fromAccountId\", \"toAccountId\" : \"toAccountId\", \"amount\" : \"500\"}")
.accept(MediaType.APPLICATION_JSON))
@@ -45,7 +45,7 @@ public class MoneyTransferControllerIntegrationTest {
@Test
public void shouldRejectBadRequest() throws Exception {
mockMvc.perform(post("/api/transfers")
mockMvc.perform(post("/transfers")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"fromAccountId\" : \"fromAccountIdXXXXXX\"}, {\"toAccountId\" : \"toAccountId\"}, {\"amount\" : \"500\"}")
.accept(MediaType.APPLICATION_JSON))

View File

@@ -1,3 +1,3 @@
{
"presets": ["es2015", "stage-0", "react"]
"presets": ["es2015"]
}

View File

@@ -1,5 +1,4 @@
node_modules
build
!/build
#dist
dist-intermediate

View File

@@ -1,15 +1,153 @@
# Money Transfer App - Frontend Client
# Unicorn Standard Starter Kit
This..
This starter kit provides you with the code and conventions you need to get straight into building your React/Redux based app.
## Happiness is six lines away
*Prerequisites: node.js and git*
```
cd js-frontend
git clone https://github.com/unicorn-standard/starter-kit.git your-repo-name
cd your-repo-name
npm install
npm run build
npm start
npm run open # (from a different console window, otherwise open localhost:3000)
```
Text..
Presto, you've got a ready-to-customise application!
![Unicorn Standard Starter Kit](http://unicornstandard.com/files/boilerplate.png?1)
## Why use Unicorn Standard Starter Kit?
- Your directory structure is sorted as soon as you `git clone`
- ES6 compilation and automatic-reloading development server are configured for you with [webpack](https://webpack.github.io/) and [Babel](https://babeljs.io/)
- [Redux](http://redux.js.org/) is an incredibly simple way of modelling your data, with great community support
- [React](https://www.reactjs.org/) is an incredibly simple way of rendering your views, and is maintained by Facebook
- Simple [uniloc](http://unicornstandard.com/packages/uniloc.html)-based routing is included - easy to understand, and easy to customize
- The [Pacomo](http://unicornstandard.com/packages/pacomo.html) CSS conventions eliminate bugs caused by conflicting styles
- The actors pattern allows you to easily react to changes on your store *without* forcing a re-render
- Your redux store is already configured with navigation, data and view models
- Comes with views, layouts and reducers for a simple document editor!
## Getting Started
#### Put your name on it
- Update name, desription and author in `package.json`
- Update app title in `src/index.html`
- Restart the dev server (make sure to do this after any changes to `src/index.html`)
#### Make sure your editor is happy
- Setup ES6 syntax highlighting on extensions `.js` and `.jsx` (see [babel-sublime](https://github.com/babel/babel-sublime))
#### Start building
- Add a route to `src/constants/ROUTES.js`
- Add a nav menu item for your route in `src/components/ApplicationLayout.jsx`
- Add a component for your route in `src/components`
- Add reducers and actions for your component's view model in `src/actions` and `src/reducers/view`
- Add any data models which your component reqiures in `src/reducers/data`
- Add a container to map your store's `state` and `dispatch` to component props in `src/containers`
- Configure your route in `src/Application.jsx`
- Bask in the glory of your creation
- Don't forget to commit your changes and push to Bitbucket or GitHub!
#### Show your friends
- Run `gulp dist` to output a web-ready build of your app to `dist`
## Structure
### Entry point
`main.js` is the entry point to your application. It defines your redux store, handles any actions dispatched to your redux store, handles changes to the browser's current URL, and also makes an initial route change dispatch.
Most of the above will be obvious from a quick read through `main.js` - if there is one thing which may strike you as "interesting", it'll be the block which handles actors.
### Actors
*[Read the introduction to actors](http://jamesknelson.com/join-the-dark-side-of-the-flux-responding-to-actions-with-actors/)*
Each time your store's state changes, a sequence of functions are called on the *current state* of your store. These functions are called **actors**.
There is one important exception to this rule: actors will not be called if `main.js` is currently in the midst of calling the sequence from a previous update. This allows earlier actors in a sequence to dispatch actions to the store, with later actors in the sequence receiving the *updated* state.
The code which accomplishes this is very small:
```javascript
let acting = false
store.subscribe(function() {
// Ensure that any action dispatched by actors do not result in a new
// actor run, allowing actors to dispatch with impunity.
if (!acting) {
acting = true
for (let actor of actors) {
actor(store.getState(), store.dispatch.bind(store))
}
acting = false
}
})
```
The actor is defined in `src/actors/index.js`. By default, it runs the following sequence:
- **redirector** - dispatch a navigation action if the current location should redirect to another location
- **renderer** - renders your <Application> component with React
### Model
Your model (i.e. reducers and actions) is pre-configured with three parts:
#### Navigation
The `navigation` state holds the following information:
- `location` is the object which your `ROUTES` constant's `lookup` function returns for the current URL. With the default uniloc-based `ROUTES` object, this will have a string `name` property, and an `options` object containing any route parameters.
- `transitioning` is true if a navigation `start` action has been dispatched, but the browser hasn't changed URL yet
#### Data
The `data` state can be thought of as the database for your application. If your application reads data from a remote server, it should be stored here. Any metadata should also be stored here, including the time it was fetched or its current version number.
#### View
The `view` state has a property for each of the view's in your app, holding their current state. For example, form state should be stored in the view models.
### Directories
- `src/actions` - Redux action creators
- `src/actors` - Handle changes to your store's state
- `src/components` - React components, stateless where possible
- `src/constants` - Define stateless data
- `src/containers` - Unstyled "smart" components which take your store's `state` and `dispatch`, and possibly navigation `location`, and pass them to "dumb" components
- `src/reducers` - Redux reducers
- `src/static` - Files which will be copied across to the root directory on build
- `src/styles` - Helpers for stylesheets for individual components
- `src/utils` - General code which isn't specific to your application
- `src/validators` - Functions which take an object containing user entry and return an object containing any errors
Other directories:
- `build` - Intermediate files produced by the development server. Don't touch these.
- `dist` - The output of `gulp dist`, which contains your distribution-ready app.
- `config/environments` - The build system will assign one of these to the `environment` module, depending on the current build environment.
Main application files:
- `src/Application.jsx` - Your application's top-level React component
- `src/index.html` - The single page for your single page application
- `src/main.js` - The application's entry point
- `src/main.less` - Global styles for your application
Main build files:
- `gulpfile.babel.js` - Build scripts written with [gulp](http://gulpjs.com/)
- `webpack.config.js` - [Webpack](http://webpack.github.io/) configuration
## TODO
- Watch `static` and `index.html` for changes and copy them across to `build` when appropriate

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,31 +0,0 @@
<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="ie6" > <![endif]-->
<!--[if IE 7 ]> <html lang="en" class="ie7" > <![endif]-->
<!--[if IE 8 ]> <html lang="en" class="ie8" > <![endif]-->
<!--[if IE 9 ]> <html lang="en" class="ie9" > <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Money Transfer App</title><meta name="description" content="ES Money Transfer App" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" /><!-- Latest compiled and minified CSS -->
<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.09cb8f5a05c9cfc35585.js"></script><script src="/vendor.f73c0104cb72cfb2809e.js"></script><script src="/style.6d7a32b1405ea1bb2bdf.js"></script><script src="/app.d4bdff82ac1db214898b.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)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXX-XX', 'auto');
ga('send', 'pageview');
</script><!--{"files":{"publicPath":"/","chunks":{"manifest":{"size":0,"entry":"/manifest.09cb8f5a05c9cfc35585.js","hash":"09cb8f5a05c9cfc35585","css":[]},"vendor":{"size":1670874,"entry":"/vendor.f73c0104cb72cfb2809e.js","hash":"f73c0104cb72cfb2809e","css":[]},"style":{"size":122,"entry":"/style.6d7a32b1405ea1bb2bdf.js","hash":"6d7a32b1405ea1bb2bdf","css":["/style.6d7a32b1405ea1bb2bdf.css"]},"app":{"size":351315,"entry":"/app.d4bdff82ac1db214898b.js","hash":"d4bdff82ac1db214898b","css":[]}},"js":["/manifest.09cb8f5a05c9cfc35585.js","/vendor.f73c0104cb72cfb2809e.js","/style.6d7a32b1405ea1bb2bdf.js","/app.d4bdff82ac1db214898b.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>
</html>

View File

@@ -1,95 +0,0 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ var parentJsonpFunction = window["webpackJsonp"];
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, callbacks = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId])
/******/ callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/ while(callbacks.length)
/******/ callbacks.shift().call(null, __webpack_require__);
/******/ if(moreModules[0]) {
/******/ installedModules[0] = 0;
/******/ return __webpack_require__(0);
/******/ }
/******/ };
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // "0" means "already loaded"
/******/ // Array means "loading", array contains callbacks
/******/ var installedChunks = {
/******/ 3:0
/******/ };
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/ // "0" is the signal for "already loaded"
/******/ if(installedChunks[chunkId] === 0)
/******/ return callback.call(null, __webpack_require__);
/******/
/******/ // an array means "currently loading".
/******/ if(installedChunks[chunkId] !== undefined) {
/******/ installedChunks[chunkId].push(callback);
/******/ } else {
/******/ // start chunk loading
/******/ installedChunks[chunkId] = [callback];
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = 'text/javascript';
/******/ script.charset = 'utf-8';
/******/ script.async = true;
/******/
/******/ script.src = __webpack_require__.p + "" + {"0":"d4bdff82ac1db214898b","1":"6d7a32b1405ea1bb2bdf","2":"f73c0104cb72cfb2809e"}[chunkId] + ".js";
/******/ head.appendChild(script);
/******/ }
/******/ };
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/";
/******/ })
/************************************************************************/
/******/ ([]);
//# sourceMappingURL=manifest.09cb8f5a05c9cfc35585.js.map

View File

@@ -1 +0,0 @@
{"version":3,"sources":["webpack:///webpack/bootstrap 8ac05bd1ead33e72514a?"],"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.09cb8f5a05c9cfc35585.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\":\"d4bdff82ac1db214898b\",\"1\":\"6d7a32b1405ea1bb2bdf\",\"2\":\"f73c0104cb72cfb2809e\"}[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 8ac05bd1ead33e72514a\n **/"],"sourceRoot":""}

View File

@@ -1,5 +0,0 @@
# www.robotstxt.org/
# Allow crawling of all content
User-agent: *
Disallow:

View File

@@ -1,441 +0,0 @@
@import url(http://fonts.googleapis.com/css?family=Roboto:300,400,500);
/**
* React Select
* ============
* Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/
* https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs
* MIT License: https://github.com/keystonejs/react-select
*/
.Select {
position: relative;
}
.Select,.Select div,.Select input,.Select span {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.Select.is-disabled > .Select-control {
background-color: #f6f6f6;
}
.Select.is-disabled .Select-arrow-zone {
cursor: default;
pointer-events: none;
}
.Select-control {
background-color: #fff;
border-color: #d9d9d9 #ccc #b3b3b3;
border-radius: 4px;
border: 1px solid #ccc;
color: #333;
cursor: default;
display: table;
height: 36px;
outline: none;
overflow: hidden;
position: relative;
width: 100%;
}
.Select-control:hover {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.is-searchable.is-open > .Select-control {
cursor: text;
}
.is-open > .Select-control {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
background: #fff;
border-color: #b3b3b3 #ccc #d9d9d9;
}
.is-open > .Select-control > .Select-arrow {
border-color: transparent transparent #999;
border-width: 0 5px 5px;
}
.is-searchable.is-focused:not(.is-open) > .Select-control {
cursor: text;
}
.is-focused:not(.is-open) > .Select-control {
border-color: #08c #0099e6 #0099e6;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 5px -1px rgba(0, 136, 204, 0.5);
}
.Select-placeholder {
bottom: 0;
color: #aaa;
left: 0;
line-height: 34px;
padding-left: 10px;
padding-right: 10px;
position: absolute;
right: 0;
top: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.has-value > .Select-control > .Select-placeholder {
color: #333;
}
.Select-value {
color: #aaa;
left: 0;
padding: 8px 52px 8px 10px;
position: absolute;
right: -15px;
top: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.has-value > .Select-control > .Select-value {
color: #333;
}
.Select-input {
height: 34px;
padding-left: 10px;
padding-right: 10px;
vertical-align: middle;
}
.Select-input > input {
background: none transparent;
border: 0 none;
box-shadow: none;
cursor: default;
display: inline-block;
font-family: inherit;
font-size: inherit;
height: 34px;
margin: 0;
outline: none;
padding: 0;
-webkit-appearance: none;
}
.is-focused .Select-input > input {
cursor: text;
}
.Select-control:not(.is-searchable) > .Select-input {
outline: none;
}
.Select-loading-zone {
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 16px;
}
.Select-loading {
-webkit-animation: Select-animation-spin 400ms infinite linear;
-o-animation: Select-animation-spin 400ms infinite linear;
animation: Select-animation-spin 400ms infinite linear;
width: 16px;
height: 16px;
box-sizing: border-box;
border-radius: 50%;
border: 2px solid #ccc;
border-right-color: #333;
display: inline-block;
position: relative;
vertical-align: middle;
}
.Select-clear-zone {
-webkit-animation: Select-animation-fadeIn 200ms;
-o-animation: Select-animation-fadeIn 200ms;
animation: Select-animation-fadeIn 200ms;
color: #999;
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 17px;
}
.Select-clear-zone:hover {
color: #D0021B;
}
.Select-clear {
display: inline-block;
font-size: 18px;
line-height: 1;
}
.Select--multi .Select-clear-zone {
width: 17px;
}
.Select-arrow-zone {
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 25px;
padding-right: 5px;
}
.Select-arrow {
border-color: #999 transparent transparent;
border-style: solid;
border-width: 5px 5px 2.5px;
display: inline-block;
height: 0;
width: 0;
}
.is-open .Select-arrow,.Select-arrow-zone:hover > .Select-arrow {
border-top-color: #666;
}
@-webkit-keyframes Select-animation-fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes Select-animation-fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.Select-menu-outer {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
background-color: #fff;
border: 1px solid #ccc;
border-top-color: #e6e6e6;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
box-sizing: border-box;
margin-top: -1px;
max-height: 200px;
position: absolute;
top: 100%;
width: 100%;
z-index: 1000;
-webkit-overflow-scrolling: touch;
}
.Select-menu {
max-height: 198px;
overflow-y: auto;
}
.Select-option {
box-sizing: border-box;
color: #666666;
cursor: pointer;
display: block;
padding: 8px 10px;
}
.Select-option:last-child {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
.Select-option.is-focused {
background-color: #f2f9fc;
color: #333;
}
.Select-option.is-disabled {
color: #cccccc;
cursor: not-allowed;
}
.Select-noresults,.Select-search-prompt,.Select-searching {
box-sizing: border-box;
color: #999999;
cursor: default;
display: block;
padding: 8px 10px;
}
.Select--multi .Select-input {
vertical-align: middle;
margin-left: 10px;
padding: 0;
}
.Select--multi.has-value .Select-input {
margin-left: 5px;
}
.Select-item {
background-color: #f2f9fc;
border-radius: 2px;
border: 1px solid #c9e6f2;
color: #08c;
display: inline-block;
font-size: 0.9em;
margin-left: 5px;
margin-top: 5px;
vertical-align: top;
}
.Select-item-icon,.Select-item-label {
display: inline-block;
vertical-align: middle;
}
.Select-item-label {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
cursor: default;
padding: 2px 5px;
}
.Select-item-label .Select-item-label__a {
color: #08c;
cursor: pointer;
}
.Select-item-icon {
cursor: pointer;
border-bottom-left-radius: 2px;
border-top-left-radius: 2px;
border-right: 1px solid #c9e6f2;
padding: 1px 5px 3px;
}
.Select-item-icon:hover,.Select-item-icon:focus {
background-color: #ddeff7;
color: #0077b3;
}
.Select-item-icon:active {
background-color: #c9e6f2;
}
.Select--multi.is-disabled .Select-item {
background-color: #f2f2f2;
border: 1px solid #d9d9d9;
color: #888;
}
.Select--multi.is-disabled .Select-item-icon {
cursor: not-allowed;
border-right: 1px solid #d9d9d9;
}
.Select--multi.is-disabled .Select-item-icon:hover,.Select--multi.is-disabled .Select-item-icon:focus,.Select--multi.is-disabled .Select-item-icon:active {
background-color: #f2f2f2;
}
@keyframes Select-animation-spin {
to {
transform: rotate(1turn);
}
}
@-webkit-keyframes Select-animation-spin {
to {
-webkit-transform: rotate(1turn);
}
}
/*
* This file contains Global styles.
*
* In general, your styles should *not* be in this file, but in the individual
* component files. For details, see the Pacomo specification:
*
* https://github.com/unicorn-standard/pacomo
*/
* {
box-sizing: border-box;
margin: 0;
}
*:before,*:after {
box-sizing: border-box;
}
html,body,main {
position: relative;
height: 100%;
min-height: 100%;
font-family: Roboto;
}
body {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
input,button,select,textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
#react-app {
position: relative;
height: 100%;
min-height: 100%;
}
body {
padding-bottom: 50px;
/* height: 100%; */
/* min-height: 100%; */
height: auto;
}
.footer-navigation {
height: 1px;
}
.footer-navigation > .container {
height: 100%;
}
.footer-navigation > .container > * {
top: 50%;
transform: translateY(-50%);
}
.page-header {
padding-bottom: 9px;
margin: 0px 0 20px;
border-bottom: 1px solid #eee;
}
h1 {
margin-top: .5em;
}
/*# sourceMappingURL=style.6d7a32b1405ea1bb2bdf.css.map*/

View File

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

View File

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

View File

@@ -1 +0,0 @@
{"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":""}

View File

@@ -1,334 +0,0 @@
/**
* React Select
* ============
* Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/
* https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs
* MIT License: https://github.com/keystonejs/react-select
*/
.Select {
position: relative;
}
.Select,
.Select div,
.Select input,
.Select span {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.Select.is-disabled > .Select-control {
background-color: #f9f9f9;
}
.Select.is-disabled > .Select-control:hover {
box-shadow: none;
}
.Select.is-disabled .Select-arrow-zone {
cursor: default;
pointer-events: none;
}
.Select-control {
background-color: #fff;
border-color: #d9d9d9 #ccc #b3b3b3;
border-radius: 4px;
border: 1px solid #ccc;
color: #333;
cursor: default;
display: table;
height: 36px;
outline: none;
overflow: hidden;
position: relative;
width: 100%;
}
.Select-control:hover {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.is-searchable.is-open > .Select-control {
cursor: text;
}
.is-open > .Select-control {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
background: #fff;
border-color: #b3b3b3 #ccc #d9d9d9;
}
.is-open > .Select-control > .Select-arrow {
border-color: transparent transparent #999;
border-width: 0 5px 5px;
}
.is-searchable.is-focused:not(.is-open) > .Select-control {
cursor: text;
}
.is-focused:not(.is-open) > .Select-control {
border-color: #007eff;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px rgba(0, 126, 255, 0.1);
}
.Select-placeholder,
:not(.Select--multi) > .Select-control .Select-value {
bottom: 0;
color: #aaa;
left: 0;
line-height: 34px;
padding-left: 10px;
padding-right: 10px;
position: absolute;
right: 0;
top: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.has-value:not(.Select--multi) > .Select-control > .Select-value .Select-value-label,
.has-value.is-pseudo-focused:not(.Select--multi) > .Select-control > .Select-value .Select-value-label {
color: #333;
}
.has-value:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label,
.has-value.is-pseudo-focused:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label {
cursor: pointer;
text-decoration: none;
}
.has-value:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label:hover,
.has-value.is-pseudo-focused:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label:hover,
.has-value:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label:focus,
.has-value.is-pseudo-focused:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label:focus {
color: #007eff;
outline: none;
text-decoration: underline;
}
.Select-input {
height: 34px;
padding-left: 10px;
padding-right: 10px;
vertical-align: middle;
}
.Select-input > input {
background: none transparent;
border: 0 none;
box-shadow: none;
cursor: default;
display: inline-block;
font-family: inherit;
font-size: inherit;
height: 34px;
margin: 0;
outline: none;
padding: 0;
-webkit-appearance: none;
}
.is-focused .Select-input > input {
cursor: text;
}
.has-value.is-pseudo-focused .Select-input {
opacity: 0;
}
.Select-control:not(.is-searchable) > .Select-input {
outline: none;
}
.Select-loading-zone {
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 16px;
}
.Select-loading {
-webkit-animation: Select-animation-spin 400ms infinite linear;
-o-animation: Select-animation-spin 400ms infinite linear;
animation: Select-animation-spin 400ms infinite linear;
width: 16px;
height: 16px;
box-sizing: border-box;
border-radius: 50%;
border: 2px solid #ccc;
border-right-color: #333;
display: inline-block;
position: relative;
vertical-align: middle;
}
.Select-clear-zone {
-webkit-animation: Select-animation-fadeIn 200ms;
-o-animation: Select-animation-fadeIn 200ms;
animation: Select-animation-fadeIn 200ms;
color: #999;
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 17px;
}
.Select-clear-zone:hover {
color: #D0021B;
}
.Select-clear {
display: inline-block;
font-size: 18px;
line-height: 1;
}
.Select--multi .Select-clear-zone {
width: 17px;
}
.Select-arrow-zone {
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 25px;
padding-right: 5px;
}
.Select-arrow {
border-color: #999 transparent transparent;
border-style: solid;
border-width: 5px 5px 2.5px;
display: inline-block;
height: 0;
width: 0;
}
.is-open .Select-arrow,
.Select-arrow-zone:hover > .Select-arrow {
border-top-color: #666;
}
@-webkit-keyframes Select-animation-fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes Select-animation-fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.Select-menu-outer {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
background-color: #fff;
border: 1px solid #ccc;
border-top-color: #e6e6e6;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
box-sizing: border-box;
margin-top: -1px;
max-height: 200px;
position: absolute;
top: 100%;
width: 100%;
z-index: 1;
-webkit-overflow-scrolling: touch;
}
.Select-menu {
max-height: 198px;
overflow-y: auto;
}
.Select-option {
box-sizing: border-box;
background-color: #fff;
color: #666666;
cursor: pointer;
display: block;
padding: 8px 10px;
}
.Select-option:last-child {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
.Select-option.is-focused {
background-color: rgba(0, 126, 255, 0.08);
color: #333;
}
.Select-option.is-disabled {
color: #cccccc;
cursor: default;
}
.Select-noresults {
box-sizing: border-box;
color: #999999;
cursor: default;
display: block;
padding: 8px 10px;
}
.Select--multi .Select-input {
vertical-align: middle;
margin-left: 10px;
padding: 0;
}
.Select--multi.has-value .Select-input {
margin-left: 5px;
}
.Select--multi .Select-value {
background-color: rgba(0, 126, 255, 0.08);
border-radius: 2px;
border: 1px solid rgba(0, 126, 255, 0.24);
color: #007eff;
display: inline-block;
font-size: 0.9em;
line-height: 1.4;
margin-left: 5px;
margin-top: 5px;
vertical-align: top;
}
.Select--multi .Select-value-icon,
.Select--multi .Select-value-label {
display: inline-block;
vertical-align: middle;
}
.Select--multi .Select-value-label {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
cursor: default;
padding: 2px 5px;
}
.Select--multi a.Select-value-label {
color: #007eff;
cursor: pointer;
text-decoration: none;
}
.Select--multi a.Select-value-label:hover {
text-decoration: underline;
}
.Select--multi .Select-value-icon {
cursor: pointer;
border-bottom-left-radius: 2px;
border-top-left-radius: 2px;
border-right: 1px solid rgba(0, 126, 255, 0.24);
padding: 1px 5px 3px;
}
.Select--multi .Select-value-icon:hover,
.Select--multi .Select-value-icon:focus {
background-color: rgba(0, 113, 230, 0.08);
color: #0071e6;
}
.Select--multi .Select-value-icon:active {
background-color: rgba(0, 126, 255, 0.24);
}
.Select--multi.is-disabled .Select-value {
background-color: #fcfcfc;
border: 1px solid #e3e3e3;
color: #333;
}
.Select--multi.is-disabled .Select-value-icon {
cursor: not-allowed;
border-right: 1px solid #e3e3e3;
}
.Select--multi.is-disabled .Select-value-icon:hover,
.Select--multi.is-disabled .Select-value-icon:focus,
.Select--multi.is-disabled .Select-value-icon:active {
background-color: #fcfcfc;
}
@keyframes Select-animation-spin {
to {
transform: rotate(1turn);
}
}
@-webkit-keyframes Select-animation-spin {
to {
-webkit-transform: rotate(1turn);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,217 +0,0 @@
/**
* Created by andrew on 8/18/16.
*/
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PurifyCSSPlugin = require('purifycss-webpack-plugin');
exports.devServer = function(options) {
return {
entry: {
'webpack-dev-server': 'webpack-dev-server/client?http://localhost:8080',
hmr: 'webpack/hot/only-dev-server'
},
devServer: {
contentBase: './build',
hot: true,
historyApiFallback: true,
inline: true,
stats: 'errors-only',
host: options.host, // Defaults to `localhost`
port: options.port, // Defaults to 8080
proxy: {
'/api*' : {
target: 'http://localhost:8080'
}
// '/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'
// }
}
},
watchOptions: {
// Delay the rebuild after the first change
aggregateTimeout: 300,
// Poll using interval (in ms, accepts boolean too)
poll: 1000
},
plugins: [
// Enable multi-pass compilation for enhanced performance
// in larger projects. Good default.
new webpack.HotModuleReplacementPlugin({
multiStep: true
})
]
};
};
exports.setupCSS = function(paths) {
return {
module: {
loaders: [
{
test: /\.css$/,
loaders: ['style', 'css'],
include: paths
}
]
}
};
};
exports.setupLess = function(paths) {
return {
module: {
loaders: [
{
test: /\.(le)|(c)ss$/,
loaders: ['style', 'css', 'less'],
include: paths
}
]
}
};
};
exports.useJSON = function() {
return {
module: {
loaders: [
{
test: /\.json$/,
loaders: ['json']
}
]
}
};
};
exports.minify = function() {
return {
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
};
};
exports.extractBundle = function(options) {
const entry = {};
entry[options.name] = options.entries;
return {
// Define an entry point needed for splitting.
entry: entry,
plugins: [
// Extract bundle and manifest files. Manifest is
// needed for reliable caching.
new webpack.optimize.CommonsChunkPlugin({
names: [options.name, 'manifest']
})
]
};
};
exports.clean = function(path) {
return {
plugins: [
new CleanWebpackPlugin([path], {
// Without `root` CleanWebpackPlugin won't point to our
// project and will fail to work.
root: process.cwd()
})
]
};
};
exports.extractCSS = function(paths) {
return {
module: {
loaders: [
// Extract CSS during build
{
test: /\.css$/,
// loaders: ['style', 'css'],
loader: ExtractTextPlugin.extract('style', 'css'),
include: paths
}
]
},
plugins: [
// Output extracted CSS to a file
new ExtractTextPlugin('[name].[chunkhash].css')
]
};
};
exports.extractLESS = function(paths) {
return {
module: {
loaders: [
// Extract CSS during build
{
test: /\.(le)|(c)ss$/,
// loaders: ['style', 'css', 'less'],
loader: ExtractTextPlugin.extract(
"style-loader",
'css?sourceMap!' +
'less?sourceMap'
),
include: paths
}
]
},
plugins: [
// Output extracted CSS to a file
new ExtractTextPlugin('[name].[chunkhash].css')
]
};
};
exports.purifyCSS = function(paths) {
return {
plugins: [
new PurifyCSSPlugin({
basePath: process.cwd(),
// `paths` is used to point PurifyCSS to files not
// visible to Webpack. You can pass glob patterns
// to it.
paths: paths,
purifyOptions: {
// minify: false,
// info: true,
// output: './output.css'
}
})
]
}
};
exports.useJQuery = function() {
return {
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery"
})
]
};
};

View File

@@ -1,7 +1,7 @@
{
"name": "event-sourcing-examples",
"description": "ES Money Transfer App",
"author": "cer",
"name": "",
"description": "",
"author": "",
"private": true,
"version": "0.1.0",
"license": "MIT",
@@ -9,26 +9,20 @@
"scripts": {
"start": "gulp serve",
"open": "gulp open",
"gulp": "gulp",
"build": "webpack",
"watch": "webpack --progress --colors --watch --display-error-details --display-chunks --profile",
"start-dev": "export PORT=3000 && webpack-dev-server --host 0.0.0.0"
"gulp": "gulp"
},
"devDependencies": {
"autoprefixer-loader": "^2.0.0",
"babel-core": "6.1.4",
"babel-loader": "6.1.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",
"babel-loader": "^6.2.4",
"babel-polyfill": "^6.2.0",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.1.2",
"clean-webpack-plugin": "^0.1.10",
"copy-webpack-plugin": "^3.0.1",
"babel-plugin-transform-runtime": "6.1.4",
"babel-polyfill": "^6.1.4",
"babel-preset-es2015": "6.1.4",
"babel-preset-react": "6.1.4",
"babel-preset-stage-0": "6.1.2",
"babel-register": "6.1.4",
"babel-runtime": "^6.0.14",
"css-loader": "^0.14.4",
"del": "^1.2.0",
"extract-text-webpack-plugin": "^0.8.1",
@@ -41,22 +35,17 @@
"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",
"less-loader": "^2.2.0",
"node-libs-browser": "^0.5.2",
"open": "0.0.5",
"purifycss-webpack-plugin": "^2.0.3",
"react-hot-loader": "^1.3.0",
"redux-devtools": "^2.1.5",
"run-sequence": "^1.1.0",
"style-loader": "^0.12.3",
"url-loader": "^0.5.6",
"webpack": "^1.9.10",
"webpack-dev-server": "^1.9.0",
"webpack-merge": "^0.14.1",
"webpack-validator": "^2.2.7"
"webpack-dev-server": "^1.9.0"
},
"dependencies": {
"classnames": "^2.2.3",
@@ -70,10 +59,10 @@
"react": "^0.14.7",
"react-bootstrap": "^0.28.3",
"react-dom": "^0.14.0",
"react-loader": "^2.4.0",
"react-loader": "^2.0.0",
"react-pacomo": "^0.5.1",
"react-redux": "^4.4.0",
"react-router": "^2.7.0",
"react-router": "^2.0.0-rc2",
"react-router-bootstrap": "^0.20.1",
"react-router-redux": "^3.0.0",
"react-select": "^0.9.1",

View File

@@ -1,81 +0,0 @@
<!DOCTYPE html>
<!--[if lt IE 7 ]> <html lang="en" class="ie6" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 7 ]> <html lang="en" class="ie7" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 8 ]> <html lang="en" class="ie8" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if IE 9 ]> <html lang="en" class="ie9" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <![endif]-->
<!--[if (gt IE 9)|!(IE)]><!--> <html lang="en" class="" <% if(htmlWebpackPlugin.files.manifest) { %> manifest="<%= htmlWebpackPlugin.files.manifest %>"<% } %>> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title><%= htmlWebpackPlugin.options.title || 'Webpack App'%></title><%
if (htmlWebpackPlugin.options.description) {
%><meta name="description" content="<%= htmlWebpackPlugin.options.description%>" /><%
} %><%
if (htmlWebpackPlugin.files.favicon) {
%><link rel="shortcut icon" href="<%= htmlWebpackPlugin.files.favicon%>"><%
} %><%
if (htmlWebpackPlugin.options.mobile) {
%><meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" /><%
} %><!-- Latest compiled and minified CSS -->
<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"><%
for (var css in htmlWebpackPlugin.files.css) {
%><link href="<%= htmlWebpackPlugin.files.css[css] %>" rel="stylesheet"><%
} %><%
if (htmlWebpackPlugin.options.baseHref) {
%><base href="<%= htmlWebpackPlugin.options.baseHref %>" /><%
} %></head>
<body><%
if (htmlWebpackPlugin.options.unsupportedBrowser) {
%><style>.unsupported-browser { display: none; }</style>
<div class="unsupported-browser">
Sorry, your browser is not supported. Please upgrade to
the latest version or switch your browser to use this site.
See <a href="http://outdatedbrowser.com/">outdatedbrowser.com</a>
for options.
</div><%
} %><%
if (htmlWebpackPlugin.options.appMountId) {
%><div id="<%= htmlWebpackPlugin.options.appMountId%>"></div><%
} %><%
if (htmlWebpackPlugin.options.appMountIds && htmlWebpackPlugin.options.appMountIds.length > 0) {
%><%
for (var index in htmlWebpackPlugin.options.appMountIds) {
%><div id="<%= htmlWebpackPlugin.options.appMountIds[index]%>"></div><%
} %><%
} %><%
if (htmlWebpackPlugin.options.window) {
%><script>
<%
for (var varName in htmlWebpackPlugin.options.window) { %>window['<%=varName%>'] = <%= JSON.stringify(htmlWebpackPlugin.options.window[varName]) %>;<%
} %>
</script><%
} %><%
for (var chunk in htmlWebpackPlugin.files.chunks) {
%><script src="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>"></script><%
} %><%
if (htmlWebpackPlugin.options.devServer) {
%><script src="<%= htmlWebpackPlugin.options.devServer%>/webpack-dev-server.js"></script><%
} %><%
if (htmlWebpackPlugin.options.googleAnalytics) {
%><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)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
<% if (htmlWebpackPlugin.options.googleAnalytics.trackingId) { %>
ga('create', '<%= htmlWebpackPlugin.options.googleAnalytics.trackingId%>', 'auto');
<% } else { throw new Error("html-webpack-template requires googleAnalytics.trackingId config"); }%>
<% if (htmlWebpackPlugin.options.googleAnalytics.pageViewOnLoad) { %>
ga('send', 'pageview');
<% } %>
</script><%
}
%><!--<%= JSON.stringify(htmlWebpackPlugin)%>--></body>
</html>

View File

@@ -1,5 +0,0 @@
# www.robotstxt.org/
# Allow crawling of all content
User-agent: *
Disallow:

View File

@@ -1,334 +0,0 @@
/**
* React Select
* ============
* Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/
* https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs
* MIT License: https://github.com/keystonejs/react-select
*/
.Select {
position: relative;
}
.Select,
.Select div,
.Select input,
.Select span {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.Select.is-disabled > .Select-control {
background-color: #f9f9f9;
}
.Select.is-disabled > .Select-control:hover {
box-shadow: none;
}
.Select.is-disabled .Select-arrow-zone {
cursor: default;
pointer-events: none;
}
.Select-control {
background-color: #fff;
border-color: #d9d9d9 #ccc #b3b3b3;
border-radius: 4px;
border: 1px solid #ccc;
color: #333;
cursor: default;
display: table;
height: 36px;
outline: none;
overflow: hidden;
position: relative;
width: 100%;
}
.Select-control:hover {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.is-searchable.is-open > .Select-control {
cursor: text;
}
.is-open > .Select-control {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
background: #fff;
border-color: #b3b3b3 #ccc #d9d9d9;
}
.is-open > .Select-control > .Select-arrow {
border-color: transparent transparent #999;
border-width: 0 5px 5px;
}
.is-searchable.is-focused:not(.is-open) > .Select-control {
cursor: text;
}
.is-focused:not(.is-open) > .Select-control {
border-color: #007eff;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 0 3px rgba(0, 126, 255, 0.1);
}
.Select-placeholder,
:not(.Select--multi) > .Select-control .Select-value {
bottom: 0;
color: #aaa;
left: 0;
line-height: 34px;
padding-left: 10px;
padding-right: 10px;
position: absolute;
right: 0;
top: 0;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.has-value:not(.Select--multi) > .Select-control > .Select-value .Select-value-label,
.has-value.is-pseudo-focused:not(.Select--multi) > .Select-control > .Select-value .Select-value-label {
color: #333;
}
.has-value:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label,
.has-value.is-pseudo-focused:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label {
cursor: pointer;
text-decoration: none;
}
.has-value:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label:hover,
.has-value.is-pseudo-focused:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label:hover,
.has-value:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label:focus,
.has-value.is-pseudo-focused:not(.Select--multi) > .Select-control > .Select-value a.Select-value-label:focus {
color: #007eff;
outline: none;
text-decoration: underline;
}
.Select-input {
height: 34px;
padding-left: 10px;
padding-right: 10px;
vertical-align: middle;
}
.Select-input > input {
background: none transparent;
border: 0 none;
box-shadow: none;
cursor: default;
display: inline-block;
font-family: inherit;
font-size: inherit;
height: 34px;
margin: 0;
outline: none;
padding: 0;
-webkit-appearance: none;
}
.is-focused .Select-input > input {
cursor: text;
}
.has-value.is-pseudo-focused .Select-input {
opacity: 0;
}
.Select-control:not(.is-searchable) > .Select-input {
outline: none;
}
.Select-loading-zone {
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 16px;
}
.Select-loading {
-webkit-animation: Select-animation-spin 400ms infinite linear;
-o-animation: Select-animation-spin 400ms infinite linear;
animation: Select-animation-spin 400ms infinite linear;
width: 16px;
height: 16px;
box-sizing: border-box;
border-radius: 50%;
border: 2px solid #ccc;
border-right-color: #333;
display: inline-block;
position: relative;
vertical-align: middle;
}
.Select-clear-zone {
-webkit-animation: Select-animation-fadeIn 200ms;
-o-animation: Select-animation-fadeIn 200ms;
animation: Select-animation-fadeIn 200ms;
color: #999;
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 17px;
}
.Select-clear-zone:hover {
color: #D0021B;
}
.Select-clear {
display: inline-block;
font-size: 18px;
line-height: 1;
}
.Select--multi .Select-clear-zone {
width: 17px;
}
.Select-arrow-zone {
cursor: pointer;
display: table-cell;
position: relative;
text-align: center;
vertical-align: middle;
width: 25px;
padding-right: 5px;
}
.Select-arrow {
border-color: #999 transparent transparent;
border-style: solid;
border-width: 5px 5px 2.5px;
display: inline-block;
height: 0;
width: 0;
}
.is-open .Select-arrow,
.Select-arrow-zone:hover > .Select-arrow {
border-top-color: #666;
}
@-webkit-keyframes Select-animation-fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes Select-animation-fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.Select-menu-outer {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
background-color: #fff;
border: 1px solid #ccc;
border-top-color: #e6e6e6;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
box-sizing: border-box;
margin-top: -1px;
max-height: 200px;
position: absolute;
top: 100%;
width: 100%;
z-index: 1;
-webkit-overflow-scrolling: touch;
}
.Select-menu {
max-height: 198px;
overflow-y: auto;
}
.Select-option {
box-sizing: border-box;
background-color: #fff;
color: #666666;
cursor: pointer;
display: block;
padding: 8px 10px;
}
.Select-option:last-child {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
.Select-option.is-focused {
background-color: rgba(0, 126, 255, 0.08);
color: #333;
}
.Select-option.is-disabled {
color: #cccccc;
cursor: default;
}
.Select-noresults {
box-sizing: border-box;
color: #999999;
cursor: default;
display: block;
padding: 8px 10px;
}
.Select--multi .Select-input {
vertical-align: middle;
margin-left: 10px;
padding: 0;
}
.Select--multi.has-value .Select-input {
margin-left: 5px;
}
.Select--multi .Select-value {
background-color: rgba(0, 126, 255, 0.08);
border-radius: 2px;
border: 1px solid rgba(0, 126, 255, 0.24);
color: #007eff;
display: inline-block;
font-size: 0.9em;
line-height: 1.4;
margin-left: 5px;
margin-top: 5px;
vertical-align: top;
}
.Select--multi .Select-value-icon,
.Select--multi .Select-value-label {
display: inline-block;
vertical-align: middle;
}
.Select--multi .Select-value-label {
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
cursor: default;
padding: 2px 5px;
}
.Select--multi a.Select-value-label {
color: #007eff;
cursor: pointer;
text-decoration: none;
}
.Select--multi a.Select-value-label:hover {
text-decoration: underline;
}
.Select--multi .Select-value-icon {
cursor: pointer;
border-bottom-left-radius: 2px;
border-top-left-radius: 2px;
border-right: 1px solid rgba(0, 126, 255, 0.24);
padding: 1px 5px 3px;
}
.Select--multi .Select-value-icon:hover,
.Select--multi .Select-value-icon:focus {
background-color: rgba(0, 113, 230, 0.08);
color: #0071e6;
}
.Select--multi .Select-value-icon:active {
background-color: rgba(0, 126, 255, 0.24);
}
.Select--multi.is-disabled .Select-value {
background-color: #fcfcfc;
border: 1px solid #e3e3e3;
color: #333;
}
.Select--multi.is-disabled .Select-value-icon {
cursor: not-allowed;
border-right: 1px solid #e3e3e3;
}
.Select--multi.is-disabled .Select-value-icon:hover,
.Select--multi.is-disabled .Select-value-icon:focus,
.Select--multi.is-disabled .Select-value-icon:active {
background-color: #fcfcfc;
}
@keyframes Select-animation-spin {
to {
transform: rotate(1turn);
}
}
@-webkit-keyframes Select-animation-spin {
to {
-webkit-transform: rotate(1turn);
}
}

View File

@@ -5,29 +5,47 @@
import React from "react";
import { createStore, compose, applyMiddleware, combineReducers} from "redux";
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 { ReduxRouter} from "redux-router";
//import { Router, IndexRoute, Route, browserHistory } from 'react-router';
//import { syncHistory, routeReducer } from 'react-router-redux';
//import { configure as reduxAuthConfigure, authStateReducer } from "redux-auth";
//import { authStateReducer } from "redux-auth";
//import authStateReducer from './reducers/auth';
//import appStateReducer from './reducers/data';
import mainReducer from './reducers';
import { configure as reduxAuthConfigure } from './actions/configure';
//import { AuthGlobals } from "redux-auth/bootstrap-theme";
import { createHistory, createHashHistory, createMemoryHistory } from "history";
import { pushState, routerStateReducer, reduxReactRouter as clientRouter} from "redux-router";
import { reduxReactRouter as serverRouter } from "redux-router/server";
import mainReducer from './reducers';
import { configure as endpointsConfig } from './actions/configure';
import { requireAuthentication } from './components/AuthComponent';
//import demoButtons from "./reducers/request-test-buttons";
//import demoUi from "./reducers/demo-ui";
import Container from "./components/partials/Container";
import MyAccounts from "./views/MyAccounts";
import Account from "./views/Account";
import SignIn from "./views/SignIn";
import SignUp from "./views/SignUp";
//import GlobalComponents from "./views/partials/GlobalComponents";
class App extends React.Component {
render() {
return (<Container>
return (
<Container>
{this.props.children}
</Container>);
</Container>
);
}
}
@@ -64,15 +82,15 @@ export function initialize({cookies, isServer, currentLocation, userAgent} = {})
/**
* The React Router 1.0 routes for both the server and the client.
*/
return store.dispatch(endpointsConfig([
return store.dispatch(reduxAuthConfigure([
{
default: {
//apiUrl: '/',
emailSignInPath: '/api/login',
customersPath: '/api/customers',
currentUserPath: '/api/user',
accountsPath: '/api/accounts',
transfersPath: '/api/transfers'
emailSignInPath: '/login',
customersPath: '/customers',
currentUserPath: '/user',
accountsPath: '/accounts',
transfersPath: '/transfers'
}
}
], {

View File

@@ -2,41 +2,65 @@
* Created by andrew on 26/02/16.
*/
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';
import {
getCurrentSettings,
setCurrentSettings,
getInitialEndpointKey,
setDefaultEndpointKey,
setCurrentEndpoint,
setCurrentEndpointKey,
retrieveData,
persistData,
destroySession,
persistUserData,
retrieveUserData,
retrieveHeaders
} from "../utils/sessionStorage";
import {
apiGetCurrentUser
} from '../utils/api';
import {entityReceived } from './entities';
export function authenticateStart() {
return { type: T.AUTH.AUTHENTICATE_START };
}
export function authenticateComplete(user) {
return { type: T.AUTH.AUTHENTICATE_COMPLETE, user };
}
export function authenticateError(errors) {
return { type: T.AUTH.AUTHENTICATE_ERROR, errors };
}
export const authenticateStart = makeActionCreator(T.AUTH.AUTHENTICATE_START);
export const authenticateComplete = makeActionCreator(T.AUTH.AUTHENTICATE_COMPLETE, 'user');
export const authenticateError = makeActionCreator(T.AUTH.AUTHENTICATE_ERROR, 'errors');
export function authenticate(forceReread) {
return (dispatch) => {
return dispatch => {
dispatch(authenticateStart());
const savedUserPromise = new Promise((rs, rj) => {
const currentHeaders = U.retrieveHeaders();
const currentHeaders = retrieveHeaders();
const accessToken = currentHeaders["access-token"];
if (!accessToken) {
return rj({ reason: 'no token'});
}
const savedUser = U.retrieveUserData();
const savedUser = retrieveUserData();
if (savedUser && !forceReread) {
return rs(savedUser);
}
return apiGetCurrentUser().then((userData) => {
U.persistUserData(userData);
persistUserData(userData);
dispatch(entityReceived(userData.id, userData));
rs(userData);
}, (err) => {
debugger;
rj(err);
});

View File

@@ -5,7 +5,6 @@ import T from '../constants/ACTION_TYPES';
import { makeActionCreator } from '../utils/actions';
import * as api from '../utils/api';
import { authenticate } from './authenticate';
import root from '../utils/root';
export const entityRequested = makeActionCreator(T.ENTITIES.REQUESTED, 'id');
export const entityReceived = makeActionCreator(T.ENTITIES.RECEIVED, 'id', 'entity');
@@ -45,47 +44,18 @@ export function accountsList(userId) {
};
}
function readUntilChanged(initialData, customerId) {
const initialDataFlat = root['JSON'].stringify(initialData);
debugger;
return new Promise((rs, rj) => {
setTimeout(() => {
api.apiRetrieveAccounts(customerId)
.then(data => {
debugger;
if (initialDataFlat == root['JSON'].stringify(data)) {
return readUntilChanged.call(this, data, customerId).then(rs, rj); // Promise
}
rs(data);
})
.catch(rj)
}, 500);
})
}
export function accountCreate(customerId, payload) {
return dispatch => {
dispatch(accountCreateStart());
return api.apiCreateAccount(customerId, payload)
.then(data => {
if (data.accountId) {
const { accountId } = data;
dispatch(accountCreateComplete({
id: accountId,
...payload
}));
// dispatch(entityReceived(accountId, payload));
dispatch(authenticate(true));
return accountId;
} else {
return readUntilChanged(data, customerId)
.then(() => {
dispatch(accountCreateComplete({
id: ''
}));
dispatch(authenticate(true));
});
}
.then(({ accountId }) => {
dispatch(accountCreateComplete({
id: accountId,
...payload
}));
// dispatch(entityReceived(accountId, payload));
dispatch(authenticate(true));
return accountId;
})
.catch(err => {
debugger;
@@ -123,7 +93,7 @@ export function fetchOwnAccounts(customerId) {
//dispatch(accountsListRequested());
return api.apiRetrieveAccounts(customerId)
.then(data => {
dispatch(accountsListReceived(data.accounts));
dispatch(accountsListReceived(data));
});
};
}
@@ -247,20 +217,20 @@ export const makeTransfer = (accountId, payload) => {
};
};
export const getTransfersRequested = makeActionCreator(T.TRANSFERS.LIST_START, 'id');
export const getTransfersComplete = makeActionCreator(T.TRANSFERS.LIST_COMPLETE, 'id', 'payload');
export const getTransfersError = makeActionCreator(T.TRANSFERS.LIST_ERROR, 'id', 'error');
export const getTransfersRequested = makeActionCreator(T.TRANSFERS.LIST_START);
export const getTransfersComplete = makeActionCreator(T.TRANSFERS.LIST_COMPLETE, 'payload');
export const getTransfersError = makeActionCreator(T.TRANSFERS.LIST_ERROR, 'error');
export const getTransfers = (accountId) => {
return dispatch => {
dispatch(getTransfersRequested(accountId));
dispatch(getTransfersRequested());
return api.apiRetrieveTransfers(accountId)
.then(data => {
dispatch(getTransfersComplete(accountId, data.transactionsHistory));
dispatch(getTransfersComplete(data));
return data;
})
.catch(err => {
dispatch(getTransfersError(accountId, err));
dispatch(getTransfersError(err));
return err;
});
};

View File

@@ -4,10 +4,12 @@
import React from "react";
import ReactDOM from "react-dom";
import { initialize } from "./app";
/**
* Fire-up React Router.
*/
const reactRoot = window.document.getElementById("root");
const reactRoot = window.document.getElementById("react-app");
initialize().then(({ provider }) => {
ReactDOM.render(provider, reactRoot);
});

View File

@@ -4,9 +4,8 @@
import React from "react";
import { connect } from 'react-redux';
import Spinner from "react-loader";
// import * as BS from "react-bootstrap";
import * as BS from "react-bootstrap";
import * as A from '../actions/entities';
import { Route, IndexRoute, Link, IndexLink } from "react-router";
// import { Money } from '../components/Money';
@@ -33,15 +32,15 @@ export class AccountInfo extends React.Component {
const account = entities[accountId];
if (!account) {
return (<Link to={ `/account/${accountId}` }>{ accountId } <Spinner loaded={false} /></Link>)
return (<div>{ accountId } <Spinner ref="spinner" loaded={false} /></div>)
}
const { title } = account;
return (<Link to={ `/account/${accountId}` }>{ title }</Link>);
return (<div>{ title } </div>);
}
}
export default connect(({ app }) => ({
entities: app.data.entities
}))(AccountInfo);
}))(AccountInfo);

View File

@@ -11,99 +11,49 @@ import AccountInfo from './AccountInfo';
export class TransfersTable extends React.Component {
render() {
const { transfers, forAccount } = this.props;
const { loading, data, errors } = transfers || {};
if (!transfers || loading) {
return (<h2><Spinner loaded={false} /> Loading..</h2>);
}
const { loading, data, errors } = this.props;
if (loading) {
return (<h2><Spinner ref="spinner" loaded={false} /> Loading..</h2>);
}
if (Object.keys(errors).length) {
return (<div className="text-danger">Errors..</div>);
}
const currentAccountId = forAccount;
const transfersMarkup = data.length ?
data
.sort((a, b) => ((a.date - b.date)))
.filter(({ entryType, toAccountId, fromAccountId}) => ((entryType !=='transaction') || (fromAccountId === currentAccountId) || (toAccountId === currentAccountId)))
.reduce(({
items, balance
}, v) => {
if (v.entryType == 'account') {
balance = v.initialBalance;
} else if (v.entryType == 'transaction') {
const isOriginating = v.fromAccountId == currentAccountId;
balance += (isOriginating ? -1 : 1) * v.amount;
}
v.balance = balance;
items.push(v);
return { items, balance };
}, {
items: [],
balance: 0
}).items
.sort((a, b) => (-(a.date - b.date)))
.map(({
entryType,
amount,
fromAccountId,
toAccountId,
transactionId,
description = '—',
date = null,
status = '—',
balance,
initialBalance = null
}) => {
const transfers = data.length ? data.map(({
amount,
fromAccountId,
toAccountId,
transactionId,
description = '',
date = null,
status = ''
}, idx) => (<tr key={idx}>
<td><TimeAgo date={date} /></td>
<td><AccountInfo accountId={ fromAccountId } /></td>
<td><AccountInfo accountId={ toAccountId } /></td>
<td><Money amount={ amount } /></td>
<td>{ description || 'N/a'}</td>
<td>{ status || 'N/a' }</td>
</tr>)) : (<tr>
<td colSpan={6}>No transfers for this account just yet.</td>
</tr>);
const transferTimestamp = new Date(date);
const timeAgoTitle = transferTimestamp.toLocaleDateString() + ' ' + transferTimestamp.toLocaleTimeString();
if (entryType == 'account') {
return (<tr>
<td><TimeAgo date={date} title={ timeAgoTitle } /></td>
<td colSpan="3">Account created</td>
<td><Money amount={ initialBalance } /></td>
<td></td>
<td>{ status || '—' }</td>
</tr>);
}
const isOriginating = fromAccountId == currentAccountId;
const directionMarkup = isOriginating ? 'Debit' : 'Credit';
const counterAccountMarkup = isOriginating ?
<AccountInfo accountId={ toAccountId } /> :
<AccountInfo accountId={ fromAccountId } />;
return (<tr>
<td><TimeAgo date={date} title={ timeAgoTitle } /></td>
<td>{ directionMarkup }</td>
<td>{ counterAccountMarkup }</td>
<td><Money amount={ amount } /></td>
<td><Money amount={ balance } /></td>
<td>{ description || '—' }</td>
<td>{ status || '—' }</td>
</tr>);
}) : (<tr>
<td colSpan={6}>No transfers for this account just yet.</td>
</tr>);
return (
<BS.Table striped bordered condensed hover>
<thead>
<tr>
<th>Date</th>
<th>Type</th>
<th>Other Account</th>
<th>What</th>
<th>Counter Account</th>
<th>Amount</th>
<th>Balance</th>
<th>Description</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{ transfersMarkup }
{ transfers }
</tbody>
</BS.Table>
);

View File

@@ -11,6 +11,11 @@ import HeaderLinks from '../HeaderLinks';
//const SignOutButton = () => (<div>SignOutButton!</div>);
//if (!global.__SERVER__ && !global.__TEST__) {
// require("../../styles/main.scss");
//}
class Container extends React.Component {
static propTypes = {
children: PropTypes.node

View File

@@ -1,81 +1,81 @@
import { TODO_DEFINE, defineActionType } from '../utils/defineActionTypes'
import defineActionTypes from '../utils/defineActionTypes'
export default defineActionType({
export default defineActionTypes({
/*
* View model
*/
AUTH: {
CONFIGURE_START: TODO_DEFINE,
CONFIGURE_COMPLETE: TODO_DEFINE,
CONFIGURE_ERROR: TODO_DEFINE,
AUTHENTICATE_START: TODO_DEFINE,
AUTHENTICATE_COMPLETE: TODO_DEFINE,
AUTHENTICATE_ERROR: TODO_DEFINE,
SIGN_IN_START: TODO_DEFINE,
SIGN_IN_COMPLETE: TODO_DEFINE,
SIGN_IN_ERROR: TODO_DEFINE,
SIGN_IN_FORM_UPDATE: TODO_DEFINE,
SIGN_UP_START: TODO_DEFINE,
SIGN_UP_COMPLETE: TODO_DEFINE,
SIGN_UP_ERROR: TODO_DEFINE,
SIGN_UP_FORM_UPDATE: TODO_DEFINE,
SIGN_OUT_START: TODO_DEFINE,
SIGN_OUT_COMPLETE: TODO_DEFINE
},
AUTH: `
CONFIGURE_START
CONFIGURE_COMPLETE
CONFIGURE_ERROR
AUTHENTICATE_START
AUTHENTICATE_COMPLETE
AUTHENTICATE_ERROR
SIGN_IN_START
SIGN_IN_COMPLETE
SIGN_IN_ERROR
SIGN_IN_FORM_UPDATE
SIGN_UP_START
SIGN_UP_COMPLETE
SIGN_UP_ERROR
SIGN_UP_FORM_UPDATE
SIGN_OUT_START
SIGN_OUT_COMPLETE
`,
ENTITIES: {
REQUESTED: TODO_DEFINE,
RECEIVED: TODO_DEFINE,
RECEIVED_LIST: TODO_DEFINE
},
ENTITIES: `
REQUESTED
RECEIVED
RECEIVED_LIST
`,
ACCOUNTS: {
LIST_START: TODO_DEFINE,
LIST_COMPLETE: TODO_DEFINE,
LIST_ERROR: TODO_DEFINE,
LIST_REF_START: TODO_DEFINE,
LIST_REF_COMPLETE: TODO_DEFINE,
LIST_REF_ERROR: TODO_DEFINE,
CREATE_START: TODO_DEFINE,
CREATE_COMPLETE: TODO_DEFINE,
CREATE_ERROR: TODO_DEFINE,
CREATE_FORM_UPDATE: TODO_DEFINE,
EDIT_START: TODO_DEFINE,
EDIT_COMPLETE: TODO_DEFINE,
EDIT_ERROR: TODO_DEFINE,
EDIT_FORM_UPDATE: TODO_DEFINE,
CREATE_REF_START: TODO_DEFINE,
CREATE_REF_COMPLETE: TODO_DEFINE,
CREATE_REF_ERROR: TODO_DEFINE,
CREATE_REF_FORM_UPDATE: TODO_DEFINE,
CREATE_REF_OWNER_LOOKUP_START: TODO_DEFINE,
CREATE_REF_OWNER_LOOKUP_COMPLETE: TODO_DEFINE,
CREATE_REF_ACCOUNT_LOOKUP_START: TODO_DEFINE,
CREATE_REF_ACCOUNT_LOOKUP_COMPLETE: TODO_DEFINE
},
ACCOUNTS: `
LIST_START
LIST_COMPLETE
LIST_ERROR
LIST_REF_START
LIST_REF_COMPLETE
LIST_REF_ERROR
CREATE_START
CREATE_COMPLETE
CREATE_ERROR
CREATE_FORM_UPDATE
EDIT_START
EDIT_COMPLETE
EDIT_ERROR
EDIT_FORM_UPDATE
CREATE_REF_START
CREATE_REF_COMPLETE
CREATE_REF_ERROR
CREATE_REF_FORM_UPDATE
CREATE_REF_OWNER_LOOKUP_START
CREATE_REF_OWNER_LOOKUP_COMPLETE
CREATE_REF_ACCOUNT_LOOKUP_START
CREATE_REF_ACCOUNT_LOOKUP_COMPLETE
`,
ACCOUNT: {
SINGLE_START: TODO_DEFINE,
SINGLE_COMPLETE: TODO_DEFINE,
SINGLE_ERROR: TODO_DEFINE,
DELETE_START: TODO_DEFINE,
DELETE_COMPLETE: TODO_DEFINE,
DELETE_ERROR: TODO_DEFINE
},
ACCOUNT: `
SINGLE_START
SINGLE_COMPLETE
SINGLE_ERROR
DELETE_START
DELETE_COMPLETE
DELETE_ERROR
`,
TRANSFERS: {
MAKE_START: TODO_DEFINE,
MAKE_COMPLETE: TODO_DEFINE,
MAKE_ERROR: TODO_DEFINE,
MAKE_FORM_UPDATE: TODO_DEFINE,
LIST_START: TODO_DEFINE,
LIST_COMPLETE: TODO_DEFINE,
LIST_ERROR: TODO_DEFINE
},
TRANSFERS: `
MAKE_START
MAKE_COMPLETE
MAKE_ERROR
MAKE_FORM_UPDATE
LIST_START
LIST_COMPLETE
LIST_ERROR
`,
ERROR: {
START:TODO_DEFINE,
STOP:TODO_DEFINE
}
ERROR: `
START
STOP
`
})

View File

@@ -40,7 +40,7 @@ class ButtonLoader extends React.Component {
? this.props.spinColorDark
: this.props.spinColorLight;
icon = <Spinner {...this.props.spinConfig} color={spinColor} loaded={false} />;
icon = <Spinner ref="spinner" {...this.props.spinConfig} color={spinColor} loaded={false} />;
} else {
icon = this.props.icon;
}

View File

@@ -5,8 +5,7 @@ import { batchedSubscribe } from 'redux-batched-subscribe'
import * as navigation from './actions/navigation'
import actors from './actors'
import rootReducer from './reducers';
import { blocked } from './utils/blockedExecution';
import rootReducer from './reducers'
// Add middleware to allow our action creators to return functions and arrays
@@ -26,16 +25,23 @@ const store = createStoreWithBatching(rootReducer)
// Handle changes to our store with a list of actor functions, but ensure
// that the actor sequence cannot be started by a dispatch from an actor
store.subscribe(blocked(() => {
for (let actor of actors) {
actor(store.getState(), store.dispatch)
let acting = false
store.subscribe(function() {
if (!acting) {
acting = true
for (let actor of actors) {
actor(store.getState(), store.dispatch)
}
acting = false
}
}));
})
// Dispatch navigation events when the URL's hash changes, and when the
// application loads
function onHashChange() {
store.dispatch(navigation.complete())
}
window.addEventListener('hashchange', onHashChange, false);
onHashChange();
window.addEventListener('hashchange', onHashChange, false)
onHashChange()

View File

@@ -1,28 +1,6 @@
/**
* Created by andrew on 3/22/16.
*/
export const createByIdDataReducer = ([KEY_REQUEST, KEY_SUCCESS, KEY_ERROR], next) => (state = {}, action) => {
switch (action.type) {
case KEY_REQUEST:
case KEY_SUCCESS:
case KEY_ERROR: {
if (!action.id) {
debugger;
}
if (state.errors) {
debugger;
}
return {
...state,
[action.id]: next(state, action)
};
}
default:
return state;
}
};
const createDataReducer = ([KEY_REQUEST, KEY_SUCCESS, KEY_ERROR], payloadActionNameProp = 'payload', payloadStateNameProp = 'data', payloadAssignFn = (k = []) => [...k]) => {
const initialState = {

View File

@@ -12,9 +12,6 @@ const nodeInitialState = {
};
export const entities = (state = {...initialState}, action) => {
if (typeof action.length !== 'undefined') {
debugger;
}
switch(action.type) {
case T.ENTITIES.REQUESTED: {
const { id } = action;

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