diff --git a/Procfile b/Procfile index fd1ee1f..abaa37c 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: java $JAVA_OPTS -Ddw.http.port=$PORT -Ddw.http.adminPort=$PORT -Ddw.github.user=$GITHUB_USER -Ddw.github.token=$GITHUB_TOKEN -Ddw.github.repositories_heroku="$GITHUB_REPOSITORIES" -Ddw.coinbase.apiKey=$COINBASE_API_KEY -Ddw.github.webhook.password=$GITHUB_WEBHOOK_PASSWORD -jar target/BitHub-0.1.jar server +web: java $JAVA_OPTS -Ddw.server.type=simple -Ddw.server.applicationContextPath=/ -Ddw.server.connector.type=http -Ddw.server.connector.port=$PORT -Ddw.github.user=$GITHUB_USER -Ddw.github.token=$GITHUB_TOKEN -Ddw.github.repositories_heroku="$GITHUB_REPOSITORIES" -Ddw.coinbase.apiKey=$COINBASE_API_KEY -Ddw.github.webhook.password=$GITHUB_WEBHOOK_PASSWORD -Ddw.organization.name="$ORGANIZATION_NAME" -Ddw.organization.donationUrl=$DONATION_URL -jar target/BitHub-0.1.jar server diff --git a/README.md b/README.md index fede806..ae3cd31 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ $ heroku config:set GITHUB_TOKEN=your_bithub_authtoken $ heroku config:set GITHUB_WEBHOOK_PASSWORD=your_webhook_password $ heroku config:set GITHUB_REPOSITORIES="[{\"url\" : \"https://github.com/youraccount/yourrepo\"}, {\"url\" : \"https://github.com/youraccount/yourotherrepo\"}]" $ heroku config:set COINBASE_API_KEY=your_api_key +$ heroku config:set ORGANIZATION_NAME=your_organization_name +$ heroku config:set DONATION_URL=your_donation_url $ git remote add your_heroku_remote $ git push heroku master ``` diff --git a/config/sample.yml b/config/sample.yml index d567fd8..c02cdbd 100644 --- a/config/sample.yml +++ b/config/sample.yml @@ -1,8 +1,14 @@ +organization: + name: # Your name (eg. Open Whisper Systems) + donationUrl: # A Coinbase link where you can receive donations (eg. https://coinbase.com/checkouts/d29fd4c37ca442393e32fdcb95304701) + github: user: # Your BitHub instance's GitHub username. token: # Your BitHub instance's GitHub auth token. + webhook: password: # HTTP basic auth. The username defaults to "bithub". + repositories: # A list of repository URLs to support payouts for. - url: # A repository's URL mode: # Either MONEYMONEY (default) or FREEBIE. diff --git a/pom.xml b/pom.xml index 25a5454..7e9560b 100644 --- a/pom.xml +++ b/pom.xml @@ -56,6 +56,11 @@ dropwizard-views ${dropwizard.version} + + io.dropwizard + dropwizard-views-mustache + ${dropwizard.version} + io.dropwizard dropwizard-servlets diff --git a/src/main/java/org/whispersystems/bithub/BithubServerConfiguration.java b/src/main/java/org/whispersystems/bithub/BithubServerConfiguration.java index 6ba2869..a1f25a6 100644 --- a/src/main/java/org/whispersystems/bithub/BithubServerConfiguration.java +++ b/src/main/java/org/whispersystems/bithub/BithubServerConfiguration.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.whispersystems.bithub.config.BithubConfiguration; import org.whispersystems.bithub.config.CoinbaseConfiguration; import org.whispersystems.bithub.config.GithubConfiguration; +import org.whispersystems.bithub.config.OrganizationConfiguration; import javax.validation.Valid; import javax.validation.constraints.NotNull; @@ -43,6 +44,11 @@ public class BithubServerConfiguration extends Configuration { @Valid private BithubConfiguration bithub = new BithubConfiguration(); + @Valid + @NotNull + @JsonProperty + private OrganizationConfiguration organization; + public GithubConfiguration getGithubConfiguration() { return github; @@ -56,4 +62,7 @@ public class BithubServerConfiguration extends Configuration { return bithub; } + public OrganizationConfiguration getOrganizationConfiguration() { + return organization; + } } diff --git a/src/main/java/org/whispersystems/bithub/BithubService.java b/src/main/java/org/whispersystems/bithub/BithubService.java index 2f6f2c5..c826409 100644 --- a/src/main/java/org/whispersystems/bithub/BithubService.java +++ b/src/main/java/org/whispersystems/bithub/BithubService.java @@ -22,10 +22,12 @@ import org.whispersystems.bithub.auth.GithubWebhookAuthenticator; import org.whispersystems.bithub.client.CoinbaseClient; import org.whispersystems.bithub.client.GithubClient; import org.whispersystems.bithub.config.RepositoryConfiguration; +import org.whispersystems.bithub.controllers.DashboardController; import org.whispersystems.bithub.controllers.GithubController; import org.whispersystems.bithub.controllers.StatusController; import org.whispersystems.bithub.mappers.IOExceptionMapper; import org.whispersystems.bithub.mappers.UnauthorizedHookExceptionMapper; +import org.whispersystems.bithub.storage.CacheManager; import javax.servlet.DispatcherType; import java.math.BigDecimal; @@ -60,14 +62,22 @@ public class BithubService extends Application { String githubWebhookPwd = config.getGithubConfiguration().getWebhookConfiguration().getPassword(); List githubRepositories = config.getGithubConfiguration().getRepositories(); BigDecimal payoutRate = config.getBithubConfiguration().getPayoutRate(); - GithubClient githubClient = new GithubClient(githubUser, githubToken); - CoinbaseClient coinbaseClient = new CoinbaseClient(config.getCoinbaseConfiguration().getApiKey()); + String organizationName = config.getOrganizationConfiguration().getName(); + String donationUrl = config.getOrganizationConfiguration().getDonationUrl().toExternalForm(); + + GithubClient githubClient = new GithubClient(githubUser, githubToken); + CoinbaseClient coinbaseClient = new CoinbaseClient(config.getCoinbaseConfiguration().getApiKey()); + CacheManager cacheManager = new CacheManager(coinbaseClient, githubClient, githubRepositories, payoutRate); environment.servlets().addFilter("CORS", CrossOriginFilter.class) .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); - + + environment.lifecycle().manage(cacheManager); + environment.jersey().register(new GithubController(githubRepositories, githubClient, coinbaseClient, payoutRate)); - environment.jersey().register(new StatusController(coinbaseClient, payoutRate)); + environment.jersey().register(new StatusController(cacheManager, githubRepositories)); + environment.jersey().register(new DashboardController(organizationName, donationUrl, cacheManager)); + environment.jersey().register(new IOExceptionMapper()); environment.jersey().register(new UnauthorizedHookExceptionMapper()); environment.jersey().register(new BasicAuthProvider<>(new GithubWebhookAuthenticator(githubWebhookUser, githubWebhookPwd), diff --git a/src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java b/src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java index aeedbbc..e3669be 100644 --- a/src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java +++ b/src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java @@ -30,8 +30,8 @@ import org.whispersystems.bithub.entities.BalanceResponse; import org.whispersystems.bithub.entities.BitcoinTransaction; import org.whispersystems.bithub.entities.BitcoinTransactionResponse; import org.whispersystems.bithub.entities.ExchangeRate; -import org.whispersystems.bithub.entities.RecentTransactionsResponse; -import org.whispersystems.bithub.entities.Transaction; +import org.whispersystems.bithub.entities.CoinbseRecentTransactionsResponse; +import org.whispersystems.bithub.entities.CoinbaseTransaction; import javax.ws.rs.core.MediaType; import java.io.IOException; @@ -59,12 +59,12 @@ public class CoinbaseClient { this.client = Client.create(getClientConfig()); } - public List getRecentTransactions() throws IOException { + public List getRecentTransactions() throws IOException { try { return client.resource(COINBASE_URL) .path(RECENT_TRANSACTIONS_PATH) .queryParam("api_key", apiKey) - .get(RecentTransactionsResponse.class).getTransactions(); + .get(CoinbseRecentTransactionsResponse.class).getTransactions(); } catch (UniformInterfaceException | ClientHandlerException e) { throw new IOException(e); } diff --git a/src/main/java/org/whispersystems/bithub/client/GithubClient.java b/src/main/java/org/whispersystems/bithub/client/GithubClient.java index a4ba8fb..9085b49 100644 --- a/src/main/java/org/whispersystems/bithub/client/GithubClient.java +++ b/src/main/java/org/whispersystems/bithub/client/GithubClient.java @@ -42,8 +42,10 @@ import javax.ws.rs.core.MediaType; */ public class GithubClient { - private static final String GITHUB_URL = "https://api.github.com/"; - private static final String COMMENT_PATH = "/repos/%s/%s/commits/%s/comments"; + private static final String GITHUB_URL = "https://api.github.com/"; + private static final String COMMENT_PATH = "/repos/%s/%s/commits/%s/comments"; + private static final String COMMIT_PATH = "/repos/%s/%s/git/commits/%s"; + private static final String REPOSITORY_PATH = "/repos/%s/%s"; private final Logger logger = LoggerFactory.getLogger(GithubClient.class); @@ -55,6 +57,37 @@ public class GithubClient { this.client = Client.create(getClientConfig()); } + public String getCommitDescription(String commitUrl) { + String[] commitUrlParts = commitUrl.split("/"); + String owner = commitUrlParts[commitUrlParts.length - 4]; + String repository = commitUrlParts[commitUrlParts.length - 3]; + String commit = commitUrlParts[commitUrlParts.length - 1]; + + String path = String.format(COMMIT_PATH, owner, repository, commit); + WebResource resource = client.resource(GITHUB_URL).path(path); + Commit response = resource.type(MediaType.APPLICATION_JSON_TYPE) + .accept(MediaType.APPLICATION_JSON_TYPE) + .header("Authorization", authorizationHeader) + .get(Commit.class); + + return response.getMessage(); + } + + public Repository getRepository(String url) { + String[] urlParts = url.split("/"); + String owner = urlParts[urlParts.length - 2]; + String name = urlParts[urlParts.length - 1]; + + String path = String.format(REPOSITORY_PATH, owner, name); + WebResource resource = client.resource(GITHUB_URL).path(path); + + return resource.type(MediaType.APPLICATION_JSON_TYPE) + .accept(MediaType.APPLICATION_JSON_TYPE) + .header("Authorization", authorizationHeader) + .get(Repository.class); + + } + public void addCommitComment(Repository repository, Commit commit, String comment) { try { String path = String.format(COMMENT_PATH, repository.getOwner().getName(), diff --git a/src/main/java/org/whispersystems/bithub/config/OrganizationConfiguration.java b/src/main/java/org/whispersystems/bithub/config/OrganizationConfiguration.java new file mode 100644 index 0000000..2aa3829 --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/config/OrganizationConfiguration.java @@ -0,0 +1,26 @@ +package org.whispersystems.bithub.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.Valid; +import java.net.URL; + +public class OrganizationConfiguration { + + @JsonProperty + @NotEmpty + private String name; + + @JsonProperty + @Valid + private URL donationUrl; + + public String getName() { + return name; + } + + public URL getDonationUrl() { + return donationUrl; + } +} diff --git a/src/main/java/org/whispersystems/bithub/controllers/DashboardController.java b/src/main/java/org/whispersystems/bithub/controllers/DashboardController.java new file mode 100644 index 0000000..04813eb --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/controllers/DashboardController.java @@ -0,0 +1,39 @@ +package org.whispersystems.bithub.controllers; + +import com.codahale.metrics.annotation.Timed; +import org.whispersystems.bithub.storage.CacheManager; +import org.whispersystems.bithub.views.DashboardView; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/") +public class DashboardController { + + private final CacheManager cacheManager; + private final String organizationName; + private final String donationUrl; + + + + public DashboardController(String organizationName, String donationUrl, + CacheManager cacheManager) + { + this.organizationName = organizationName; + this.donationUrl = donationUrl; + this.cacheManager = cacheManager; + } + + @Timed + @GET + @Produces(MediaType.TEXT_HTML) + public DashboardView getDashboard() { + return new DashboardView(organizationName, donationUrl, + cacheManager.getCurrentPaymentAmount(), + cacheManager.getRepositories(), + cacheManager.getRecentTransactions()); + } + +} diff --git a/src/main/java/org/whispersystems/bithub/controllers/StatusController.java b/src/main/java/org/whispersystems/bithub/controllers/StatusController.java index a838a3a..8eb4962 100644 --- a/src/main/java/org/whispersystems/bithub/controllers/StatusController.java +++ b/src/main/java/org/whispersystems/bithub/controllers/StatusController.java @@ -20,28 +20,27 @@ package org.whispersystems.bithub.controllers; import com.codahale.metrics.annotation.Timed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.whispersystems.bithub.client.CoinbaseClient; -import org.whispersystems.bithub.entities.Payment; +import org.whispersystems.bithub.config.RepositoryConfiguration; +import org.whispersystems.bithub.entities.Repositories; +import org.whispersystems.bithub.entities.Repository; import org.whispersystems.bithub.entities.Transaction; -import org.whispersystems.bithub.util.Badge; -import org.whispersystems.bithub.views.RecentTransactionsView; +import org.whispersystems.bithub.entities.Transactions; +import org.whispersystems.bithub.storage.CacheManager; +import org.whispersystems.bithub.storage.CurrentPayment; +import org.whispersystems.bithub.views.TransactionsView; -import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; -import javax.ws.rs.core.CacheControl; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; -import java.math.BigDecimal; -import java.math.RoundingMode; +import java.util.LinkedList; import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; + +import io.dropwizard.jersey.caching.CacheControl; /** * Handles incoming API calls for BitHub instance status information. @@ -51,123 +50,65 @@ import java.util.concurrent.atomic.AtomicReference; @Path("/v1/status") public class StatusController { - private static final int UPDATE_FREQUENCY_MILLIS = 60 * 1000; + private final Logger logger = LoggerFactory.getLogger(StatusController.class); - private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); - private final Logger logger = LoggerFactory.getLogger(StatusController.class); + private final List repositoryConfiguration; + private final CacheManager coinbaseManager; - private final AtomicReference cachedPaymentStatus; - private final AtomicReference cachedTransactions; - private final BigDecimal payoutRate; - - public StatusController(CoinbaseClient coinbaseClient, BigDecimal payoutRate) throws IOException { - this.payoutRate = payoutRate; - this.cachedPaymentStatus = new AtomicReference<>(createCurrentPaymentForBalance(coinbaseClient)); - this.cachedTransactions = new AtomicReference<>(createRecentTransactionsView(coinbaseClient)); - - initializeUpdates(coinbaseClient); + public StatusController(CacheManager coinbaseManager, + List repositoryConfiguration) + throws IOException + { + this.coinbaseManager = coinbaseManager; + this.repositoryConfiguration = repositoryConfiguration; } @Timed @GET @Path("/transactions") - @Consumes({MediaType.APPLICATION_JSON, MediaType.TEXT_HTML}) - public RecentTransactionsView getTransactions() - throws IOException + public Response getTransactions(@QueryParam("format") @DefaultValue("html") String format) + throws IOException { - return cachedTransactions.get(); + List recentTransactions = coinbaseManager.getRecentTransactions(); + + switch (format) { + case "html": return Response.ok(new TransactionsView(recentTransactions), MediaType.TEXT_HTML_TYPE).build(); + case "json": + default: return Response.ok(new Transactions(recentTransactions), MediaType.APPLICATION_JSON_TYPE).build(); + } } + @Timed + @GET + @Path("/repositories") + @Produces(MediaType.APPLICATION_JSON) + public Repositories getRepositories() { + List repositories = new LinkedList<>(); + + for (RepositoryConfiguration configuration : repositoryConfiguration) { + repositories.add(new Repository(configuration.getUrl())); + } + + return new Repositories(repositories); + } + + @Timed @GET @Path("/payment/commit") + @CacheControl(noCache = true) public Response getCurrentCommitPrice(@QueryParam("format") @DefaultValue("png") String format) throws IOException { - CacheControl cacheControl = new CacheControl(); - cacheControl.setNoCache(true); + CurrentPayment currentPayment = coinbaseManager.getCurrentPaymentAmount(); switch (format) { case "json": - return Response.ok(cachedPaymentStatus.get().getEntity(), MediaType.APPLICATION_JSON_TYPE).cacheControl(cacheControl).build(); + return Response.ok(currentPayment.getEntity(), MediaType.APPLICATION_JSON_TYPE).build(); case "png_small": - return Response.ok(cachedPaymentStatus.get().getSmallBadge(), "image/png").cacheControl(cacheControl).build(); + return Response.ok(currentPayment.getSmallBadge(), "image/png").build(); default: - return Response.ok(cachedPaymentStatus.get().getBadge(), "image/png").cacheControl(cacheControl).build(); + return Response.ok(currentPayment.getBadge(), "image/png").build(); } } - - private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseClient) - throws IOException - { - BigDecimal currentBalance = coinbaseClient.getAccountBalance(); - BigDecimal paymentBtc = currentBalance.multiply(payoutRate); - BigDecimal exchangeRate = coinbaseClient.getExchangeRate(); - BigDecimal paymentUsd = paymentBtc.multiply(exchangeRate); - - paymentUsd = paymentUsd.setScale(2, RoundingMode.CEILING); - return new CurrentPayment(Badge.createFor(paymentUsd.toPlainString()), - Badge.createSmallFor(paymentUsd.toPlainString()), - new Payment(paymentUsd.toPlainString())); - } - - private RecentTransactionsView createRecentTransactionsView(CoinbaseClient coinbaseClient) - throws IOException - { - List recentTransactions = coinbaseClient.getRecentTransactions(); - BigDecimal exchangeRate = coinbaseClient.getExchangeRate(); - - return new RecentTransactionsView(recentTransactions, exchangeRate); - } - - public void initializeUpdates(final CoinbaseClient coinbaseClient) { - executor.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - CurrentPayment currentPayment = createCurrentPaymentForBalance(coinbaseClient); - cachedPaymentStatus.set(currentPayment); - } catch (IOException e) { - logger.warn("Failed to update badge", e); - } - } - }, UPDATE_FREQUENCY_MILLIS, UPDATE_FREQUENCY_MILLIS, TimeUnit.MILLISECONDS); - - executor.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - try { - RecentTransactionsView view = createRecentTransactionsView(coinbaseClient); - cachedTransactions.set(view); - } catch (IOException e) { - logger.warn("Failed to update recent transactions", e); - } - } - }, UPDATE_FREQUENCY_MILLIS, UPDATE_FREQUENCY_MILLIS, TimeUnit.MILLISECONDS); - } - - private class CurrentPayment { - private final byte[] badge; - private final byte[] smallBadge; - private final Payment entity; - - private CurrentPayment(byte[] badge, byte[] smallBadge, Payment entity) { - this.badge = badge; - this.smallBadge = smallBadge; - this.entity = entity; - } - - private byte[] getBadge() { - return badge; - } - - private byte[] getSmallBadge() { - return smallBadge; - } - - private Payment getEntity() { - return entity; - } - } - } diff --git a/src/main/java/org/whispersystems/bithub/entities/CoinbaseTransaction.java b/src/main/java/org/whispersystems/bithub/entities/CoinbaseTransaction.java new file mode 100644 index 0000000..2be7429 --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/entities/CoinbaseTransaction.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2013 Open WhisperSystems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.whispersystems.bithub.entities; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CoinbaseTransaction { + + @JsonProperty(value = "created_at") + @NotEmpty + private String createdTime; + + @JsonProperty + @NotNull + private Amount amount; + + @JsonProperty(value = "recipient_address") + private String recipientAddress; + + @JsonProperty + private String notes; + + public String getCreatedTime() { + return createdTime; + } + + public String getAmount() { + return amount.getAmount(); + } + + public String getRecipientAddress() { + return recipientAddress; + } + + public String getNotes() { + return notes; + } + + public boolean isSentTransaction() { + BigDecimal amount = new BigDecimal(getAmount()); + return amount.compareTo(new BigDecimal(0.0)) < 0; + } + +} diff --git a/src/main/java/org/whispersystems/bithub/entities/TransactionWrapper.java b/src/main/java/org/whispersystems/bithub/entities/CoinbaseTransactionWrapper.java similarity index 88% rename from src/main/java/org/whispersystems/bithub/entities/TransactionWrapper.java rename to src/main/java/org/whispersystems/bithub/entities/CoinbaseTransactionWrapper.java index b9d55f7..ddf3f38 100644 --- a/src/main/java/org/whispersystems/bithub/entities/TransactionWrapper.java +++ b/src/main/java/org/whispersystems/bithub/entities/CoinbaseTransactionWrapper.java @@ -23,12 +23,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import javax.validation.constraints.NotNull; @JsonIgnoreProperties(ignoreUnknown = true) -public class TransactionWrapper { +public class CoinbaseTransactionWrapper { @JsonProperty @NotNull - private Transaction transaction; + private CoinbaseTransaction transaction; - public Transaction getTransaction() { + public CoinbaseTransaction getTransaction() { return transaction; } } diff --git a/src/main/java/org/whispersystems/bithub/entities/RecentTransactionsResponse.java b/src/main/java/org/whispersystems/bithub/entities/CoinbseRecentTransactionsResponse.java similarity index 78% rename from src/main/java/org/whispersystems/bithub/entities/RecentTransactionsResponse.java rename to src/main/java/org/whispersystems/bithub/entities/CoinbseRecentTransactionsResponse.java index 95525b3..f960af0 100644 --- a/src/main/java/org/whispersystems/bithub/entities/RecentTransactionsResponse.java +++ b/src/main/java/org/whispersystems/bithub/entities/CoinbseRecentTransactionsResponse.java @@ -25,16 +25,16 @@ import java.util.LinkedList; import java.util.List; @JsonIgnoreProperties(ignoreUnknown = true) -public class RecentTransactionsResponse { +public class CoinbseRecentTransactionsResponse { @JsonProperty @NotNull - private List transactions; + private List transactions; - public List getTransactions() { - List rawTransactions = new LinkedList(); + public List getTransactions() { + List rawTransactions = new LinkedList(); - for (TransactionWrapper transactionWrapper : transactions) { + for (CoinbaseTransactionWrapper transactionWrapper : transactions) { rawTransactions.add(transactionWrapper.getTransaction()); } diff --git a/src/main/java/org/whispersystems/bithub/entities/Payment.java b/src/main/java/org/whispersystems/bithub/entities/Payment.java index c8c3771..55de042 100644 --- a/src/main/java/org/whispersystems/bithub/entities/Payment.java +++ b/src/main/java/org/whispersystems/bithub/entities/Payment.java @@ -26,4 +26,8 @@ public class Payment { public Payment(String payment) { this.payment = payment; } + + public String getPayment() { + return payment; + } } diff --git a/src/main/java/org/whispersystems/bithub/entities/Repositories.java b/src/main/java/org/whispersystems/bithub/entities/Repositories.java new file mode 100644 index 0000000..764bb3f --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/entities/Repositories.java @@ -0,0 +1,15 @@ +package org.whispersystems.bithub.entities; + +import java.util.List; + +public class Repositories { + + public List repositories; + + public Repositories() {} + + public Repositories(List repositories) { + this.repositories = repositories; + } + +} diff --git a/src/main/java/org/whispersystems/bithub/entities/Repository.java b/src/main/java/org/whispersystems/bithub/entities/Repository.java index 77d38ae..a1caa47 100644 --- a/src/main/java/org/whispersystems/bithub/entities/Repository.java +++ b/src/main/java/org/whispersystems/bithub/entities/Repository.java @@ -38,6 +38,15 @@ public class Repository { @NotEmpty private String name; + @JsonProperty + private String description; + + public Repository() {} + + public Repository(String url) { + this.url = url; + } + public Author getOwner() { return owner; } @@ -49,4 +58,8 @@ public class Repository { public String getUrl() { return url; } + + public String getDescription() { + return description; + } } diff --git a/src/main/java/org/whispersystems/bithub/entities/Transaction.java b/src/main/java/org/whispersystems/bithub/entities/Transaction.java index 7dc55fb..f88ea76 100644 --- a/src/main/java/org/whispersystems/bithub/entities/Transaction.java +++ b/src/main/java/org/whispersystems/bithub/entities/Transaction.java @@ -1,58 +1,62 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - package org.whispersystems.bithub.entities; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import org.hibernate.validator.constraints.NotEmpty; -import javax.validation.constraints.NotNull; - -@JsonIgnoreProperties(ignoreUnknown = true) public class Transaction { - @JsonProperty(value = "created_at") - @NotEmpty - private String createdTime; + @JsonProperty + private String destination; @JsonProperty - @NotNull - private Amount amount; - - @JsonProperty(value = "recipient_address") - private String recipientAddress; + private String amount; @JsonProperty - private String notes; + private String commitUrl; - public String getCreatedTime() { - return createdTime; + @JsonProperty + private String commitSha; + + @JsonProperty + private String timestamp; + + @JsonProperty + private String description; + + public Transaction() {} + + public Transaction(String destination, String amount, String commitUrl, + String commitSha, String timestamp, String description) + { + this.destination = destination; + this.amount = amount; + this.commitUrl = commitUrl; + this.commitSha = commitSha; + this.timestamp = timestamp; + this.description = description; + } + + public String getDestination() { + return destination; } public String getAmount() { - return amount.getAmount(); + return amount; } - public String getRecipientAddress() { - return recipientAddress; + public String getCommitUrl() { + return commitUrl; } - public String getNotes() { - return notes; + public String getCommitSha() { + return commitSha; } + + public String getTimestamp() { + return timestamp; + } + + public String getDescription() { + return description; + } + } diff --git a/src/main/java/org/whispersystems/bithub/entities/Transactions.java b/src/main/java/org/whispersystems/bithub/entities/Transactions.java new file mode 100644 index 0000000..9024699 --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/entities/Transactions.java @@ -0,0 +1,19 @@ +package org.whispersystems.bithub.entities; + + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public class Transactions { + + @JsonProperty + private List transactions; + + public Transactions() {} + + public Transactions(List transactions) { + this.transactions = transactions; + } + +} diff --git a/src/main/java/org/whispersystems/bithub/filters/CorsHeaderFilter.java b/src/main/java/org/whispersystems/bithub/filters/CorsHeaderFilter.java deleted file mode 100644 index 6736dd8..0000000 --- a/src/main/java/org/whispersystems/bithub/filters/CorsHeaderFilter.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.whispersystems.bithub.filters; - -import java.io.IOException; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class CorsHeaderFilter implements Filter { - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException - { - if (response instanceof HttpServletResponse) { - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - HttpServletRequest httpServletRequest = (HttpServletRequest) request; - httpServletResponse.addHeader("Access-Control-Allow-Origin", "*"); - if ("OPTIONS".equals(httpServletRequest.getMethod())) { - httpServletResponse.addHeader("Access-Control-Allow-Headers", "Content-Type"); - } - } - - chain.doFilter(request, response); - } - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - public void destroy() { - - } -} \ No newline at end of file diff --git a/src/main/java/org/whispersystems/bithub/storage/CacheManager.java b/src/main/java/org/whispersystems/bithub/storage/CacheManager.java new file mode 100644 index 0000000..a8a125b --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/storage/CacheManager.java @@ -0,0 +1,160 @@ +package org.whispersystems.bithub.storage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.whispersystems.bithub.client.CoinbaseClient; +import org.whispersystems.bithub.client.GithubClient; +import org.whispersystems.bithub.config.RepositoryConfiguration; +import org.whispersystems.bithub.entities.CoinbaseTransaction; +import org.whispersystems.bithub.entities.Payment; +import org.whispersystems.bithub.entities.Repository; +import org.whispersystems.bithub.entities.Transaction; +import org.whispersystems.bithub.util.Badge; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.ParseException; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import io.dropwizard.lifecycle.Managed; + +public class CacheManager implements Managed { + + private static final int UPDATE_FREQUENCY_MILLIS = 60 * 1000; + + private final Logger logger = LoggerFactory.getLogger(CacheManager.class); + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); + + private final CoinbaseClient coinbaseClient; + private final GithubClient githubClient; + private final BigDecimal payoutRate; + private final List repositories; + + private AtomicReference cachedPaymentStatus; + private AtomicReference> cachedTransactions; + private AtomicReference> cachedRepositories; + + public CacheManager(CoinbaseClient coinbaseClient, + GithubClient githubClient, + List repositories, + BigDecimal payoutRate) + { + this.coinbaseClient = coinbaseClient; + this.githubClient = githubClient; + this.payoutRate = payoutRate; + this.repositories = repositories; + } + + @Override + public void start() throws Exception { + this.cachedPaymentStatus = new AtomicReference<>(createCurrentPaymentForBalance(coinbaseClient)); + this.cachedTransactions = new AtomicReference<>(createRecentTransactions(coinbaseClient)); + this.cachedRepositories = new AtomicReference<>(createRepositories(githubClient, repositories)); + + initializeUpdates(coinbaseClient, githubClient, repositories); + } + + @Override + public void stop() throws Exception { + this.executor.shutdownNow(); + } + + public List getRecentTransactions() { + return cachedTransactions.get(); + } + + public CurrentPayment getCurrentPaymentAmount() { + return cachedPaymentStatus.get(); + } + + public List getRepositories() { + return cachedRepositories.get(); + } + + public void initializeUpdates(final CoinbaseClient coinbaseClient, + final GithubClient githubClient, + final List repoConfigs) + { + executor.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + try { + CurrentPayment currentPayment = createCurrentPaymentForBalance(coinbaseClient); + List transactions = createRecentTransactions (coinbaseClient); + List repositories = createRepositories(githubClient, repoConfigs); + + cachedPaymentStatus.set(currentPayment); + cachedTransactions.set(transactions); + cachedRepositories.set(repositories); + + } catch (IOException e) { + logger.warn("Failed to update badge", e); + } + } + }, UPDATE_FREQUENCY_MILLIS, UPDATE_FREQUENCY_MILLIS, TimeUnit.MILLISECONDS); + } + + private List createRepositories(GithubClient githubClient, + List configured) + { + List repositoryList = new LinkedList<>(); + + for (RepositoryConfiguration repository : configured) { + repositoryList.add(githubClient.getRepository(repository.getUrl())); + } + + return repositoryList; + } + + private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseClient) + throws IOException + { + BigDecimal currentBalance = coinbaseClient.getAccountBalance(); + BigDecimal paymentBtc = currentBalance.multiply(payoutRate); + BigDecimal exchangeRate = coinbaseClient.getExchangeRate(); + BigDecimal paymentUsd = paymentBtc.multiply(exchangeRate); + + paymentUsd = paymentUsd.setScale(2, RoundingMode.CEILING); + return new CurrentPayment(Badge.createFor(paymentUsd.toPlainString()), + Badge.createSmallFor(paymentUsd.toPlainString()), + new Payment(paymentUsd.toPlainString())); + } + + private List createRecentTransactions(CoinbaseClient coinbaseClient) + throws IOException + { + List recentTransactions = coinbaseClient.getRecentTransactions(); + BigDecimal exchangeRate = coinbaseClient.getExchangeRate(); + List transactions = new LinkedList<>(); + + for (CoinbaseTransaction coinbaseTransaction : recentTransactions) { + try { + if (coinbaseTransaction.isSentTransaction()) { + CoinbaseTransactionParser parser = new CoinbaseTransactionParser(coinbaseTransaction); + String url = parser.parseUrlFromMessage(); + String sha = parser.parseShaFromUrl(url); + String description = githubClient.getCommitDescription(url); + + transactions.add(new Transaction(parser.parseDestinationFromMessage(), + parser.parseAmountInDollars(exchangeRate), + url, sha, parser.parseTimestamp(), + description)); + + if (transactions.size() >= 10) + break; + } + } catch (ParseException e) { + logger.warn("Parse", e); + } + } + + return transactions; + } + +} diff --git a/src/main/java/org/whispersystems/bithub/storage/CoinbaseTransactionParser.java b/src/main/java/org/whispersystems/bithub/storage/CoinbaseTransactionParser.java new file mode 100644 index 0000000..5c81626 --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/storage/CoinbaseTransactionParser.java @@ -0,0 +1,73 @@ +package org.whispersystems.bithub.storage; + +import org.apache.commons.lang3.StringEscapeUtils; +import org.ocpsoft.prettytime.PrettyTime; +import org.whispersystems.bithub.entities.CoinbaseTransaction; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +public class CoinbaseTransactionParser { + + private final CoinbaseTransaction coinbaseTransaction; + + public CoinbaseTransactionParser(CoinbaseTransaction coinbaseTransaction) { + this.coinbaseTransaction = coinbaseTransaction; + } + + public String parseAmountInDollars(BigDecimal exchangeRate) { + return new BigDecimal(coinbaseTransaction.getAmount()).abs() + .multiply(exchangeRate) + .setScale(2, RoundingMode.CEILING) + .toPlainString(); + } + + public String parseTimestamp() throws ParseException { + String timestamp = coinbaseTransaction.getCreatedTime(); + int offendingColon = timestamp.lastIndexOf(':'); + String fixedTimestamp = timestamp.substring(0, offendingColon) + timestamp.substring(offendingColon + 1); + return new PrettyTime().format(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(fixedTimestamp)); + } + + public String parseDestinationFromMessage() { + String message = StringEscapeUtils.unescapeHtml4(coinbaseTransaction.getNotes()); + int startToken = message.indexOf("__"); + + if (startToken == -1) { + return "Unknown"; + } + + int endToken = message.indexOf("__", startToken + 1); + + if (endToken == -1) { + return "Unknown"; + } + + return message.substring(startToken+2, endToken); + } + + public String parseUrlFromMessage() throws ParseException { + String message = StringEscapeUtils.unescapeHtml4(coinbaseTransaction.getNotes()); + int urlIndex = message.indexOf("https://"); + + return message.substring(urlIndex).trim(); + } + + public String parseShaFromUrl(String url) throws ParseException { + if (url == null) { + throw new ParseException("No url", 0); + } + + String[] parts = url.split("/"); + String fullHash = parts[parts.length-1]; + + if (fullHash.length() < 8) { + throw new ParseException("Not long enough", 0); + } + + return fullHash.substring(0, 8); + } + +} diff --git a/src/main/java/org/whispersystems/bithub/storage/CurrentPayment.java b/src/main/java/org/whispersystems/bithub/storage/CurrentPayment.java new file mode 100644 index 0000000..7cb170d --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/storage/CurrentPayment.java @@ -0,0 +1,29 @@ +package org.whispersystems.bithub.storage; + +import org.whispersystems.bithub.entities.Payment; + +public class CurrentPayment { + + private final byte[] badge; + private final byte[] smallBadge; + private final Payment entity; + + protected CurrentPayment(byte[] badge, byte[] smallBadge, Payment entity) { + this.badge = badge; + this.smallBadge = smallBadge; + this.entity = entity; + } + + public byte[] getBadge() { + return badge; + } + + public byte[] getSmallBadge() { + return smallBadge; + } + + public Payment getEntity() { + return entity; + } + +} diff --git a/src/main/java/org/whispersystems/bithub/views/DashboardView.java b/src/main/java/org/whispersystems/bithub/views/DashboardView.java new file mode 100644 index 0000000..e0b9e7b --- /dev/null +++ b/src/main/java/org/whispersystems/bithub/views/DashboardView.java @@ -0,0 +1,56 @@ +package org.whispersystems.bithub.views; + +import org.whispersystems.bithub.entities.Repository; +import org.whispersystems.bithub.entities.Transaction; +import org.whispersystems.bithub.storage.CurrentPayment; + +import java.util.List; + +import io.dropwizard.views.View; + +public class DashboardView extends View { + + private final String organizationName; + private final String donationUrl; + private final CurrentPayment currentPayment; + private final List repositories; + private final List transactions; + + public DashboardView(String organizationName, String donationUrl, + CurrentPayment currentPayment, + List repositories, + List transactions) + { + super("dashboard.mustache"); + this.organizationName = organizationName; + this.donationUrl = donationUrl; + this.currentPayment = currentPayment; + this.repositories = repositories; + this.transactions = transactions; + } + + public String getPayment() { + return currentPayment.getEntity().getPayment(); + } + + public String getOrganizationName() { + return organizationName; + } + + public String getDonationUrl() { + return donationUrl; + } + + public List getRepositories() { + return repositories; + } + + public List getTransactions() { + return transactions; + } + + public String getRepositoriesCount() { + return String.valueOf(repositories.size()); + } + +} diff --git a/src/main/java/org/whispersystems/bithub/views/TransactionView.java b/src/main/java/org/whispersystems/bithub/views/TransactionView.java deleted file mode 100644 index b8dfe4e..0000000 --- a/src/main/java/org/whispersystems/bithub/views/TransactionView.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.whispersystems.bithub.views; - -import org.apache.commons.lang3.StringEscapeUtils; -import org.ocpsoft.prettytime.PrettyTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.text.ParseException; -import java.text.SimpleDateFormat; - -/** - * A rendered HTML view of an individual BitHub transaction. - * - * @author Moxie Marlinspike - */ -public class TransactionView { - - private final Logger logger = LoggerFactory.getLogger(RecentTransactionsView.class); - - private final String destination; - private final String amount; - private final String commitUrl; - private final String commitSha; - private final String timestamp; - - public TransactionView(BigDecimal exchangeRate, String amount, - String timestamp, String message) - throws ParseException - { - this.amount = getAmountInDollars(exchangeRate, amount); - this.destination = parseDestinationFromMessage(message); - this.timestamp = parseTimestamp(timestamp); - this.commitUrl = parseUrlFromMessage(message); - this.commitSha = parseShaFromUrl(commitUrl); - } - - private String getAmountInDollars(BigDecimal exchangeRate, String amount) { - return new BigDecimal(amount).abs() - .multiply(exchangeRate) - .setScale(2, RoundingMode.CEILING) - .toPlainString(); - } - - private String parseTimestamp(String timestamp) throws ParseException { - int offendingColon = timestamp.lastIndexOf(':'); - String fixedTimestamp = timestamp.substring(0, offendingColon) + timestamp.substring(offendingColon + 1); - return new PrettyTime().format(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(fixedTimestamp)); - } - - private String parseDestinationFromMessage(String message) { - message = StringEscapeUtils.unescapeHtml4(message); - int startToken = message.indexOf("__"); - - if (startToken == -1) { - return "Unknown"; - } - - int endToken = message.indexOf("__", startToken + 1); - - if (endToken == -1) { - return "Unknown"; - } - - return message.substring(startToken+2, endToken); - } - - private String parseUrlFromMessage(String message) throws ParseException { - message = StringEscapeUtils.unescapeHtml4(message); - int urlIndex = message.indexOf("https://"); - - return message.substring(urlIndex).trim(); - } - - private String parseShaFromUrl(String url) throws ParseException { - if (url == null) { - throw new ParseException("No url", 0); - } - - String[] parts = url.split("/"); - String fullHash = parts[parts.length-1]; - - if (fullHash.length() < 8) { - throw new ParseException("Not long enough", 0); - } - - return fullHash.substring(0, 8); - } - - public String getDestination() { - return destination; - } - - public String getAmount() { - return amount; - } - - public String getCommitUrl() { - return commitUrl; - } - - public String getCommitSha() { - return commitSha; - } - - public String getTimestamp() { - return timestamp; - } -} - - diff --git a/src/main/java/org/whispersystems/bithub/views/RecentTransactionsView.java b/src/main/java/org/whispersystems/bithub/views/TransactionsView.java similarity index 52% rename from src/main/java/org/whispersystems/bithub/views/RecentTransactionsView.java rename to src/main/java/org/whispersystems/bithub/views/TransactionsView.java index 269d4cf..4773baf 100644 --- a/src/main/java/org/whispersystems/bithub/views/RecentTransactionsView.java +++ b/src/main/java/org/whispersystems/bithub/views/TransactionsView.java @@ -19,6 +19,7 @@ package org.whispersystems.bithub.views; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.whispersystems.bithub.entities.CoinbaseTransaction; import org.whispersystems.bithub.entities.Transaction; import java.math.BigDecimal; @@ -33,34 +34,16 @@ import io.dropwizard.views.View; * * @author Moxie Marlinspike */ -public class RecentTransactionsView extends View { +public class TransactionsView extends View { - private final Logger logger = LoggerFactory.getLogger(RecentTransactionsView.class); - private final List transactions = new LinkedList<>(); + private final List transactions; - public RecentTransactionsView(List recentTransactions, BigDecimal exchangeRate) { + public TransactionsView(List transactions) { super("recent_transactions.mustache"); - - for (Transaction transaction : recentTransactions) { - try { - if (isSentTransaction(transaction)) { - transactions.add(new TransactionView(exchangeRate, - transaction.getAmount(), - transaction.getCreatedTime(), - transaction.getNotes())); - } - } catch (ParseException e) { - logger.warn("Error parsing: ", e); - } - } + this.transactions = transactions; } - private boolean isSentTransaction(Transaction transaction) { - BigDecimal amount = new BigDecimal(transaction.getAmount()); - return amount.compareTo(new BigDecimal(0.0)) < 0; - } - - public List getTransactions() { + public List getTransactions() { return transactions; } } diff --git a/src/main/resources/org/whispersystems/bithub/views/dashboard.mustache b/src/main/resources/org/whispersystems/bithub/views/dashboard.mustache new file mode 100644 index 0000000..ac613f4 --- /dev/null +++ b/src/main/resources/org/whispersystems/bithub/views/dashboard.mustache @@ -0,0 +1,160 @@ + + + + + + + + + + + BitHub :: Dashboard + + + + + + + + + + + + +
+
+
+

BitHub

+

{{organizationName}}

+

Donate BTC today

+
+
+
+
+ +
+
+ {{payment}} + +
+
+
+
+
+
+ +
+
+ 0 + +
+
+
+
+
+
+ +
+
+ {{repositoriesCount}} + +
+
+
+
+ +

Recent payments

+
+ + + + + + + + + + + {{#transactions}} + + + + + + + {{/transactions}} + +
USDAuthorDescriptionCommit
${{amount}} USD{{destination}}
{{description}}
{{commitSha}}
+
+ +

Repositories

+
+ {{#repositories}} +
+
+
+ {{name}} +
+
+

{{description}}

+
+
+
+ {{/repositories}} +
+
+ + diff --git a/src/test/java/org/whispersystems/bithub/tests/controllers/StatusControllerTest.java b/src/test/java/org/whispersystems/bithub/tests/controllers/StatusControllerTest.java index 8654744..639ba32 100644 --- a/src/test/java/org/whispersystems/bithub/tests/controllers/StatusControllerTest.java +++ b/src/test/java/org/whispersystems/bithub/tests/controllers/StatusControllerTest.java @@ -4,15 +4,20 @@ import com.sun.jersey.api.client.ClientResponse; import org.junit.ClassRule; import org.junit.Test; import org.whispersystems.bithub.client.CoinbaseClient; +import org.whispersystems.bithub.client.GithubClient; +import org.whispersystems.bithub.config.RepositoryConfiguration; import org.whispersystems.bithub.controllers.StatusController; -import org.whispersystems.bithub.entities.RecentTransactionsResponse; +import org.whispersystems.bithub.entities.CoinbseRecentTransactionsResponse; +import org.whispersystems.bithub.entities.Repository; +import org.whispersystems.bithub.storage.CacheManager; import javax.ws.rs.core.MediaType; -import java.io.IOException; import java.math.BigDecimal; +import java.util.LinkedList; import io.dropwizard.testing.junit.ResourceTestRule; import static org.fest.assertions.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.whispersystems.bithub.tests.util.JsonHelper.fromJson; @@ -20,25 +25,31 @@ import static org.whispersystems.bithub.tests.util.JsonHelper.jsonFixture; public class StatusControllerTest { - private static final BigDecimal PAYOUT_RATE = new BigDecimal(0.02); - private static final BigDecimal BALANCE = new BigDecimal(10.01); - private static final BigDecimal EXCHANGE_RATE = new BigDecimal(1.0); + private static final BigDecimal PAYOUT_RATE = new BigDecimal(0.02 ); + private static final BigDecimal BALANCE = new BigDecimal(10.01); + private static final BigDecimal EXCHANGE_RATE = new BigDecimal(1.0 ); private static final CoinbaseClient coinbaseClient = mock(CoinbaseClient.class); + private static final GithubClient githubClient = mock(GithubClient.class ); @ClassRule public static ResourceTestRule resources; static { try { - when(coinbaseClient.getRecentTransactions()).thenReturn(fromJson(jsonFixture("payloads/transactions.json"), RecentTransactionsResponse.class).getTransactions()); + when(coinbaseClient.getRecentTransactions()).thenReturn(fromJson(jsonFixture("payloads/transactions.json"), CoinbseRecentTransactionsResponse.class).getTransactions()); when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE); when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE); + CacheManager coinbaseManager = new CacheManager(coinbaseClient, githubClient, + new LinkedList(), + PAYOUT_RATE); + coinbaseManager.start(); + resources = ResourceTestRule.builder() - .addResource(new StatusController(coinbaseClient, PAYOUT_RATE)) + .addResource(new StatusController(coinbaseManager, null)) .build(); - } catch (IOException e) { + } catch (Exception e) { throw new AssertionError(e); } } @@ -62,7 +73,7 @@ public class StatusControllerTest { @Test public void testTransactionsJson() throws Exception { - ClientResponse response = resources.client().resource("/v1/status/transactions/").accept(MediaType.APPLICATION_JSON_TYPE) + ClientResponse response = resources.client().resource("/v1/status/transactions/?format=json").accept(MediaType.APPLICATION_JSON_TYPE) .get(ClientResponse.class); assertThat(response.getStatus()).isEqualTo(200);