Compare commits
1 Commits
wip-custom
...
wip-new-ev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d78f88337a |
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -27,6 +27,8 @@ public class QuerySideAccountConfiguration {
|
||||
return new AccountQueryService(accountInfoRepository);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Bean
|
||||
public QuerySideDependencyChecker querysideDependencyChecker(MongoTemplate mongoTemplate) {
|
||||
return new QuerySideDependencyChecker(mongoTemplate);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST;
|
||||
*/
|
||||
@RestController
|
||||
@Validated
|
||||
@RequestMapping("/api")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.*;
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"presets": ["es2015", "stage-0", "react"]
|
||||
"presets": ["es2015"]
|
||||
}
|
||||
|
||||
1
js-frontend/.gitignore
vendored
1
js-frontend/.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
node_modules
|
||||
build
|
||||
!/build
|
||||
#dist
|
||||
dist-intermediate
|
||||
|
||||
@@ -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!
|
||||
|
||||

|
||||
|
||||
## 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
@@ -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>
|
||||
@@ -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
|
||||
@@ -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":""}
|
||||
@@ -1,5 +0,0 @@
|
||||
# www.robotstxt.org/
|
||||
|
||||
# Allow crawling of all content
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -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*/
|
||||
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"style.6d7a32b1405ea1bb2bdf.css","sourceRoot":""}
|
||||
@@ -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
|
||||
@@ -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":""}
|
||||
@@ -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
@@ -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"
|
||||
})
|
||||
]
|
||||
};
|
||||
|
||||
};
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
@@ -1,5 +0,0 @@
|
||||
# www.robotstxt.org/
|
||||
|
||||
# Allow crawling of all content
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
], {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
`
|
||||
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user