[
  {
    "path": ".gitignore",
    "content": ".idea\n*.iml\ntarget\nconfig/production.yml\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\n"
  },
  {
    "path": "Procfile",
    "content": "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.coinbase.apiSecret=$COINBASE_API_SECRET -Ddw.github.webhook.password=$GITHUB_WEBHOOK_PASSWORD -Ddw.organization.name=\"$ORGANIZATION_NAME\" -Ddw.organization.donationUrl=$DONATION_URL -jar target/BitHub-0.1.jar server\n"
  },
  {
    "path": "README.md",
    "content": "BitHub\n=================\n\n[![Build Status](https://travis-ci.org/WhisperSystems/BitHub.png?branch=master)](https://travis-ci.org/WhisperSystems/BitHub)\n\nBitHub is a service that will automatically pay a percentage of Bitcoin funds for every submission to a GitHub repository.\n\nMore information can be found in our [announcement blog post](https://whispersystems.org/blog/bithub).\n\nOpting Out\n----------\n\nIf you'd like to opt out of receiving a payment, simply include the string \"FREEBIE\" somewhere in your commit message, and you will not receive BTC for that commit.\n\n\nBuilding\n-------------\n\n    $ git clone https://github.com/WhisperSystems/BitHub.git\n    $ cd BitHub\n    $ mvn3 package\n\nRunning\n-----------\n\n1. Create a GitHub account for your BitHub server.\n1. Create a Coinbase account for your BitHub server.\n1. Add the above credentials to `config/sample.yml`\n1. Execute `$ java -jar target/BitHub-0.1.jar server config/yourconfig.yml`\n\nDeploying To Heroku\n------------\n\n```\n$ heroku create your_app_name\n$ heroku config:set GITHUB_USER=your_bithub_username\n$ heroku config:set GITHUB_TOKEN=your_bithub_authtoken\n$ heroku config:set GITHUB_WEBHOOK_PASSWORD=your_webhook_password\n$ heroku config:set GITHUB_REPOSITORIES=\"[{\\\"url\\\" : \\\"https://github.com/youraccount/yourrepo\\\"}, {\\\"url\\\" : \\\"https://github.com/youraccount/yourotherrepo\\\"}]\"\n$ heroku config:set COINBASE_API_KEY=your_api_key\n$ heroku config:set ORGANIZATION_NAME=your_organization_name\n$ heroku config:set DONATION_URL=your_donation_url\n$ git remote add your_heroku_remote\n$ git push heroku master\n```\n\nMailing list\n------------\n\nHave a question? Ask on our mailing list!\n\nwhispersystems@lists.riseup.net\n\nhttps://lists.riseup.net/www/info/whispersystems\n\nCurrent BitHub Payment For Commit: \n=================\n[![Current Price](https://bithub.herokuapp.com/v1/status/payment/commit)](https://whispersystems.org/blog/bithub/)\n\n"
  },
  {
    "path": "assembly.xml",
    "content": "<assembly xmlns=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0\"\n          xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n          xsi:schemaLocation=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd\">\n    <id>bin</id>\n    <includeBaseDirectory>false</includeBaseDirectory>\n    <formats>\n        <format>tar.gz</format>\n    </formats>\n    <fileSets>\n        <fileSet>\n            <directory>${project.basedir}/config</directory>\n            <outputDirectory>/config</outputDirectory>\n            <includes>\n                <include>*</include>\n            </includes>\n        </fileSet>\n        <fileSet>\n            <directory>${project.build.directory}</directory>\n            <outputDirectory>/</outputDirectory>\n            <includes>\n                <include>${project.name}-${project.version}.jar</include>\n            </includes>\n        </fileSet>\n    </fileSets>\n</assembly>"
  },
  {
    "path": "config/sample.yml",
    "content": "organization:\n  name: # Your name (eg. Open Whisper Systems)\n  donationUrl: # A Coinbase link where you can receive donations (eg. https://coinbase.com/checkouts/d29fd4c37ca442393e32fdcb95304701)\n\ngithub:\n  user: # Your BitHub instance's GitHub username.\n  token: # Your BitHub instance's GitHub auth token.\n\n  webhook:\n    password: # HTTP basic auth. The username defaults to \"bithub\".\n\n  repositories: # A list of repository URLs to support payouts for.\n    - url: # A repository's URL\n      mode: # Either MONEYMONEY (default) or FREEBIE.\n            # The former will pay out on every commit, unless\n            # FREEBIE is specified in the message.  The latter will\n            # only pay out if MONEYMONEY is specified in the message.\n\ncoinbase:\n  apiKey: # Your Coinbase API key.\n  apiSecret: # Your Coinbase API secret.\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <prerequisites>\n        <maven>3.0.0</maven>\n    </prerequisites>\n\n    <groupId>org.whispersystems.bithub</groupId>\n    <artifactId>BitHub</artifactId>\n    <version>0.1</version>\n\n    <properties>\n        <dropwizard.version>0.7.0</dropwizard.version>\n    </properties>\n\n    <dependencies>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-core</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-auth</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-jdbi</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-client</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-migrations</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-testing</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-metrics-graphite</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-views</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-views-mustache</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n        <dependency>\n            <groupId>io.dropwizard</groupId>\n            <artifactId>dropwizard-servlets</artifactId>\n            <version>${dropwizard.version}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.sun.jersey</groupId>\n            <artifactId>jersey-json</artifactId>\n            <version>1.18.1</version>\n        </dependency>\n        <dependency>\n            <groupId>com.codahale.metrics</groupId>\n            <artifactId>metrics-graphite</artifactId>\n            <version>3.0.2</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.sun.jersey.contribs</groupId>\n            <artifactId>jersey-multipart</artifactId>\n            <version>1.18.1</version>\n        </dependency>\n        <dependency>\n            <groupId>commons-net</groupId>\n            <artifactId>commons-net</artifactId>\n            <version>3.2</version>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-lang3</artifactId>\n            <version>3.1</version>\n        </dependency>\n\n        <dependency>\n            <groupId>com.coinbase.api</groupId>\n            <artifactId>coinbase-java</artifactId>\n            <version>1.9.1</version>\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-annotations</artifactId>\n            <version>2.4.3</version>\n        </dependency>\n\n    </dependencies>\n\n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-compiler-plugin</artifactId>\n                <configuration>\n                    <source>1.7</source>\n                    <target>1.7</target>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <version>2.2.1</version>\n                <executions>\n                    <execution>\n                        <id>attach-sources</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-jar-plugin</artifactId>\n                <version>2.4</version>\n                <configuration>\n                    <archive>\n                        <manifest>\n                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>\n                        </manifest>\n                    </archive>\n                </configuration>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-shade-plugin</artifactId>\n                <version>1.6</version>\n                <configuration>\n                    <createDependencyReducedPom>true</createDependencyReducedPom>\n                    <filters>\n                        <filter>\n                            <artifact>*:*</artifact>\n                            <excludes>\n                                <exclude>META-INF/*.SF</exclude>\n                                <exclude>META-INF/*.DSA</exclude>\n                                <exclude>META-INF/*.RSA</exclude>\n                            </excludes>\n                        </filter>\n                    </filters>\n                </configuration>\n                <executions>\n                    <execution>\n                        <phase>package</phase>\n                        <goals>\n                            <goal>shade</goal>\n                        </goals>\n                        <configuration>\n                            <transformers>\n                                <transformer implementation=\"org.apache.maven.plugins.shade.resource.ServicesResourceTransformer\"/>\n                                <transformer implementation=\"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer\">\n                                    <mainClass>org.whispersystems.bithub.BithubService</mainClass>\n                                </transformer>\n                            </transformers>\n                        </configuration>\n                    </execution>\n                </executions>\n            </plugin>\n\n            <plugin>\n                <artifactId>maven-assembly-plugin</artifactId>\n                <version>2.4</version>\n                <configuration>\n                    <descriptors>\n                        <descriptor>assembly.xml</descriptor>\n                    </descriptors>\n                </configuration>\n                <executions>\n                    <execution>\n                        <id>make-assembly</id> <!-- this is used for inheritance merges -->\n                        <phase>package</phase> <!-- bind to the packaging phase -->\n                        <goals>\n                            <goal>single</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n\n        </plugins>\n    </build>\n\n</project>\n\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/BithubServerConfiguration.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.whispersystems.bithub.config.BithubConfiguration;\nimport org.whispersystems.bithub.config.CoinbaseConfiguration;\nimport org.whispersystems.bithub.config.GithubConfiguration;\nimport org.whispersystems.bithub.config.OrganizationConfiguration;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotNull;\n\nimport io.dropwizard.Configuration;\n\npublic class BithubServerConfiguration extends Configuration {\n\n  @Valid\n  @NotNull\n  @JsonProperty\n  private GithubConfiguration github;\n\n  @Valid\n  @NotNull\n  @JsonProperty\n  private CoinbaseConfiguration coinbase;\n\n  @JsonProperty\n  @Valid\n  private BithubConfiguration bithub = new BithubConfiguration();\n\n  @Valid\n  @NotNull\n  @JsonProperty\n  private OrganizationConfiguration organization;\n\n\n  public GithubConfiguration getGithubConfiguration() {\n    return github;\n  }\n\n  public CoinbaseConfiguration getCoinbaseConfiguration() {\n    return coinbase;\n  }\n\n  public BithubConfiguration getBithubConfiguration() {\n    return bithub;\n  }\n\n  public OrganizationConfiguration getOrganizationConfiguration() {\n    return organization;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/BithubService.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub;\n\nimport org.eclipse.jetty.servlets.CrossOriginFilter;\nimport org.whispersystems.bithub.auth.GithubWebhookAuthenticator;\nimport org.whispersystems.bithub.client.CoinbaseClient;\nimport org.whispersystems.bithub.client.GithubClient;\nimport org.whispersystems.bithub.config.CoinbaseConfiguration;\nimport org.whispersystems.bithub.config.RepositoryConfiguration;\nimport org.whispersystems.bithub.controllers.DashboardController;\nimport org.whispersystems.bithub.controllers.GithubController;\nimport org.whispersystems.bithub.controllers.StatusController;\nimport org.whispersystems.bithub.mappers.IOExceptionMapper;\nimport org.whispersystems.bithub.mappers.UnauthorizedHookExceptionMapper;\nimport org.whispersystems.bithub.storage.CacheManager;\n\nimport javax.servlet.DispatcherType;\nimport java.math.BigDecimal;\nimport java.util.EnumSet;\nimport java.util.List;\n\nimport io.dropwizard.Application;\nimport io.dropwizard.auth.basic.BasicAuthProvider;\nimport io.dropwizard.setup.Bootstrap;\nimport io.dropwizard.setup.Environment;\nimport io.dropwizard.views.ViewBundle;\n\n/**\n * The main entry point for the service.\n *\n * @author Moxie Marlinspike\n */\npublic class BithubService extends Application<BithubServerConfiguration> {\n\n  @Override\n  public void initialize(Bootstrap<BithubServerConfiguration> bootstrap) {\n    bootstrap.addBundle(new ViewBundle());\n  }\n\n  @Override\n  public void run(BithubServerConfiguration config, Environment environment)\n      throws Exception\n  {\n    String                        githubUser         = config.getGithubConfiguration().getUser();\n    String                        githubToken        = config.getGithubConfiguration().getToken();\n    String                        githubWebhookUser  = config.getGithubConfiguration().getWebhookConfiguration().getUsername();\n    String                        githubWebhookPwd   = config.getGithubConfiguration().getWebhookConfiguration().getPassword();\n    List<RepositoryConfiguration> githubRepositories = config.getGithubConfiguration().getRepositories();\n    BigDecimal                    payoutRate         = config.getBithubConfiguration().getPayoutRate();\n    String                        organizationName   = config.getOrganizationConfiguration().getName();\n    String                        donationUrl        = config.getOrganizationConfiguration().getDonationUrl().toExternalForm();\n    String                        coinbaseApiKey     = config.getCoinbaseConfiguration().getApiKey();\n    String                        coinbaseApiSecret  = config.getCoinbaseConfiguration().getApiSecret();\n\n    GithubClient   githubClient   = new GithubClient(githubUser, githubToken);\n    CoinbaseClient coinbaseClient = new CoinbaseClient(coinbaseApiKey, coinbaseApiSecret);\n    CacheManager   cacheManager   = new CacheManager(coinbaseClient, githubClient, githubRepositories, payoutRate);\n\n    environment.servlets().addFilter(\"CORS\", CrossOriginFilter.class)\n               .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, \"/*\");\n\n    environment.lifecycle().manage(cacheManager);\n\n    environment.jersey().register(new GithubController(githubRepositories, githubClient, coinbaseClient, payoutRate));\n    environment.jersey().register(new StatusController(cacheManager, githubRepositories));\n    environment.jersey().register(new DashboardController(organizationName, donationUrl, cacheManager));\n\n    environment.jersey().register(new IOExceptionMapper());\n    environment.jersey().register(new UnauthorizedHookExceptionMapper());\n    environment.jersey().register(new BasicAuthProvider<>(new GithubWebhookAuthenticator(githubWebhookUser, githubWebhookPwd),\n                                                          GithubWebhookAuthenticator.REALM));\n  }\n\n  public static void main(String[] args) throws Exception {\n    new BithubService().run(args);\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/auth/GithubWebhookAuthenticator.java",
    "content": "package org.whispersystems.bithub.auth;\n\nimport com.google.common.base.Optional;\n\nimport io.dropwizard.auth.Authenticator;\nimport io.dropwizard.auth.basic.BasicCredentials;\n\n/**\n * Accepts only one fixed username/password combination.\n */\npublic class GithubWebhookAuthenticator implements Authenticator<BasicCredentials, GithubWebhookAuthenticator.Authentication> {\n\n  /**\n   * Represents a successful basic HTTP authentication.\n   */\n  public static class Authentication {\n  }\n\n  public static final String REALM = \"bithub\";\n\n  private final BasicCredentials correctCredentials;\n\n  public GithubWebhookAuthenticator(String username, String password) {\n    this.correctCredentials = new BasicCredentials(username, password);\n  }\n\n  @Override\n  public Optional<Authentication> authenticate(BasicCredentials clientCredentials) {\n    if (correctCredentials.equals(clientCredentials)) {\n      return Optional.of(new Authentication());\n    } else {\n      return Optional.absent();\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.client;\n\nimport com.coinbase.api.Coinbase;\nimport com.coinbase.api.CoinbaseBuilder;\nimport com.coinbase.api.entity.Account;\nimport com.coinbase.api.entity.Transaction;\nimport com.coinbase.api.exception.CoinbaseException;\nimport org.joda.money.CurrencyUnit;\nimport org.joda.money.Money;\nimport org.whispersystems.bithub.entities.Author;\n\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.util.List;\n\n/**\n * Handles interaction with the Coinbase API.\n *\n * @author Moxie Marlinspike\n */\npublic class CoinbaseClient {\n\n  private final Coinbase coinbase;\n\n  public CoinbaseClient(String apiKey, String apiSecret) {\n    this.coinbase = new CoinbaseBuilder().withApiKey(apiKey, apiSecret).build();\n  }\n\n  public List<Transaction> getRecentTransactions()\n      throws CoinbaseException, IOException\n  {\n    return coinbase.getTransactions().getTransactions();\n  }\n\n  public BigDecimal getExchangeRate() throws IOException, CoinbaseException {\n    return coinbase.getExchangeRates().get(\"btc_to_usd\");\n  }\n\n  public void sendPayment(Author author, BigDecimal amount, String url)\n      throws TransferFailedException\n  {\n    try {\n      String note = \"Commit payment:\\n__\" + author.getUsername() + \"__ \" + url;\n\n      Transaction transaction = new Transaction();\n      transaction.setTo(author.getEmail());\n      transaction.setAmount(Money.of(CurrencyUnit.of(\"BTC\"), amount, RoundingMode.DOWN));\n      transaction.setNotes(note);\n\n      Transaction response = coinbase.sendMoney(transaction);\n\n      if (response.getStatus() != Transaction.Status.COMPLETE) {\n        throw new TransferFailedException();\n      }\n    } catch (CoinbaseException | IOException e) {\n      throw new TransferFailedException(e);\n    }\n  }\n\n  public BigDecimal getAccountBalance() throws IOException, CoinbaseException {\n    List<Account> accounts = coinbase.getAccounts().getAccounts();\n    Account       primary  = null;\n\n    for (Account account : accounts) {\n      if (account.isPrimary()) {\n        primary = account;\n        break;\n      }\n    }\n\n    if (primary != null) return coinbase.getBalance(primary.getId()).getAmount();\n    else                 return new BigDecimal(0.0);\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/client/GithubClient.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.client;\n\nimport com.sun.jersey.api.client.Client;\nimport com.sun.jersey.api.client.ClientHandlerException;\nimport com.sun.jersey.api.client.ClientResponse;\nimport com.sun.jersey.api.client.UniformInterfaceException;\nimport com.sun.jersey.api.client.WebResource;\nimport com.sun.jersey.api.client.config.ClientConfig;\nimport com.sun.jersey.api.client.config.DefaultClientConfig;\nimport com.sun.jersey.api.json.JSONConfiguration;\nimport com.sun.jersey.core.util.Base64;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.whispersystems.bithub.entities.Commit;\nimport org.whispersystems.bithub.entities.CommitComment;\nimport org.whispersystems.bithub.entities.Repository;\n\nimport javax.ws.rs.core.MediaType;\n\n/**\n * Handles interaction with the GitHub API.\n *\n * @author Moxie Marlinspike\n */\npublic class GithubClient {\n\n  private static final String GITHUB_URL      = \"https://api.github.com/\";\n  private static final String COMMENT_PATH    = \"/repos/%s/%s/commits/%s/comments\";\n  private static final String COMMIT_PATH     = \"/repos/%s/%s/git/commits/%s\";\n  private static final String REPOSITORY_PATH = \"/repos/%s/%s\";\n\n  private final Logger logger = LoggerFactory.getLogger(GithubClient.class);\n\n  private final String authorizationHeader;\n  private final Client client;\n\n  public GithubClient(String user, String token) {\n    this.authorizationHeader = getAuthorizationHeader(user, token);\n    this.client              = Client.create(getClientConfig());\n  }\n\n  public String getCommitDescription(String commitUrl) {\n    String[] commitUrlParts = commitUrl.split(\"/\");\n    String   owner          = commitUrlParts[commitUrlParts.length - 4];\n    String   repository     = commitUrlParts[commitUrlParts.length - 3];\n    String   commit         = commitUrlParts[commitUrlParts.length - 1];\n\n    String      path     = String.format(COMMIT_PATH, owner, repository, commit);\n    WebResource resource = client.resource(GITHUB_URL).path(path);\n    Commit      response = resource.type(MediaType.APPLICATION_JSON_TYPE)\n                                   .accept(MediaType.APPLICATION_JSON_TYPE)\n                                   .header(\"Authorization\", authorizationHeader)\n                                   .get(Commit.class);\n\n    return response.getMessage();\n  }\n\n  public Repository getRepository(String url) {\n    String[] urlParts = url.split(\"/\");\n    String   owner    = urlParts[urlParts.length - 2];\n    String   name     = urlParts[urlParts.length - 1];\n\n    String      path     = String.format(REPOSITORY_PATH, owner, name);\n    WebResource resource = client.resource(GITHUB_URL).path(path);\n\n    return resource.type(MediaType.APPLICATION_JSON_TYPE)\n                   .accept(MediaType.APPLICATION_JSON_TYPE)\n                   .header(\"Authorization\", authorizationHeader)\n                   .get(Repository.class);\n\n  }\n\n  public void addCommitComment(Repository repository, Commit commit, String comment) {\n    try {\n      String path = String.format(COMMENT_PATH, repository.getOwner().getName(),\n                                  repository.getName(), commit.getSha());\n\n      WebResource    resource = client.resource(GITHUB_URL).path(path);\n      ClientResponse response = resource.type(MediaType.APPLICATION_JSON_TYPE)\n                                        .accept(MediaType.APPLICATION_JSON)\n                                        .header(\"Authorization\", authorizationHeader)\n                                        .entity(new CommitComment(comment))\n                                        .post(ClientResponse.class);\n\n      if (response.getStatus() < 200 || response.getStatus() >=300) {\n        logger.warn(\"Commit comment failed: \" + response.getClientResponseStatus().getReasonPhrase());\n      }\n\n    } catch (UniformInterfaceException | ClientHandlerException e) {\n      logger.warn(\"Comment failed\", e);\n    }\n  }\n\n  private ClientConfig getClientConfig() {\n    ClientConfig config = new DefaultClientConfig();\n    config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);\n\n    return config;\n  }\n\n  private String getAuthorizationHeader(String user, String token) {\n    return \"Basic \" + new String(Base64.encode(user + \":\" + token));\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/client/TransferFailedException.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.client;\n\npublic class TransferFailedException extends Exception {\n\n  public TransferFailedException() {\n    super();\n  }\n\n  public TransferFailedException(Throwable e) {\n    super(e);\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/BithubConfiguration.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.config;\n\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport org.hibernate.validator.constraints.NotEmpty;\n\nimport java.math.BigDecimal;\n\npublic class BithubConfiguration {\n\n  @JsonProperty\n  @NotEmpty\n  private String payout = \"0.02\";\n\n  public BigDecimal getPayoutRate() {\n    return new BigDecimal(payout);\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/CoinbaseConfiguration.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.config;\n\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.validator.constraints.NotEmpty;\n\npublic class CoinbaseConfiguration {\n\n  @JsonProperty\n  @NotEmpty\n  private String apiKey;\n\n  @JsonProperty\n  @NotEmpty\n  private String apiSecret;\n\n  public String getApiKey() {\n    return apiKey;\n  }\n\n  public String getApiSecret() {\n    return apiSecret;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/GithubConfiguration.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.config;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.hibernate.validator.constraints.NotEmpty;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.validation.Valid;\nimport javax.validation.constraints.NotNull;\nimport java.io.IOException;\nimport java.util.LinkedList;\nimport java.util.List;\n\npublic class GithubConfiguration {\n\n  private final Logger logger = LoggerFactory.getLogger(GithubConfiguration.class);\n\n  @JsonProperty\n  @NotEmpty\n  private String user;\n\n  @JsonProperty\n  @NotEmpty\n  private String token;\n\n  @JsonProperty\n  private List<RepositoryConfiguration> repositories;\n\n  @JsonProperty\n  private String repositories_heroku;\n\n  @Valid\n  @NotNull\n  @JsonProperty\n  private WebhookConfiguration webhook;\n\n  public String getUser() {\n    return user;\n  }\n\n  public String getToken() {\n    return token;\n  }\n\n  public List<RepositoryConfiguration> getRepositories() {\n    if (repositories != null) {\n      return repositories;\n    }\n\n    if (repositories_heroku != null) {\n      try {\n        ObjectMapper mapper = new ObjectMapper();\n        return mapper.readValue(repositories_heroku, new TypeReference<List<RepositoryConfiguration>>() {});\n      } catch (IOException e) {\n        logger.warn(\"Error deserializing\", e);\n      }\n    }\n\n    return new LinkedList<>();\n  }\n\n  public WebhookConfiguration getWebhookConfiguration() {\n    return webhook;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/OrganizationConfiguration.java",
    "content": "package org.whispersystems.bithub.config;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.validator.constraints.NotEmpty;\n\nimport javax.validation.Valid;\nimport java.net.URL;\n\npublic class OrganizationConfiguration {\n\n  @JsonProperty\n  @NotEmpty\n  private String name;\n\n  @JsonProperty\n  @Valid\n  private URL donationUrl;\n\n  public String getName() {\n    return name;\n  }\n\n  public URL getDonationUrl() {\n    return donationUrl;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/RepositoryConfiguration.java",
    "content": "package org.whispersystems.bithub.config;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.validator.constraints.NotEmpty;\n\npublic class RepositoryConfiguration {\n\n  @JsonProperty\n  @NotEmpty\n  private String url;\n\n  @JsonProperty\n  @NotEmpty\n  private String mode = \"MONEYMONEY\";\n\n  public RepositoryConfiguration(String url, String mode) {\n    this.url  = url;\n    this.mode = mode;\n  }\n\n  public RepositoryConfiguration(String url) {\n    this.url = url;\n  }\n\n  public RepositoryConfiguration() {}\n\n  public String getUrl() {\n    return url;\n  }\n\n  public String getMode() {\n    return mode;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/WebhookConfiguration.java",
    "content": "package org.whispersystems.bithub.config;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.validator.constraints.NotEmpty;\n\npublic class WebhookConfiguration {\n\n  @JsonProperty\n  @NotEmpty\n  private String username = \"bithub\";\n\n  @JsonProperty\n  @NotEmpty\n  private String password;\n\n  public String getUsername() { return username; }\n\n  public String getPassword() { return password; }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/controllers/DashboardController.java",
    "content": "package org.whispersystems.bithub.controllers;\n\nimport com.codahale.metrics.annotation.Timed;\nimport org.whispersystems.bithub.storage.CacheManager;\nimport org.whispersystems.bithub.views.DashboardView;\n\nimport javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs.core.MediaType;\n\n@Path(\"/\")\npublic class DashboardController {\n\n  private final CacheManager cacheManager;\n  private final String       organizationName;\n  private final String       donationUrl;\n\n\n\n  public DashboardController(String organizationName, String donationUrl,\n                             CacheManager cacheManager)\n  {\n    this.organizationName = organizationName;\n    this.donationUrl      = donationUrl;\n    this.cacheManager     = cacheManager;\n  }\n\n  @Timed\n  @GET\n  @Produces(MediaType.TEXT_HTML)\n  public DashboardView getDashboard() {\n    return new DashboardView(organizationName, donationUrl,\n                             cacheManager.getCurrentPaymentAmount(),\n                             cacheManager.getRepositories(),\n                             cacheManager.getRecentTransactions());\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/controllers/GithubController.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.controllers;\n\nimport com.codahale.metrics.annotation.Timed;\nimport com.coinbase.api.exception.CoinbaseException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.apache.commons.net.util.SubnetUtils;\nimport org.apache.commons.net.util.SubnetUtils.SubnetInfo;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.whispersystems.bithub.auth.GithubWebhookAuthenticator.Authentication;\nimport org.whispersystems.bithub.client.CoinbaseClient;\nimport org.whispersystems.bithub.client.GithubClient;\nimport org.whispersystems.bithub.client.TransferFailedException;\nimport org.whispersystems.bithub.config.RepositoryConfiguration;\nimport org.whispersystems.bithub.entities.Commit;\nimport org.whispersystems.bithub.entities.PushEvent;\nimport org.whispersystems.bithub.entities.Repository;\n\nimport javax.validation.Validation;\nimport javax.validation.Validator;\nimport javax.validation.ValidatorFactory;\nimport javax.ws.rs.Consumes;\nimport javax.ws.rs.FormParam;\nimport javax.ws.rs.HeaderParam;\nimport javax.ws.rs.POST;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.core.MediaType;\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport io.dropwizard.auth.Auth;\n\n/**\n * Handles incoming API calls from GitHub.  These are currently only\n * PushEvent webhooks.\n *\n * @author Moxie Marlinspike\n */\n@Path(\"/v1/github\")\npublic class GithubController {\n\n  private static final String GITHUB_WEBOOK_CIDR = \"192.30.252.0/22\";\n  private static final String MASTER_REF         = \"refs/heads/master\";\n\n  private final Logger     logger         = LoggerFactory.getLogger(GithubController.class);\n  private final SubnetInfo trustedNetwork = new SubnetUtils(GITHUB_WEBOOK_CIDR).getInfo();\n\n  private final CoinbaseClient      coinbaseClient;\n  private final GithubClient        githubClient;\n  private final Map<String, String> repositories;\n  private final BigDecimal          payoutRate;\n\n  public GithubController(List<RepositoryConfiguration> repositories,\n                          GithubClient githubClient,\n                          CoinbaseClient coinbaseClient,\n                          BigDecimal payoutRate)\n  {\n    this.coinbaseClient = coinbaseClient;\n    this.githubClient   = githubClient;\n    this.repositories   = new HashMap<>();\n    this.payoutRate     = payoutRate;\n\n    for (RepositoryConfiguration repository : repositories) {\n      this.repositories.put(repository.getUrl().toLowerCase(),\n                            repository.getMode().toUpperCase());\n    }\n  }\n\n  @Timed\n  @POST\n  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)\n  @Path(\"/commits/\")\n  public void handleCommits(@Auth Authentication auth,\n                            @HeaderParam(\"X-Forwarded-For\") String clientIp,\n                            @FormParam(\"payload\") String eventString)\n      throws IOException, UnauthorizedHookException, TransferFailedException, CoinbaseException\n  {\n    authenticate(clientIp);\n    PushEvent event = getEventFromPayload(eventString);\n\n    if (!repositories.containsKey(event.getRepository().getUrl().toLowerCase())) {\n      throw new UnauthorizedHookException(\"Not a valid repository: \" +\n                                          event.getRepository().getUrl());\n    }\n\n    if (!event.getRef().equals(MASTER_REF)) {\n      logger.info(\"Not a push to master: \" + event.getRef());\n      return;\n    }\n\n    Repository   repository   = event.getRepository();\n    String       defaultMode  = repositories.get(repository.getUrl().toLowerCase());\n    List<Commit> commits      = getQualifyingCommits(event, defaultMode);\n    BigDecimal   balance      = coinbaseClient.getAccountBalance();\n    BigDecimal   exchangeRate = coinbaseClient.getExchangeRate();\n\n    logger.info(\"Retrieved balance: \" + balance.toPlainString());\n\n    sendPaymentsFor(repository, commits, balance, exchangeRate);\n  }\n\n\n  private void sendPaymentsFor(Repository repository, List<Commit> commits,\n                               BigDecimal balance, BigDecimal exchangeRate)\n  {\n    for (Commit commit : commits) {\n      try {\n        BigDecimal payout = balance.multiply(payoutRate);\n\n        if (isViablePaymentAmount(payout)) {\n          coinbaseClient.sendPayment(commit.getAuthor(), payout, commit.getUrl());\n        }\n\n        balance = balance.subtract(payout);\n\n        githubClient.addCommitComment(repository, commit,\n                                      getCommitCommentStringForPayment(payout, exchangeRate));\n      } catch (TransferFailedException e) {\n        logger.warn(\"Transfer failed\", e);\n      }\n    }\n  }\n\n  private PushEvent getEventFromPayload(String payload) throws IOException {\n    ObjectMapper     objectMapper = new ObjectMapper();\n    PushEvent        event        = objectMapper.readValue(payload, PushEvent.class);\n    ValidatorFactory factory      = Validation.buildDefaultValidatorFactory();\n    Validator        validator    = factory.getValidator();\n\n    validator.validate(event);\n    return event;\n  }\n\n  private List<Commit> getQualifyingCommits(PushEvent event, String defaultMode) {\n    List<Commit> commits = new LinkedList<>();\n    Set<String>  emails  = new HashSet<>();\n\n    for (Commit commit : event.getCommits()) {\n      logger.info(commit.getUrl());\n      if (!emails.contains(commit.getAuthor().getEmail())) {\n        logger.info(\"Unique author: \"+ commit.getAuthor().getEmail());\n        if (isViableMessage(commit.getMessage(), defaultMode)) {\n          logger.info(\"Not a merge commit or freebie...\");\n\n          emails.add(commit.getAuthor().getEmail());\n          commits.add(commit);\n        }\n      }\n    }\n\n    return commits;\n  }\n\n  private boolean isViableMessage(String message, String defaultMode) {\n    if (message == null || message.startsWith(\"Merge\"))\n      return false;\n\n    return (!message.contains(\"FREEBIE\") && defaultMode.equals(\"MONEYMONEY\")) ||\n           (message.contains(\"MONEYMONEY\") && defaultMode.equals(\"FREEBIE\"));\n  }\n\n  private boolean isViablePaymentAmount(BigDecimal payment) {\n    return payment.compareTo(new BigDecimal(0)) == 1;\n  }\n\n  private String getCommitCommentStringForPayment(BigDecimal payment, BigDecimal exchangeRate) {\n    if (isViablePaymentAmount(payment)) {\n      BigDecimal paymentUsd = payment.multiply(exchangeRate).setScale(2, RoundingMode.CEILING);\n      return \"Thanks! BitHub has sent payment of  $\" + paymentUsd.toPlainString() + \"USD for this commit.\";\n    } else {\n      return \"Thanks! Unfortunately our BitHub balance is $0.00, so no payout can be made.\";\n    }\n  }\n\n  private void authenticate(String clientIp) throws UnauthorizedHookException {\n    if (clientIp == null) {\n      throw new UnauthorizedHookException(\"No X-Forwarded-For!\");\n    }\n\n    if (!trustedNetwork.isInRange(clientIp)) {\n      throw new UnauthorizedHookException(\"Untrusted IP: \" + clientIp);\n    }\n }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/controllers/StatusController.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.controllers;\n\nimport com.codahale.metrics.annotation.Timed;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.whispersystems.bithub.config.RepositoryConfiguration;\nimport org.whispersystems.bithub.entities.Repositories;\nimport org.whispersystems.bithub.entities.Repository;\nimport org.whispersystems.bithub.entities.Transaction;\nimport org.whispersystems.bithub.entities.Transactions;\nimport org.whispersystems.bithub.storage.CacheManager;\nimport org.whispersystems.bithub.storage.CurrentPayment;\nimport org.whispersystems.bithub.views.TransactionsView;\n\nimport javax.ws.rs.DefaultValue;\nimport javax.ws.rs.GET;\nimport javax.ws.rs.Path;\nimport javax.ws.rs.Produces;\nimport javax.ws.rs.QueryParam;\nimport javax.ws.rs.core.MediaType;\nimport javax.ws.rs.core.Response;\nimport java.io.IOException;\nimport java.util.LinkedList;\nimport java.util.List;\n\nimport io.dropwizard.jersey.caching.CacheControl;\n\n/**\n * Handles incoming API calls for BitHub instance status information.\n *\n * @author Moxie Marlinspike\n */\n@Path(\"/v1/status\")\npublic class StatusController {\n\n  private final Logger logger = LoggerFactory.getLogger(StatusController.class);\n\n  private final List<RepositoryConfiguration> repositoryConfiguration;\n  private final CacheManager coinbaseManager;\n\n  public StatusController(CacheManager coinbaseManager,\n                          List<RepositoryConfiguration> repositoryConfiguration)\n      throws IOException\n  {\n    this.coinbaseManager         = coinbaseManager;\n    this.repositoryConfiguration = repositoryConfiguration;\n  }\n\n  @Timed\n  @GET\n  @Path(\"/transactions\")\n  public Response getTransactions(@QueryParam(\"format\") @DefaultValue(\"html\") String format)\n        throws IOException\n  {\n    List<Transaction> recentTransactions = coinbaseManager.getRecentTransactions();\n\n    switch (format) {\n      case \"html\": return Response.ok(new TransactionsView(recentTransactions), MediaType.TEXT_HTML_TYPE).build();\n      case \"json\":\n      default:     return Response.ok(new Transactions(recentTransactions), MediaType.APPLICATION_JSON_TYPE).build();\n    }\n  }\n\n  @Timed\n  @GET\n  @Path(\"/repositories\")\n  @Produces(MediaType.APPLICATION_JSON)\n  public Repositories getRepositories() {\n    List<Repository> repositories = new LinkedList<>();\n\n    for (RepositoryConfiguration configuration : repositoryConfiguration) {\n      repositories.add(new Repository(configuration.getUrl()));\n    }\n\n    return new Repositories(repositories);\n  }\n\n\n  @Timed\n  @GET\n  @Path(\"/payment/commit\")\n  @CacheControl(noCache = true)\n  public Response getCurrentCommitPrice(@QueryParam(\"format\") @DefaultValue(\"png\") String format)\n      throws IOException\n  {\n    CurrentPayment currentPayment = coinbaseManager.getCurrentPaymentAmount();\n\n    switch (format) {\n      case \"json\":\n        return Response.ok(currentPayment.getEntity(), MediaType.APPLICATION_JSON_TYPE).build();\n      case \"png_small\":\n        return Response.ok(currentPayment.getSmallBadge(), \"image/png\").build();\n      default:\n        return Response.ok(currentPayment.getBadge(), \"image/png\").build();\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/controllers/UnauthorizedHookException.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.controllers;\n\npublic class UnauthorizedHookException extends Throwable {\n  public UnauthorizedHookException(String s) {\n    super(s);\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Author.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.entities;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.validator.constraints.NotEmpty;\n\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class Author {\n\n  @JsonProperty\n  private String name;\n\n  @JsonProperty\n  @NotEmpty\n  private String email;\n\n  @JsonProperty\n  private String username;\n\n  public String getName() {\n    return name;\n  }\n\n  public String getEmail() {\n    return email;\n  }\n\n  public String getUsername() {\n    return username;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Commit.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.entities;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport javax.validation.constraints.NotNull;\n\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class Commit {\n\n  @JsonProperty\n  private String  id;\n\n  @JsonProperty\n  private String  message;\n\n  @JsonProperty\n  @NotNull\n  private Author  author;\n\n  @JsonProperty\n  private String  url;\n\n  @JsonProperty\n  private boolean distinct;\n\n  public String getSha() {\n    return id;\n  }\n\n  public String getMessage() {\n    return message;\n  }\n\n  public Author getAuthor() {\n    return author;\n  }\n\n  public String getUrl() {\n    return url;\n  }\n\n  public boolean isDistinct() {\n    return distinct;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/CommitComment.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.entities;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class CommitComment {\n\n  @JsonProperty\n  private String body;\n\n  public CommitComment(String body) {\n    this.body = body;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Payment.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.entities;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Payment {\n  @JsonProperty\n  private String payment;\n\n  public Payment(String payment) {\n    this.payment = payment;\n  }\n\n  public String getPayment() {\n    return payment;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/PushEvent.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.entities;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport javax.validation.constraints.NotNull;\nimport java.util.List;\n\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class PushEvent {\n\n  @JsonProperty\n  private String head;\n\n  @JsonProperty\n  private String ref;\n\n  @JsonProperty\n  private int size;\n\n  @JsonProperty\n  @NotNull\n  List<Commit> commits;\n\n  @JsonProperty\n  @NotNull\n  Repository repository;\n\n  public Repository getRepository() {\n    return repository;\n  }\n\n  public String getHead() {\n    return head;\n  }\n\n  public String getRef() {\n    return ref;\n  }\n\n  public int getSize() {\n    return size;\n  }\n\n  public List<Commit> getCommits() {\n    return commits;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Repositories.java",
    "content": "package org.whispersystems.bithub.entities;\n\nimport java.util.List;\n\npublic class Repositories {\n\n  public List<Repository> repositories;\n\n  public Repositories() {}\n\n  public Repositories(List<Repository> repositories) {\n    this.repositories = repositories;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Repository.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.entities;\n\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.validator.constraints.NotEmpty;\n\nimport javax.validation.constraints.NotNull;\n\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class Repository {\n\n  @JsonProperty\n  @NotEmpty\n  private String url;\n\n  @JsonProperty\n  @NotNull\n  private Author owner;\n\n  @JsonProperty\n  @NotEmpty\n  private String name;\n\n  @JsonProperty\n  private String description;\n\n  public Repository() {}\n\n  public Repository(String url) {\n    this.url = url;\n  }\n\n  public Author getOwner() {\n    return owner;\n  }\n\n  public String getName() {\n    return name;\n  }\n\n  public String getUrl() {\n    return url;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Transaction.java",
    "content": "package org.whispersystems.bithub.entities;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Transaction {\n\n  @JsonProperty\n  private String destination;\n\n  @JsonProperty\n  private String amount;\n\n  @JsonProperty\n  private String commitUrl;\n\n  @JsonProperty\n  private String commitSha;\n\n  @JsonProperty\n  private String timestamp;\n\n  @JsonProperty\n  private String description;\n\n  public Transaction() {}\n\n  public Transaction(String destination, String amount, String commitUrl,\n                     String commitSha, String timestamp, String description)\n  {\n    this.destination = destination;\n    this.amount      = amount;\n    this.commitUrl   = commitUrl;\n    this.commitSha   = commitSha;\n    this.timestamp   = timestamp;\n    this.description = description;\n  }\n\n  public String getDestination() {\n    return destination;\n  }\n\n  public String getAmount() {\n    return amount;\n  }\n\n  public String getCommitUrl() {\n    return commitUrl;\n  }\n\n  public String getCommitSha() {\n    return commitSha;\n  }\n\n  public String getTimestamp() {\n    return timestamp;\n  }\n\n  public String getDescription() {\n    return description;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Transactions.java",
    "content": "package org.whispersystems.bithub.entities;\n\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.List;\n\npublic class Transactions {\n\n  @JsonProperty\n  private List<Transaction> transactions;\n\n  public Transactions() {}\n\n  public Transactions(List<Transaction> transactions) {\n    this.transactions = transactions;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/mappers/IOExceptionMapper.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.mappers;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport javax.ws.rs.core.Response;\nimport javax.ws.rs.ext.ExceptionMapper;\nimport javax.ws.rs.ext.Provider;\nimport java.io.IOException;\n\n@Provider\npublic class IOExceptionMapper implements ExceptionMapper<IOException> {\n\n  private final Logger logger = LoggerFactory.getLogger(IOExceptionMapper.class);\n\n  @Override\n  public Response toResponse(IOException e) {\n    logger.warn(\"IOExceptionMapper\", e);\n    return Response.status(503).build();\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/mappers/UnauthorizedHookExceptionMapper.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.mappers;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.whispersystems.bithub.controllers.UnauthorizedHookException;\n\nimport javax.ws.rs.core.Response;\nimport javax.ws.rs.ext.ExceptionMapper;\nimport javax.ws.rs.ext.Provider;\n\n@Provider\npublic class UnauthorizedHookExceptionMapper implements ExceptionMapper<UnauthorizedHookException> {\n\n  private final Logger logger = LoggerFactory.getLogger(IOExceptionMapper.class);\n\n  @Override\n  public Response toResponse(UnauthorizedHookException e) {\n    logger.warn(\"IOExceptionMapper\", e);\n    return Response.status(401).build();\n  }\n}"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/storage/CacheManager.java",
    "content": "package org.whispersystems.bithub.storage;\n\nimport com.coinbase.api.exception.CoinbaseException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.whispersystems.bithub.client.CoinbaseClient;\nimport org.whispersystems.bithub.client.GithubClient;\nimport org.whispersystems.bithub.config.RepositoryConfiguration;\nimport org.whispersystems.bithub.entities.Payment;\nimport org.whispersystems.bithub.entities.Repository;\nimport org.whispersystems.bithub.entities.Transaction;\nimport org.whispersystems.bithub.util.Badge;\n\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.text.ParseException;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.ScheduledExecutorService;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.atomic.AtomicReference;\n\nimport io.dropwizard.lifecycle.Managed;\n\npublic class CacheManager implements Managed {\n\n  private static final int UPDATE_FREQUENCY_MILLIS = 60 * 1000;\n\n  private final Logger                   logger   = LoggerFactory.getLogger(CacheManager.class);\n  private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);\n\n  private final CoinbaseClient                coinbaseClient;\n  private final GithubClient                  githubClient;\n  private final BigDecimal                    payoutRate;\n  private final List<RepositoryConfiguration> repositories;\n\n  private AtomicReference<CurrentPayment>    cachedPaymentStatus;\n  private AtomicReference<List<Transaction>> cachedTransactions;\n  private AtomicReference<List<Repository>>  cachedRepositories;\n\n  public CacheManager(CoinbaseClient coinbaseClient,\n                      GithubClient githubClient,\n                      List<RepositoryConfiguration> repositories,\n                      BigDecimal payoutRate)\n  {\n    this.coinbaseClient = coinbaseClient;\n    this.githubClient   = githubClient;\n    this.payoutRate     = payoutRate;\n    this.repositories   = repositories;\n  }\n\n  @Override\n  public void start() throws Exception {\n    this.cachedPaymentStatus = new AtomicReference<>(createCurrentPaymentForBalance(coinbaseClient));\n    this.cachedTransactions  = new AtomicReference<>(createRecentTransactions(coinbaseClient));\n    this.cachedRepositories  = new AtomicReference<>(createRepositories(githubClient, repositories));\n\n    initializeUpdates(coinbaseClient, githubClient, repositories);\n  }\n\n  @Override\n  public void stop() throws Exception {\n    this.executor.shutdownNow();\n  }\n\n  public List<Transaction> getRecentTransactions() {\n    return cachedTransactions.get();\n  }\n\n  public CurrentPayment getCurrentPaymentAmount() {\n    return cachedPaymentStatus.get();\n  }\n\n  public List<Repository> getRepositories() {\n    return cachedRepositories.get();\n  }\n\n  public void initializeUpdates(final CoinbaseClient coinbaseClient,\n                                final GithubClient githubClient,\n                                final List<RepositoryConfiguration> repoConfigs)\n  {\n    executor.scheduleAtFixedRate(new Runnable() {\n      @Override\n      public void run() {\n        logger.warn(\"Running cache update...\");\n        try {\n          CurrentPayment    currentPayment = createCurrentPaymentForBalance(coinbaseClient);\n          List<Transaction> transactions   = createRecentTransactions      (coinbaseClient);\n          List<Repository>  repositories   = createRepositories(githubClient, repoConfigs);\n\n          cachedPaymentStatus.set(currentPayment);\n          cachedTransactions.set(transactions);\n          cachedRepositories.set(repositories);\n\n        } catch (IOException | CoinbaseException e) {\n          logger.warn(\"Failed to update badge\", e);\n        }\n      }\n    }, UPDATE_FREQUENCY_MILLIS, UPDATE_FREQUENCY_MILLIS, TimeUnit.MILLISECONDS);\n  }\n\n  private List<Repository> createRepositories(GithubClient githubClient,\n                                              List<RepositoryConfiguration> configured)\n  {\n    List<Repository> repositoryList = new LinkedList<>();\n\n    for (RepositoryConfiguration repository : configured) {\n      repositoryList.add(githubClient.getRepository(repository.getUrl()));\n    }\n\n    return repositoryList;\n  }\n\n  private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseClient)\n      throws IOException, CoinbaseException\n  {\n    BigDecimal currentBalance = coinbaseClient.getAccountBalance();\n    BigDecimal paymentBtc     = currentBalance.multiply(payoutRate);\n    BigDecimal exchangeRate   = coinbaseClient.getExchangeRate();\n    BigDecimal paymentUsd     = paymentBtc.multiply(exchangeRate);\n\n    paymentUsd = paymentUsd.setScale(2, RoundingMode.CEILING);\n    return new CurrentPayment(Badge.createFor(paymentUsd.toPlainString()),\n                              Badge.createSmallFor(paymentUsd.toPlainString()),\n                              new Payment(paymentUsd.toPlainString()));\n  }\n\n  private List<Transaction> createRecentTransactions(CoinbaseClient coinbaseClient)\n          throws IOException, CoinbaseException\n  {\n    List<com.coinbase.api.entity.Transaction> recentTransactions = coinbaseClient.getRecentTransactions();\n    BigDecimal                                exchangeRate       = coinbaseClient.getExchangeRate();\n    List<Transaction>                         transactions       = new LinkedList<>();\n\n    for (com.coinbase.api.entity.Transaction coinbaseTransaction : recentTransactions) {\n      try {\n        if (isSentTransaction(coinbaseTransaction)) {\n          CoinbaseTransactionParser parser      = new CoinbaseTransactionParser(coinbaseTransaction);\n          String                    url         = parser.parseUrlFromMessage();\n          String                    sha         = parser.parseShaFromUrl(url);\n          String                    description = githubClient.getCommitDescription(url);\n\n          transactions.add(new Transaction(parser.parseDestinationFromMessage(),\n                                           parser.parseAmountInDollars(exchangeRate),\n                                           url, sha, parser.parseTimestamp(),\n                                           description));\n\n          if (transactions.size() >= 10)\n            break;\n        }\n      } catch (ParseException e) {\n        logger.warn(\"Parse\", e);\n      }\n    }\n\n    return transactions;\n  }\n\n  private boolean isSentTransaction(com.coinbase.api.entity.Transaction coinbaseTransaction) {\n    BigDecimal amount = coinbaseTransaction.getAmount().getAmount();\n    return amount.compareTo(new BigDecimal(0.0)) < 0;\n  }\n\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/storage/CoinbaseTransactionParser.java",
    "content": "package org.whispersystems.bithub.storage;\n\nimport com.coinbase.api.entity.Transaction;\nimport org.apache.commons.lang3.StringEscapeUtils;\nimport org.joda.time.DateTime;\nimport org.joda.time.format.DateTimeFormat;\nimport org.joda.time.format.DateTimeFormatter;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\nimport java.text.ParseException;\n\npublic class CoinbaseTransactionParser {\n\n  private final Transaction coinbaseTransaction;\n\n  public CoinbaseTransactionParser(Transaction coinbaseTransaction) {\n    this.coinbaseTransaction = coinbaseTransaction;\n  }\n\n  public String parseAmountInDollars(BigDecimal exchangeRate) {\n    return coinbaseTransaction.getAmount().getAmount().abs()\n                              .multiply(exchangeRate)\n                              .setScale(2, RoundingMode.CEILING)\n                              .toPlainString();\n  }\n\n  public String parseTimestamp() throws ParseException {\n    DateTime          timestamp = coinbaseTransaction.getCreatedAt();\n    DateTimeFormatter fmt       = DateTimeFormat.forPattern(\"yyyy-MM-dd'T'HH:mm:ssZ\");\n    return fmt.print(timestamp);\n  }\n\n  public String parseDestinationFromMessage() {\n    String message = StringEscapeUtils.unescapeHtml4(coinbaseTransaction.getNotes());\n    int startToken = message.indexOf(\"__\");\n\n    if (startToken == -1) {\n      return \"Unknown\";\n    }\n\n    int endToken = message.indexOf(\"__\", startToken + 1);\n\n    if (endToken == -1) {\n      return \"Unknown\";\n    }\n\n    return message.substring(startToken+2, endToken);\n  }\n\n  public String parseUrlFromMessage() throws ParseException {\n    String message = StringEscapeUtils.unescapeHtml4(coinbaseTransaction.getNotes());\n    int urlIndex = message.indexOf(\"https://\");\n\n    return message.substring(urlIndex).trim();\n  }\n\n  public String parseShaFromUrl(String url) throws ParseException {\n    if (url == null) {\n      throw new ParseException(\"No url\", 0);\n    }\n\n    String[] parts    = url.split(\"/\");\n    String   fullHash = parts[parts.length-1];\n\n    if (fullHash.length() < 8) {\n      throw new ParseException(\"Not long enough\", 0);\n    }\n\n    return fullHash.substring(0, 8);\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/storage/CurrentPayment.java",
    "content": "package org.whispersystems.bithub.storage;\n\nimport org.whispersystems.bithub.entities.Payment;\n\npublic class CurrentPayment {\n\n  private final byte[]  badge;\n  private final byte[]  smallBadge;\n  private final Payment entity;\n\n  protected CurrentPayment(byte[] badge, byte[] smallBadge, Payment entity) {\n    this.badge      = badge;\n    this.smallBadge = smallBadge;\n    this.entity     = entity;\n  }\n\n  public byte[] getBadge() {\n    return badge;\n  }\n\n  public byte[] getSmallBadge() {\n    return smallBadge;\n  }\n\n  public Payment getEntity() {\n    return entity;\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/util/AdvancedAtomicLong.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.util;\n\nimport java.util.concurrent.atomic.AtomicLong;\n\npublic class AdvancedAtomicLong extends AtomicLong {\n\n  public AdvancedAtomicLong(long initial) {\n    super(initial);\n  }\n\n  public boolean setIfGreater(long compare, long update) {\n    while(true) {\n      long current = get();\n\n      if (compare > current) {\n        if (compareAndSet(current, update)) {\n          return true;\n        }\n      } else {\n        return false;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/util/Badge.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.util;\n\nimport com.google.common.io.Resources;\n\nimport javax.imageio.ImageIO;\nimport java.awt.*;\nimport java.awt.image.BufferedImage;\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\n\npublic class Badge {\n\n  public static byte[] createFor(String price) throws IOException {\n    byte[]        badgeBackground = Resources.toByteArray(Resources.getResource(\"assets/badge.png\"));\n    BufferedImage bufferedImage   = ImageIO.read(new ByteArrayInputStream(badgeBackground));\n    Graphics2D    graphics        = bufferedImage.createGraphics();\n\n    graphics.setFont(new Font(\"OpenSans\", Font.PLAIN, 34));\n    graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,\n                              RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);\n    graphics.drawString(price + \" USD\", 86, 45);\n    graphics.dispose();\n\n    ByteArrayOutputStream baos = new ByteArrayOutputStream();\n    ImageIO.write(bufferedImage, \"png\", baos);\n\n    return baos.toByteArray();\n  }\n\n  public static byte[] createSmallFor(String price) throws IOException {\n    byte[]        badgeBackground = Resources.toByteArray(Resources.getResource(\"assets/badge-small.png\"));\n    BufferedImage bufferedImage   = ImageIO.read(new ByteArrayInputStream(badgeBackground));\n    Graphics2D    graphics        = bufferedImage.createGraphics();\n\n    graphics.setFont(new Font(\"OpenSans\", Font.PLAIN, 9));\n    graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,\n                              RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);\n    graphics.drawString(price + \" USD\", 22, 14);\n    graphics.dispose();\n\n    ByteArrayOutputStream baos = new ByteArrayOutputStream();\n    ImageIO.write(bufferedImage, \"png\", baos);\n\n    return baos.toByteArray();\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/views/DashboardView.java",
    "content": "package org.whispersystems.bithub.views;\n\nimport org.whispersystems.bithub.entities.Repository;\nimport org.whispersystems.bithub.entities.Transaction;\nimport org.whispersystems.bithub.storage.CurrentPayment;\n\nimport java.util.List;\n\nimport io.dropwizard.views.View;\n\npublic class DashboardView extends View {\n\n  private final String            organizationName;\n  private final String            donationUrl;\n  private final CurrentPayment    currentPayment;\n  private final List<Repository>  repositories;\n  private final List<Transaction> transactions;\n\n  public DashboardView(String organizationName, String donationUrl,\n                       CurrentPayment currentPayment,\n                       List<Repository> repositories,\n                       List<Transaction> transactions)\n  {\n    super(\"dashboard.mustache\");\n    this.organizationName = organizationName;\n    this.donationUrl      = donationUrl;\n    this.currentPayment   = currentPayment;\n    this.repositories     = repositories;\n    this.transactions     = transactions;\n  }\n\n  public String getPayment() {\n    return currentPayment.getEntity().getPayment();\n  }\n\n  public String getOrganizationName() {\n    return organizationName;\n  }\n\n  public String getDonationUrl() {\n    return donationUrl;\n  }\n\n  public List<Repository> getRepositories() {\n    return repositories;\n  }\n\n  public List<Transaction> getTransactions() {\n    return transactions;\n  }\n\n  public String getRepositoriesCount() {\n    return String.valueOf(repositories.size());\n  }\n\n}\n"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/views/TransactionsView.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.views;\n\nimport org.whispersystems.bithub.entities.Transaction;\n\nimport java.util.List;\n\nimport io.dropwizard.views.View;\n\n/**\n * A rendered HTML view of recent BitHub transactions.\n *\n * @author Moxie Marlinspike\n */\npublic class TransactionsView extends View {\n\n  private final List<Transaction> transactions;\n\n  public TransactionsView(List<Transaction> transactions) {\n    super(\"recent_transactions.mustache\");\n    this.transactions = transactions;\n  }\n\n  public List<Transaction> getTransactions() {\n    return transactions;\n  }\n}\n"
  },
  {
    "path": "src/main/resources/banner.txt",
    "content": "888888b.   d8b 888    888    888          888      \n888  \"88b  Y8P 888    888    888          888      \n888  .88P      888    888    888          888      \n8888888K.  888 888888 8888888888 888  888 88888b.  \n888  \"Y88b 888 888    888    888 888  888 888 \"88b \n888    888 888 888    888    888 888  888 888  888 \n888   d88P 888 Y88b.  888    888 Y88b 888 888 d88P \n8888888P\"  888  \"Y888 888    888  \"Y88888 88888P\"  \n                                                   \n                                                   \n"
  },
  {
    "path": "src/main/resources/org/whispersystems/bithub/views/dashboard.mustache",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"\">\n    <link rel=\"icon\" href=\"../../favicon.ico\">\n\n    <title>BitHub :: Dashboard </title>\n\n    <!-- Bootstrap core CSS -->\n    <link href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css\" rel=\"stylesheet\">\n\n    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->\n    <!--[if lt IE 9]>\n    <script src=\"https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js\"></script>\n    <script src=\"https://oss.maxcdn.com/respond/1.4.2/respond.min.js\"></script>\n    <![endif]-->\n\n    <style type=\"text/css\">\n        body {\n            padding-top: 20px;\n        }\n\n        pre {\n            white-space: pre-wrap;       /* css-3 */\n            white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */\n            white-space: -pre-wrap;      /* Opera 4-6 */\n            white-space: -o-pre-wrap;    /* Opera 7 */\n            word-wrap: break-word;       /* Internet Explorer 5.5+ */\n\n            padding: 0px;\n            border: 0px;\n            background-color: transparent;\n            font-family: monospace;\n        }\n\n         /* top tags */\n\n        .hero-widget { text-align: center; padding-top: 20px; padding-bottom: 20px; }\n        .hero-widget .icon { display: block; font-size: 96px; line-height: 96px; margin-bottom: 10px; text-align: center; }\n        .hero-widget var { display: block; height: 64px; font-size: 64px; line-height: 64px; font-style: normal; }\n        .hero-widget label { font-size: 17px; }\n        .hero-widget .options { margin-top: 10px; }\n\n         /* repositories */\n\n        .repository {\n            margin-top: 20px;\n            padding: 40px 0px 20px 0px;\n            background-color: #f7f7f7;\n            -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);\n            -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);\n            box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);\n        }\n\n        .repository-name {\n            font-size: 50px;\n            padding: 15px;\n        }\n\n        .repository-name a {\n            text-decoration: none;\n        }\n\n        .repository-description {\n            font-size: 20px;\n            padding: 15px;\n        }\n    </style>\n</head>\n\n<body>\n\n<div class=\"container\">\n    <div class=\"row\">\n        <div class=\"col-sm-3\">\n            <h1>BitHub</h1>\n            <p class=\"lead\">{{organizationName}}</p>\n            <p><a class=\"btn btn-lg btn-success\" href=\"{{donationUrl}}\" role=\"button\">Donate BTC today</a></p>\n        </div>\n        <div class=\"col-sm-3\">\n            <div class=\"hero-widget well well-sm\">\n                <div class=\"icon\">\n                    <i class=\"glyphicon glyphicon-usd\"></i>\n                </div>\n                <div class=\"text\">\n                    <var>{{payment}}</var>\n                    <label class=\"text-muted\">USD per commit</label>\n                </div>\n            </div>\n        </div>\n        <div class=\"col-sm-3\">\n            <div class=\"hero-widget well well-sm\">\n                <div class=\"icon\">\n                    <i class=\"glyphicon glyphicon-tags\"></i>\n                </div>\n                <div class=\"text\">\n                    <var>0</var>\n                    <label class=\"text-muted\">open bounties</label>\n                </div>\n            </div>\n        </div>\n        <div class=\"col-sm-3\">\n            <div class=\"hero-widget well well-sm\">\n                <div class=\"icon\">\n                    <i class=\"glyphicon glyphicon-book\"></i>\n                </div>\n                <div class=\"text\">\n                    <var>{{repositoriesCount}}</var>\n                    <label class=\"text-muted\">repositories</label>\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <h2 class=\"sub-header\">Recent payments</h2>\n    <div class=\"table-responsive\">\n        <table class=\"table table-striped\">\n            <thead>\n            <tr>\n                <th>USD</th>\n                <th>Author</th>\n                <th>Description</th>\n                <th>Commit</th>\n            </tr>\n            </thead>\n            <tbody id=\"transactions_target\">\n                {{#transactions}}\n                <tr>\n                    <td>${{amount}} USD</td>\n                    <td><a href=\"https://github.com/{{destination}}\">{{destination}}</a></td>\n                    <td><pre>{{description}}</pre></td>\n                    <td><a href=\"{{commitUrl}}\">{{commitSha}}</a></td>\n                </tr>\n                {{/transactions}}\n            </tbody>\n        </table>\n    </div>\n\n    <h2 class=\"sub-header\">Repositories</h2>\n        <div class=\"row\">\n            {{#repositories}}\n            <div class=\"col-xs-12 col-sm-6 col-md-6 col-lg-6\">\n                <div class=\"repository\">\n                    <div class=\"repository-name\">\n                        <a href=\"{{url}}\">{{name}}</a>\n                    </div>\n                    <div class=\"repository-description\">\n                        <p>{{description}}</p>\n                    </div>\n                </div>\n            </div>\n            {{/repositories}}\n        </div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "src/main/resources/org/whispersystems/bithub/views/recent_transactions.mustache",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta http-equiv=\"Content-Security-Policy\" content=\"script-src none\">\n    <meta charset=\"utf-8\">\n\n    <link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>\n\n    <style>\n        body {\n            font-family: 'Open Sans', sans-serif;\n            letter-spacing: .01rem;\n            font-weight: 400;\n            font-style: normal;\n            font-size: 22px;\n            line-height: 1.5;\n            color: #333332;\n        }\n\n        sha {\n            font-family: Consolas, \"Liberation Mono\", Courier, monospace;\n            color: #4183c4;\n\n            margin: 2px;\n            padding: 3px;\n            border: 1px solid #ddd;\n            background-color: #f8f8f8;\n            border-radius: 3px;\n            font-size: 20px;\n        }\n\n        ul { list-style-type: none;}\n\n        li { padding: 5px; padding-left: 20px;}\n\n        li:nth-child(even) { background-color: #ffffff; }\n        li:nth-child(odd)  { background-color: #eeeeee;}\n\n        a { text-decoration: none; color: #555; }\n    </style>\n\n</head>\n<body>\n\n<ul>\n    {{#transactions}}\n    <li>Sent ${{amount}} USD to <a href=\"https://github.com/{{destination}}\" target=\"_blank\">{{destination}}</a> for <a href=\"{{commitUrl}}\" target=\"_blank\"><sha>{{commitSha}}</sha></a> {{timestamp}}.</li>\n    {{/transactions}}\n</ul>\n\n</body>\n</html>\n"
  },
  {
    "path": "src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java",
    "content": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage org.whispersystems.bithub.tests.controllers;\n\nimport com.sun.jersey.api.client.ClientResponse;\nimport com.sun.jersey.core.util.MultivaluedMapImpl;\nimport org.apache.commons.codec.binary.Base64;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.whispersystems.bithub.auth.GithubWebhookAuthenticator;\nimport org.whispersystems.bithub.client.CoinbaseClient;\nimport org.whispersystems.bithub.client.GithubClient;\nimport org.whispersystems.bithub.client.TransferFailedException;\nimport org.whispersystems.bithub.config.RepositoryConfiguration;\nimport org.whispersystems.bithub.controllers.GithubController;\nimport org.whispersystems.bithub.entities.Author;\nimport org.whispersystems.bithub.mappers.UnauthorizedHookExceptionMapper;\n\nimport javax.ws.rs.core.MediaType;\nimport java.io.InputStream;\nimport java.math.BigDecimal;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.Scanner;\n\nimport io.dropwizard.auth.basic.BasicAuthProvider;\nimport io.dropwizard.testing.junit.ResourceTestRule;\nimport static org.fest.assertions.api.Assertions.assertThat;\nimport static org.mockito.Matchers.any;\nimport static org.mockito.Matchers.anyString;\nimport static org.mockito.Matchers.eq;\nimport static org.mockito.Mockito.*;\n\npublic class GithubControllerTest {\n\n  private static final BigDecimal BALANCE = new BigDecimal(10.01);\n  private static final BigDecimal EXCHANGE_RATE = new BigDecimal(1.0);\n\n  private final CoinbaseClient coinbaseClient = mock(CoinbaseClient.class);\n  private final GithubClient   githubClient   = mock(GithubClient.class);\n\n  // HTTP Basic Authentication data\n  private final String authUsername = \"TestUser\";\n  private final String authPassword = \"TestPassword\";\n  private final String authRealm = GithubWebhookAuthenticator.REALM;\n  private final String authString = \"Basic \" + Base64.encodeBase64String((authUsername + \":\" + authPassword).getBytes());\n  private final String invalidUserAuthString = \"Basic \" + Base64.encodeBase64((\"wrong:\" + authPassword).getBytes());\n  private final String invalidPasswordAuthString = \"Basic \" + Base64.encodeBase64((authUsername + \":wrong\").getBytes());\n\n  private final List<RepositoryConfiguration> repositories = new LinkedList<RepositoryConfiguration>() {{\n    add(new RepositoryConfiguration(\"https://github.com/moxie0/test\"));\n    add(new RepositoryConfiguration(\"https://github.com/moxie0/optin\", \"FREEBIE\"));\n  }};\n\n  @Rule\n  public final ResourceTestRule resources = ResourceTestRule.builder()\n                                                            .addProvider(new UnauthorizedHookExceptionMapper())\n                                                            .addProvider(new BasicAuthProvider<>(new GithubWebhookAuthenticator(authUsername, authPassword), authRealm))\n                                                            .addResource(new GithubController(repositories, githubClient, coinbaseClient, new BigDecimal(0.02)))\n                                                            .build();\n\n\n  @Before\n  public void setup() throws Exception, TransferFailedException {\n    when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE);\n    when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE);\n  }\n\n  protected String payload(String path) {\n    InputStream is = this.getClass().getResourceAsStream(path);\n    Scanner s = new Scanner(is).useDelimiter(\"\\\\A\");\n    return s.hasNext() ? s.next() : \"\";\n  }\n\n  @Test\n  public void testInvalidRepository() throws Exception {\n    String payloadValue = payload(\"/payloads/invalid_repo.json\");\n    MultivaluedMapImpl post = new MultivaluedMapImpl();\n    post.add(\"payload\", payloadValue);\n    ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n        .header(\"X-Forwarded-For\", \"192.30.252.1\")\n        .header(\"Authorization\", authString)\n        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n        .post(ClientResponse.class, post);\n\n    assertThat(response.getStatus()).isEqualTo(401);\n  }\n\n  @Test\n  public void testInvalidOrigin() throws Exception {\n    String payloadValue = payload(\"/payloads/invalid_origin.json\");\n    MultivaluedMapImpl post = new MultivaluedMapImpl();\n    post.add(\"payload\", payloadValue);\n    ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n        .header(\"X-Forwarded-For\", \"192.30.242.1\")\n        .header(\"Authorization\", authString)\n        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n        .post(ClientResponse.class, post);\n\n    assertThat(response.getStatus()).isEqualTo(401);\n  }\n\n  @Test\n  public void testMissingAuth() throws Exception, TransferFailedException {\n      String payloadValue = payload(\"/payloads/valid_commit.json\");\n      MultivaluedMapImpl post = new MultivaluedMapImpl();\n      post.add(\"payload\", payloadValue);\n      ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n              .header(\"X-Forwarded-For\", \"192.30.252.1\")\n              .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n              .post(ClientResponse.class, post);\n\n    assertThat(response.getStatus()).isEqualTo(401);\n  }\n\n  @Test\n  public void testInvalidAuthUser() throws Exception, TransferFailedException {\n      String payloadValue = payload(\"/payloads/valid_commit.json\");\n      MultivaluedMapImpl post = new MultivaluedMapImpl();\n      post.add(\"payload\", payloadValue);\n      ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n              .header(\"X-Forwarded-For\", \"192.30.252.1\")\n              .header(\"Authorization\", invalidUserAuthString)\n              .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n              .post(ClientResponse.class, post);\n\n    assertThat(response.getStatus()).isEqualTo(401);\n  }\n\n  @Test\n  public void testInvalidAuthPassword() throws Exception, TransferFailedException {\n      String payloadValue = payload(\"/payloads/valid_commit.json\");\n      MultivaluedMapImpl post = new MultivaluedMapImpl();\n      post.add(\"payload\", payloadValue);\n      ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n              .header(\"X-Forwarded-For\", \"192.30.252.1\")\n              .header(\"Authorization\", invalidPasswordAuthString)\n              .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n              .post(ClientResponse.class, post);\n\n    assertThat(response.getStatus()).isEqualTo(401);\n  }\n\n  @Test\n  public void testOptOutCommit() throws Exception, TransferFailedException {\n    String payloadValue = payload(\"/payloads/opt_out_commit.json\");\n    MultivaluedMapImpl post = new MultivaluedMapImpl();\n    post.add(\"payload\", payloadValue);\n    ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n        .header(\"X-Forwarded-For\", \"192.30.252.1\")\n        .header(\"Authorization\", authString)\n        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n        .post(ClientResponse.class, post);\n\n    verify(coinbaseClient, never()).sendPayment(any(Author.class),\n                                       any(BigDecimal.class),\n                                       anyString());\n  }\n\n  @Test\n  public void testValidCommit() throws Exception, TransferFailedException {\n    String payloadValue = payload(\"/payloads/valid_commit.json\");\n    MultivaluedMapImpl post = new MultivaluedMapImpl();\n    post.add(\"payload\", payloadValue);\n    ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n        .header(\"X-Forwarded-For\", \"192.30.252.1\")\n        .header(\"Authorization\", authString)\n        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n        .post(ClientResponse.class, post);\n\n    verify(coinbaseClient).sendPayment(any(Author.class),\n                                       eq(BALANCE.multiply(new BigDecimal(0.02))),\n                                       anyString());\n  }\n\n  @Test\n  public void testNonMaster() throws Exception, TransferFailedException {\n    String payloadValue = payload(\"/payloads/non_master_push.json\");\n    MultivaluedMapImpl post = new MultivaluedMapImpl();\n    post.add(\"payload\", payloadValue);\n    ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n        .header(\"X-Forwarded-For\", \"192.30.252.1\")\n        .header(\"Authorization\", authString)\n        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n        .post(ClientResponse.class, post);\n\n    verify(coinbaseClient, never()).sendPayment(any(Author.class),\n                                       eq(BALANCE.multiply(new BigDecimal(0.02))),\n                                       anyString());\n  }\n\n  @Test\n  public void testValidMultipleCommitsMultipleAuthors() throws Exception, TransferFailedException {\n    String payloadValue = payload(\"/payloads/multiple_commits_authors.json\");\n    MultivaluedMapImpl post = new MultivaluedMapImpl();\n    post.add(\"payload\", payloadValue);\n    ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n        .header(\"X-Forwarded-For\", \"192.30.252.1\")\n        .header(\"Authorization\", authString)\n        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n        .post(ClientResponse.class, post);\n\n    verify(coinbaseClient, times(1)).sendPayment(any(Author.class), eq(BALANCE.multiply(new BigDecimal(0.02))),\n        anyString());\n    verify(coinbaseClient, times(1)).sendPayment(any(Author.class), eq(BALANCE.subtract(BALANCE.multiply(new BigDecimal(0.02)))\n        .multiply(new BigDecimal(0.02))), anyString());\n  }\n\n  @Test\n  public void testOptInCommit() throws Exception, TransferFailedException {\n    String payloadValue = payload(\"/payloads/opt_in_commit.json\");\n    MultivaluedMapImpl post = new MultivaluedMapImpl();\n    post.add(\"payload\", payloadValue);\n    ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n        .header(\"X-Forwarded-For\", \"192.30.252.1\")\n        .header(\"Authorization\", authString)\n        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n        .post(ClientResponse.class, post);\n\n    verify(coinbaseClient).sendPayment(any(Author.class),\n                                       eq(BALANCE.multiply(new BigDecimal(0.02))),\n                                       anyString());\n  }\n\n  @Test\n  public void testNoOptInCommit() throws Exception, TransferFailedException {\n    String payloadValue = payload(\"/payloads/no_opt_in_commit.json\");\n    MultivaluedMapImpl post = new MultivaluedMapImpl();\n    post.add(\"payload\", payloadValue);\n    ClientResponse response = resources.client().resource(\"/v1/github/commits/\")\n        .header(\"X-Forwarded-For\", \"192.30.252.1\")\n        .header(\"Authorization\", authString)\n        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)\n        .post(ClientResponse.class, post);\n\n    verify(coinbaseClient, never()).sendPayment(any(Author.class),\n                                                any(BigDecimal.class),\n                                                anyString());\n  }\n\n\n}\n"
  },
  {
    "path": "src/test/java/org/whispersystems/bithub/tests/controllers/StatusControllerTest.java",
    "content": "package org.whispersystems.bithub.tests.controllers;\n\nimport com.coinbase.api.ObjectMapperProvider;\nimport com.coinbase.api.entity.TransactionsResponse;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.sun.jersey.api.client.ClientResponse;\nimport org.junit.ClassRule;\nimport org.junit.Test;\nimport org.whispersystems.bithub.client.CoinbaseClient;\nimport org.whispersystems.bithub.client.GithubClient;\nimport org.whispersystems.bithub.config.RepositoryConfiguration;\nimport org.whispersystems.bithub.controllers.StatusController;\nimport org.whispersystems.bithub.storage.CacheManager;\n\nimport javax.ws.rs.core.MediaType;\nimport java.math.BigDecimal;\nimport java.util.LinkedList;\n\nimport io.dropwizard.testing.junit.ResourceTestRule;\nimport static org.fest.assertions.api.Assertions.assertThat;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\npublic class StatusControllerTest {\n\n  private static final BigDecimal PAYOUT_RATE   = new BigDecimal(0.02 );\n  private static final BigDecimal BALANCE       = new BigDecimal(10.01);\n  private static final BigDecimal EXCHANGE_RATE = new BigDecimal(1.0  );\n\n  private static final CoinbaseClient coinbaseClient = mock(CoinbaseClient.class);\n  private static final GithubClient   githubClient   = mock(GithubClient.class  );\n\n  @ClassRule\n  public static ResourceTestRule resources;\n\n  static {\n    try {\n      ObjectMapper objectMapper = ObjectMapperProvider.createDefaultMapper();\n      TransactionsResponse transactionsResponse = objectMapper.readValue(StatusControllerTest.class.getResourceAsStream(\"/payloads/transactions.json\"), TransactionsResponse.class);\n      when(coinbaseClient.getRecentTransactions()).thenReturn(transactionsResponse.getTransactions());\n      when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE);\n      when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE);\n\n      CacheManager coinbaseManager = new CacheManager(coinbaseClient, githubClient,\n                                                      new LinkedList<RepositoryConfiguration>(),\n                                                      PAYOUT_RATE);\n      coinbaseManager.start();\n\n      resources = ResourceTestRule.builder()\n                                  .addResource(new StatusController(coinbaseManager, null))\n                                  .build();\n    } catch (Exception e) {\n      throw new AssertionError(e);\n    }\n  }\n\n//  @Before\n//  public void setup() throws Exception {\n//    when(coinbaseClient.getRecentTransactions()).thenReturn(fromJson(jsonFixture(\"payloads/transactions.json\"), RecentTransactionsResponse.class).getTransactions());\n//    when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE);\n//    when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE);\n//\n//  }\n\n//  @Test\n//  public void testTransactionsHtml() throws Exception {\n//    ClientResponse response = resources.client().resource(\"/v1/status/transactions/\")\n//        .get(ClientResponse.class);\n//\n//    assertThat(response.getStatus()).isEqualTo(200);\n//    assertThat(response.getType()).isEqualTo(MediaType.TEXT_HTML_TYPE);\n//  }\n\n  @Test\n  public void testTransactionsJson() throws Exception {\n    ClientResponse response = resources.client().resource(\"/v1/status/transactions/?format=json\").accept(MediaType.APPLICATION_JSON_TYPE)\n        .get(ClientResponse.class);\n\n    assertThat(response.getStatus()).isEqualTo(200);\n    assertThat(response.getType()).isEqualTo(MediaType.APPLICATION_JSON_TYPE);\n  }\n\n}\n"
  },
  {
    "path": "src/test/java/org/whispersystems/bithub/tests/util/JsonHelper.java",
    "content": "package org.whispersystems.bithub.tests.util;\n\n\nimport com.fasterxml.jackson.core.JsonParseException;\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\n\nimport java.io.IOException;\n\nimport static io.dropwizard.testing.FixtureHelpers.fixture;\n\npublic class JsonHelper {\n\n  private static final ObjectMapper objectMapper = new ObjectMapper();\n\n  public static String asJson(Object object) throws JsonProcessingException {\n    return objectMapper.writeValueAsString(object);\n  }\n\n  public static <T> T fromJson(String value, Class<T> clazz) throws IOException {\n    return objectMapper.readValue(value, clazz);\n  }\n\n  public static String jsonFixture(String filename) throws IOException {\n    return objectMapper.writeValueAsString(objectMapper.readValue(fixture(filename), JsonNode.class));\n  }\n}\n"
  },
  {
    "path": "src/test/resources/payloads/invalid_origin.json",
    "content": "{\n    \"after\": \"100e9859651b35a3505cc278e9a98a076f79940b\",\n    \"before\": \"6626766348ab245bdb3351989f753bd6e792524a\",\n    \"commits\": [\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"info@whispersystems.org\",\n                \"name\": \"WhisperBTC\",\n                \"username\": \"WhisperBTC\"\n            },\n            \"committer\": {\n                \"email\": \"info@whispersystems.org\",\n                \"name\": \"WhisperBTC\",\n                \"username\": \"WhisperBTC\"\n            },\n            \"distinct\": true,\n            \"id\": \"fd7daeb1de6d72220b1313a7f1112d43885013aa\",\n            \"message\": \"Update foo\",\n            \"modified\": [\n                \"foo\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:27:00-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/fd7daeb1de6d72220b1313a7f1112d43885013aa\"\n        },\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"100e9859651b35a3505cc278e9a98a076f79940b\",\n            \"message\": \"Merge pull request #2 from WhisperBTC/patch-2\\n\\nUpdate foo\",\n            \"modified\": [\n                \"foo\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:27:28-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/100e9859651b35a3505cc278e9a98a076f79940b\"\n        }\n    ],\n    \"compare\": \"https://github.com/moxie0/tempt/compare/6626766348ab...100e9859651b\",\n    \"created\": false,\n    \"deleted\": false,\n    \"forced\": false,\n    \"head_commit\": {\n        \"added\": [],\n        \"author\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"committer\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"distinct\": true,\n        \"id\": \"100e9859651b35a3505cc278e9a98a076f79940b\",\n        \"message\": \"Merge pull request #2 from WhisperBTC/patch-2\\n\\nUpdate foo\",\n        \"modified\": [\n            \"foo\"\n        ],\n        \"removed\": [],\n        \"timestamp\": \"2013-12-14T11:27:28-08:00\",\n        \"url\": \"https://github.com/moxie0/tempt/commit/100e9859651b35a3505cc278e9a98a076f79940b\"\n    },\n    \"pusher\": {\n        \"email\": \"moxie@thoughtcrime.org\",\n        \"name\": \"moxie0\"\n    },\n    \"ref\": \"refs/heads/master\",\n    \"repository\": {\n        \"created_at\": 1386866024,\n        \"description\": \"test\",\n        \"fork\": false,\n        \"forks\": 1,\n        \"has_downloads\": true,\n        \"has_issues\": true,\n        \"has_wiki\": true,\n        \"id\": 15141344,\n        \"master_branch\": \"master\",\n        \"name\": \"tempt\",\n        \"open_issues\": 0,\n        \"owner\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"moxie0\"\n        },\n        \"private\": false,\n        \"pushed_at\": 1387049248,\n        \"size\": 216,\n        \"stargazers\": 1,\n        \"url\": \"https://github.com/moxie0/test\",\n        \"watchers\": 1\n    }\n}\n"
  },
  {
    "path": "src/test/resources/payloads/invalid_repo.json",
    "content": "{\n    \"after\": \"100e9859651b35a3505cc278e9a98a076f79940b\",\n    \"before\": \"6626766348ab245bdb3351989f753bd6e792524a\",\n    \"commits\": [\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"info@whispersystems.org\",\n                \"name\": \"WhisperBTC\",\n                \"username\": \"WhisperBTC\"\n            },\n            \"committer\": {\n                \"email\": \"info@whispersystems.org\",\n                \"name\": \"WhisperBTC\",\n                \"username\": \"WhisperBTC\"\n            },\n            \"distinct\": true,\n            \"id\": \"fd7daeb1de6d72220b1313a7f1112d43885013aa\",\n            \"message\": \"Update foo\",\n            \"modified\": [\n                \"foo\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:27:00-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/fd7daeb1de6d72220b1313a7f1112d43885013aa\"\n        },\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"100e9859651b35a3505cc278e9a98a076f79940b\",\n            \"message\": \"Merge pull request #2 from WhisperBTC/patch-2\\n\\nUpdate foo\",\n            \"modified\": [\n                \"foo\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:27:28-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/100e9859651b35a3505cc278e9a98a076f79940b\"\n        }\n    ],\n    \"compare\": \"https://github.com/moxie0/tempt/compare/6626766348ab...100e9859651b\",\n    \"created\": false,\n    \"deleted\": false,\n    \"forced\": false,\n    \"head_commit\": {\n        \"added\": [],\n        \"author\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"committer\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"distinct\": true,\n        \"id\": \"100e9859651b35a3505cc278e9a98a076f79940b\",\n        \"message\": \"Merge pull request #2 from WhisperBTC/patch-2\\n\\nUpdate foo\",\n        \"modified\": [\n            \"foo\"\n        ],\n        \"removed\": [],\n        \"timestamp\": \"2013-12-14T11:27:28-08:00\",\n        \"url\": \"https://github.com/moxie0/tempt/commit/100e9859651b35a3505cc278e9a98a076f79940b\"\n    },\n    \"pusher\": {\n        \"email\": \"moxie@thoughtcrime.org\",\n        \"name\": \"moxie0\"\n    },\n    \"ref\": \"refs/heads/master\",\n    \"repository\": {\n        \"created_at\": 1386866024,\n        \"description\": \"test\",\n        \"fork\": false,\n        \"forks\": 1,\n        \"has_downloads\": true,\n        \"has_issues\": true,\n        \"has_wiki\": true,\n        \"id\": 15141344,\n        \"master_branch\": \"master\",\n        \"name\": \"tempt\",\n        \"open_issues\": 0,\n        \"owner\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"moxie0\"\n        },\n        \"private\": false,\n        \"pushed_at\": 1387049248,\n        \"size\": 216,\n        \"stargazers\": 1,\n        \"url\": \"https://github.com/moxie0/tempt\",\n        \"watchers\": 1\n    }\n}\n"
  },
  {
    "path": "src/test/resources/payloads/multiple_commits_authors.json",
    "content": "{\n    \"after\": \"1481a2de7b2a7d02428ad93446ab166be7793fbb\",\n    \"before\": \"17c497ccc7cca9c2f735aa07e9e3813060ce9a6a\",\n    \"commits\": [\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"otherauthor@noway.biz\",\n                \"name\": \"Garen Torikian\",\n                \"username\": \"octokitty\"\n            },\n            \"committer\": {\n                \"email\": \"lolwut@noway.biz\",\n                \"name\": \"Garen Torikian\",\n                \"username\": \"octokitty\"\n            },\n            \"distinct\": true,\n            \"id\": \"c441029cf673f84c8b7db52d0a5944ee5c52ff89\",\n            \"message\": \"Test\",\n            \"modified\": [\n                \"README.md\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-02-22T13:50:07-08:00\",\n            \"url\": \"https://github.com/octokitty/testing/commit/c441029cf673f84c8b7db52d0a5944ee5c52ff89\"\n        },\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"lolwut@noway.biz\",\n                \"name\": \"Garen Torikian\",\n                \"username\": \"octokitty\"\n            },\n            \"committer\": {\n                \"email\": \"lolwut@noway.biz\",\n                \"name\": \"Garen Torikian\",\n                \"username\": \"octokitty\"\n            },\n            \"distinct\": true,\n            \"id\": \"36c5f2243ed24de58284a96f2a643bed8c028658\",\n            \"message\": \"This is me testing the windows client.\",\n            \"modified\": [\n                \"README.md\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-02-22T14:07:13-08:00\",\n            \"url\": \"https://github.com/octokitty/testing/commit/36c5f2243ed24de58284a96f2a643bed8c028658\"\n        },\n        {\n            \"added\": [\n                \"words/madame-bovary.txt\"\n            ],\n            \"author\": {\n                \"email\": \"lolwut@noway.biz\",\n                \"name\": \"Garen Torikian\",\n                \"username\": \"octokitty\"\n            },\n            \"committer\": {\n                \"email\": \"lolwut@noway.biz\",\n                \"name\": \"Garen Torikian\",\n                \"username\": \"octokitty\"\n            },\n            \"distinct\": true,\n            \"id\": \"1481a2de7b2a7d02428ad93446ab166be7793fbb\",\n            \"message\": \"Rename madame-bovary.txt to words/madame-bovary.txt\",\n            \"modified\": [],\n            \"removed\": [\n                \"madame-bovary.txt\"\n            ],\n            \"timestamp\": \"2013-03-12T08:14:29-07:00\",\n            \"url\": \"https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb\"\n        }\n    ],\n    \"compare\": \"https://github.com/octokitty/testing/compare/17c497ccc7cc...1481a2de7b2a\",\n    \"created\": false,\n    \"deleted\": false,\n    \"forced\": false,\n    \"head_commit\": {\n        \"added\": [\n            \"words/madame-bovary.txt\"\n        ],\n        \"author\": {\n            \"email\": \"lolwut@noway.biz\",\n            \"name\": \"Garen Torikian\",\n            \"username\": \"octokitty\"\n        },\n        \"committer\": {\n            \"email\": \"lolwut@noway.biz\",\n            \"name\": \"Garen Torikian\",\n            \"username\": \"octokitty\"\n        },\n        \"distinct\": true,\n        \"id\": \"1481a2de7b2a7d02428ad93446ab166be7793fbb\",\n        \"message\": \"Rename madame-bovary.txt to words/madame-bovary.txt\",\n        \"modified\": [],\n        \"removed\": [\n            \"madame-bovary.txt\"\n        ],\n        \"timestamp\": \"2013-03-12T08:14:29-07:00\",\n        \"url\": \"https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb\"\n    },\n    \"pusher\": {\n        \"email\": \"lolwut@noway.biz\",\n        \"name\": \"Garen Torikian\"\n    },\n    \"ref\": \"refs/heads/master\",\n    \"repository\": {\n        \"created_at\": 1332977768,\n        \"description\": \"\",\n        \"fork\": false,\n        \"forks\": 0,\n        \"has_downloads\": true,\n        \"has_issues\": true,\n        \"has_wiki\": true,\n        \"homepage\": \"\",\n        \"id\": 3860742,\n        \"language\": \"Ruby\",\n        \"master_branch\": \"master\",\n        \"name\": \"testing\",\n        \"open_issues\": 2,\n        \"owner\": {\n            \"email\": \"lolwut@noway.biz\",\n            \"name\": \"octokitty\"\n        },\n        \"private\": false,\n        \"pushed_at\": 1363295520,\n        \"size\": 2156,\n        \"stargazers\": 1,\n        \"url\": \"https://github.com/moxie0/test\",\n        \"watchers\": 1\n    }\n}\n"
  },
  {
    "path": "src/test/resources/payloads/no_opt_in_commit.json",
    "content": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n    \"commits\": [\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"ba1b681c71db4fcd461954b1bf344bc6e29411e5\",\n            \"message\": \"Update path\",\n            \"modified\": [\n                \"README.md\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:28-08:00\",\n            \"url\": \"https://github.com/moxie0/optin/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5\"\n        },\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n            \"message\": \"Merge branch 'master' of github.com:moxie0/tempt\",\n            \"modified\": [],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n            \"url\": \"https://github.com/moxie0/optin/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n        }\n    ],\n    \"compare\": \"https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32\",\n    \"created\": false,\n    \"deleted\": false,\n    \"forced\": false,\n    \"head_commit\": {\n        \"added\": [],\n        \"author\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"committer\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"distinct\": true,\n        \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n        \"message\": \"Merge branch 'master' of github.com:moxie0/optin\",\n        \"modified\": [],\n        \"removed\": [],\n        \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n        \"url\": \"https://github.com/moxie0/optin/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n    },\n    \"pusher\": {\n        \"email\": \"moxie@thoughtcrime.org\",\n        \"name\": \"moxie0\"\n    },\n    \"ref\": \"refs/heads/master\",\n    \"repository\": {\n        \"created_at\": 1386866024,\n        \"description\": \"test\",\n        \"fork\": false,\n        \"forks\": 1,\n        \"has_downloads\": true,\n        \"has_issues\": true,\n        \"has_wiki\": true,\n        \"id\": 15141344,\n        \"master_branch\": \"master\",\n        \"name\": \"tempt\",\n        \"open_issues\": 0,\n        \"owner\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"moxie0\"\n        },\n        \"private\": false,\n        \"pushed_at\": 1387050173,\n        \"size\": 216,\n        \"stargazers\": 1,\n        \"url\": \"https://github.com/moxie0/optin\",\n        \"watchers\": 1\n    }\n}\n"
  },
  {
    "path": "src/test/resources/payloads/non_master_push.json",
    "content": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n    \"commits\": [\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"ba1b681c71db4fcd461954b1bf344bc6e29411e5\",\n            \"message\": \"Update path\",\n            \"modified\": [\n                \"README.md\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:28-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5\"\n        },\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n            \"message\": \"Merge branch 'master' of github.com:moxie0/tempt\",\n            \"modified\": [],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n        }\n    ],\n    \"compare\": \"https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32\",\n    \"created\": false,\n    \"deleted\": false,\n    \"forced\": false,\n    \"head_commit\": {\n        \"added\": [],\n        \"author\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"committer\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"distinct\": true,\n        \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n        \"message\": \"Merge branch 'master' of github.com:moxie0/tempt\",\n        \"modified\": [],\n        \"removed\": [],\n        \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n        \"url\": \"https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n    },\n    \"pusher\": {\n        \"email\": \"moxie@thoughtcrime.org\",\n        \"name\": \"moxie0\"\n    },\n    \"ref\": \"refs/heads/lilia_doing_something\",\n    \"repository\": {\n        \"created_at\": 1386866024,\n        \"description\": \"test\",\n        \"fork\": false,\n        \"forks\": 1,\n        \"has_downloads\": true,\n        \"has_issues\": true,\n        \"has_wiki\": true,\n        \"id\": 15141344,\n        \"master_branch\": \"master\",\n        \"name\": \"tempt\",\n        \"open_issues\": 0,\n        \"owner\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"moxie0\"\n        },\n        \"private\": false,\n        \"pushed_at\": 1387050173,\n        \"size\": 216,\n        \"stargazers\": 1,\n        \"url\": \"https://github.com/moxie0/test\",\n        \"watchers\": 1\n    }\n}\n"
  },
  {
    "path": "src/test/resources/payloads/opt_in_commit.json",
    "content": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n    \"commits\": [\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"ba1b681c71db4fcd461954b1bf344bc6e29411e5\",\n            \"message\": \"Update path MONEYMONEY\",\n            \"modified\": [\n                \"README.md\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:28-08:00\",\n            \"url\": \"https://github.com/moxie0/optin/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5\"\n        },\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n            \"message\": \"Merge branch 'master' of github.com:moxie0/tempt\",\n            \"modified\": [],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n            \"url\": \"https://github.com/moxie0/optin/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n        }\n    ],\n    \"compare\": \"https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32\",\n    \"created\": false,\n    \"deleted\": false,\n    \"forced\": false,\n    \"head_commit\": {\n        \"added\": [],\n        \"author\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"committer\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"distinct\": true,\n        \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n        \"message\": \"Merge branch 'master' of github.com:moxie0/optin\",\n        \"modified\": [],\n        \"removed\": [],\n        \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n        \"url\": \"https://github.com/moxie0/optin/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n    },\n    \"pusher\": {\n        \"email\": \"moxie@thoughtcrime.org\",\n        \"name\": \"moxie0\"\n    },\n    \"ref\": \"refs/heads/master\",\n    \"repository\": {\n        \"created_at\": 1386866024,\n        \"description\": \"test\",\n        \"fork\": false,\n        \"forks\": 1,\n        \"has_downloads\": true,\n        \"has_issues\": true,\n        \"has_wiki\": true,\n        \"id\": 15141344,\n        \"master_branch\": \"master\",\n        \"name\": \"tempt\",\n        \"open_issues\": 0,\n        \"owner\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"moxie0\"\n        },\n        \"private\": false,\n        \"pushed_at\": 1387050173,\n        \"size\": 216,\n        \"stargazers\": 1,\n        \"url\": \"https://github.com/moxie0/optin\",\n        \"watchers\": 1\n    }\n}\n"
  },
  {
    "path": "src/test/resources/payloads/opt_out_commit.json",
    "content": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n    \"commits\": [\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"ba1b681c71db4fcd461954b1bf344bc6e29411e5\",\n            \"message\": \"Update path FREEBIE\",\n            \"modified\": [\n                \"README.md\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:28-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5\"\n        },\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n            \"message\": \"Merge branch 'master' of github.com:moxie0/tempt FREEBIE\",\n            \"modified\": [],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n        }\n    ],\n    \"compare\": \"https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32\",\n    \"created\": false,\n    \"deleted\": false,\n    \"forced\": false,\n    \"head_commit\": {\n        \"added\": [],\n        \"author\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"committer\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"distinct\": true,\n        \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n        \"message\": \"Merge branch 'master' of github.com:moxie0/tempt\",\n        \"modified\": [],\n        \"removed\": [],\n        \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n        \"url\": \"https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n    },\n    \"pusher\": {\n        \"email\": \"moxie@thoughtcrime.org\",\n        \"name\": \"moxie0\"\n    },\n    \"ref\": \"refs/heads/master\",\n    \"repository\": {\n        \"created_at\": 1386866024,\n        \"description\": \"test\",\n        \"fork\": false,\n        \"forks\": 1,\n        \"has_downloads\": true,\n        \"has_issues\": true,\n        \"has_wiki\": true,\n        \"id\": 15141344,\n        \"master_branch\": \"master\",\n        \"name\": \"tempt\",\n        \"open_issues\": 0,\n        \"owner\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"moxie0\"\n        },\n        \"private\": false,\n        \"pushed_at\": 1387050173,\n        \"size\": 216,\n        \"stargazers\": 1,\n        \"url\": \"https://github.com/moxie0/test\",\n        \"watchers\": 1\n    }\n}\n"
  },
  {
    "path": "src/test/resources/payloads/transactions.json",
    "content": "{\n    \"current_user\": {\n        \"id\": \"5011f33df8182b142400000e\",\n        \"email\": \"user2@example.com\",\n        \"name\": \"User Two\"\n    },\n    \"balance\": {\n        \"amount\": \"50.00000000\",\n        \"currency\": \"BTC\"\n    },\n    \"total_count\": 2,\n    \"num_pages\": 1,\n    \"current_page\": 1,\n    \"transactions\": [\n        {\n            \"transaction\": {\n                \"id\": \"5018f833f8182b129c00002f\",\n                \"created_at\": \"2012-08-01T02:34:43-07:00\",\n                \"amount\": {\n                    \"amount\": \"-1.10000000\",\n                    \"currency\": \"BTC\"\n                },\n                \"request\": true,\n                \"status\": \"pending\",\n                \"sender\": {\n                    \"id\": \"5011f33df8182b142400000e\",\n                    \"name\": \"User Two\",\n                    \"email\": \"user2@example.com\"\n                },\n                \"recipient\": {\n                    \"id\": \"5011f33df8182b142400000a\",\n                    \"name\": \"User One\",\n                    \"email\": \"user1@example.com\"\n                },\n                \"notes\": \"Commit payment:__moxie0__ https://github.com/WhisperSystems/BitHub/commit/88edf54e5b57c80ac05093a9be90965fd41291c2\"\n            }\n        },\n        {\n            \"transaction\": {\n                \"id\": \"5018f833f8182b129c00002e\",\n                \"created_at\": \"2012-08-01T02:36:43-07:00\",\n                \"hsh\": \"9d6a7d1112c3db9de5315b421a5153d71413f5f752aff75bf504b77df4e646a3\",\n                \"amount\": {\n                    \"amount\": \"-1.00000000\",\n                    \"currency\": \"BTC\"\n                },\n                \"request\": false,\n                \"status\": \"complete\",\n                \"sender\": {\n                    \"id\": \"5011f33df8182b142400000e\",\n                    \"name\": \"User Two\",\n                    \"email\": \"user2@example.com\"\n                },\n                \"recipient_address\": \"37muSN5ZrukVTvyVh3mT5Zc5ew9L9CBare\",\n                \"notes\": \"Commit payment:__moxie0__ https://github.com/WhisperSystems/BitHub/commit/88edf54e5b57c80ac05093a9be90965fd41291c2\"\n            }\n        }\n    ]\n}"
  },
  {
    "path": "src/test/resources/payloads/valid_commit.json",
    "content": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n    \"commits\": [\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"ba1b681c71db4fcd461954b1bf344bc6e29411e5\",\n            \"message\": \"Update path\",\n            \"modified\": [\n                \"README.md\"\n            ],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:28-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5\"\n        },\n        {\n            \"added\": [],\n            \"author\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"committer\": {\n                \"email\": \"moxie@thoughtcrime.org\",\n                \"name\": \"Moxie Marlinspike\",\n                \"username\": \"moxie0\"\n            },\n            \"distinct\": true,\n            \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n            \"message\": \"Merge branch 'master' of github.com:moxie0/tempt\",\n            \"modified\": [],\n            \"removed\": [],\n            \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n            \"url\": \"https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n        }\n    ],\n    \"compare\": \"https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32\",\n    \"created\": false,\n    \"deleted\": false,\n    \"forced\": false,\n    \"head_commit\": {\n        \"added\": [],\n        \"author\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"committer\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"Moxie Marlinspike\",\n            \"username\": \"moxie0\"\n        },\n        \"distinct\": true,\n        \"id\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n        \"message\": \"Merge branch 'master' of github.com:moxie0/tempt\",\n        \"modified\": [],\n        \"removed\": [],\n        \"timestamp\": \"2013-12-14T11:42:44-08:00\",\n        \"url\": \"https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f\"\n    },\n    \"pusher\": {\n        \"email\": \"moxie@thoughtcrime.org\",\n        \"name\": \"moxie0\"\n    },\n    \"ref\": \"refs/heads/master\",\n    \"repository\": {\n        \"created_at\": 1386866024,\n        \"description\": \"test\",\n        \"fork\": false,\n        \"forks\": 1,\n        \"has_downloads\": true,\n        \"has_issues\": true,\n        \"has_wiki\": true,\n        \"id\": 15141344,\n        \"master_branch\": \"master\",\n        \"name\": \"tempt\",\n        \"open_issues\": 0,\n        \"owner\": {\n            \"email\": \"moxie@thoughtcrime.org\",\n            \"name\": \"moxie0\"\n        },\n        \"private\": false,\n        \"pushed_at\": 1387050173,\n        \"size\": 216,\n        \"stargazers\": 1,\n        \"url\": \"https://github.com/moxie0/test\",\n        \"watchers\": 1\n    }\n}\n"
  },
  {
    "path": "system.properties",
    "content": "java.runtime.version=1.7\n"
  }
]