Full Code of WhisperSystems/BitHub for AI

master 82c9d21d2927 cached
59 files
125.9 KB
32.4k tokens
179 symbols
1 requests
Download .txt
Repository: WhisperSystems/BitHub
Branch: master
Commit: 82c9d21d2927
Files: 59
Total size: 125.9 KB

Directory structure:
gitextract_2c75r1ji/

├── .gitignore
├── .travis.yml
├── Procfile
├── README.md
├── assembly.xml
├── config/
│   └── sample.yml
├── design/
│   ├── badge-small.xcf
│   └── badge.xcf
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── org/
│   │   │       └── whispersystems/
│   │   │           └── bithub/
│   │   │               ├── BithubServerConfiguration.java
│   │   │               ├── BithubService.java
│   │   │               ├── auth/
│   │   │               │   └── GithubWebhookAuthenticator.java
│   │   │               ├── client/
│   │   │               │   ├── CoinbaseClient.java
│   │   │               │   ├── GithubClient.java
│   │   │               │   └── TransferFailedException.java
│   │   │               ├── config/
│   │   │               │   ├── BithubConfiguration.java
│   │   │               │   ├── CoinbaseConfiguration.java
│   │   │               │   ├── GithubConfiguration.java
│   │   │               │   ├── OrganizationConfiguration.java
│   │   │               │   ├── RepositoryConfiguration.java
│   │   │               │   └── WebhookConfiguration.java
│   │   │               ├── controllers/
│   │   │               │   ├── DashboardController.java
│   │   │               │   ├── GithubController.java
│   │   │               │   ├── StatusController.java
│   │   │               │   └── UnauthorizedHookException.java
│   │   │               ├── entities/
│   │   │               │   ├── Author.java
│   │   │               │   ├── Commit.java
│   │   │               │   ├── CommitComment.java
│   │   │               │   ├── Payment.java
│   │   │               │   ├── PushEvent.java
│   │   │               │   ├── Repositories.java
│   │   │               │   ├── Repository.java
│   │   │               │   ├── Transaction.java
│   │   │               │   └── Transactions.java
│   │   │               ├── mappers/
│   │   │               │   ├── IOExceptionMapper.java
│   │   │               │   └── UnauthorizedHookExceptionMapper.java
│   │   │               ├── storage/
│   │   │               │   ├── CacheManager.java
│   │   │               │   ├── CoinbaseTransactionParser.java
│   │   │               │   └── CurrentPayment.java
│   │   │               ├── util/
│   │   │               │   ├── AdvancedAtomicLong.java
│   │   │               │   └── Badge.java
│   │   │               └── views/
│   │   │                   ├── DashboardView.java
│   │   │                   └── TransactionsView.java
│   │   └── resources/
│   │       ├── banner.txt
│   │       └── org/
│   │           └── whispersystems/
│   │               └── bithub/
│   │                   └── views/
│   │                       ├── dashboard.mustache
│   │                       └── recent_transactions.mustache
│   └── test/
│       ├── java/
│       │   └── org/
│       │       └── whispersystems/
│       │           └── bithub/
│       │               └── tests/
│       │                   ├── controllers/
│       │                   │   ├── GithubControllerTest.java
│       │                   │   └── StatusControllerTest.java
│       │                   └── util/
│       │                       └── JsonHelper.java
│       └── resources/
│           └── payloads/
│               ├── invalid_origin.json
│               ├── invalid_repo.json
│               ├── multiple_commits_authors.json
│               ├── no_opt_in_commit.json
│               ├── non_master_push.json
│               ├── opt_in_commit.json
│               ├── opt_out_commit.json
│               ├── transactions.json
│               └── valid_commit.json
└── system.properties

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea
*.iml
target
config/production.yml



================================================
FILE: .travis.yml
================================================
language: java


================================================
FILE: Procfile
================================================
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


================================================
FILE: README.md
================================================
BitHub
=================

[![Build Status](https://travis-ci.org/WhisperSystems/BitHub.png?branch=master)](https://travis-ci.org/WhisperSystems/BitHub)

BitHub is a service that will automatically pay a percentage of Bitcoin funds for every submission to a GitHub repository.

More information can be found in our [announcement blog post](https://whispersystems.org/blog/bithub).

Opting Out
----------

If 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.


Building
-------------

    $ git clone https://github.com/WhisperSystems/BitHub.git
    $ cd BitHub
    $ mvn3 package

Running
-----------

1. Create a GitHub account for your BitHub server.
1. Create a Coinbase account for your BitHub server.
1. Add the above credentials to `config/sample.yml`
1. Execute `$ java -jar target/BitHub-0.1.jar server config/yourconfig.yml`

Deploying To Heroku
------------

```
$ heroku create your_app_name
$ heroku config:set GITHUB_USER=your_bithub_username
$ heroku config:set GITHUB_TOKEN=your_bithub_authtoken
$ heroku config:set GITHUB_WEBHOOK_PASSWORD=your_webhook_password
$ heroku config:set GITHUB_REPOSITORIES="[{\"url\" : \"https://github.com/youraccount/yourrepo\"}, {\"url\" : \"https://github.com/youraccount/yourotherrepo\"}]"
$ heroku config:set COINBASE_API_KEY=your_api_key
$ heroku config:set ORGANIZATION_NAME=your_organization_name
$ heroku config:set DONATION_URL=your_donation_url
$ git remote add your_heroku_remote
$ git push heroku master
```

Mailing list
------------

Have a question? Ask on our mailing list!

whispersystems@lists.riseup.net

https://lists.riseup.net/www/info/whispersystems

Current BitHub Payment For Commit: 
=================
[![Current Price](https://bithub.herokuapp.com/v1/status/payment/commit)](https://whispersystems.org/blog/bithub/)



================================================
FILE: assembly.xml
================================================
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
    <id>bin</id>
    <includeBaseDirectory>false</includeBaseDirectory>
    <formats>
        <format>tar.gz</format>
    </formats>
    <fileSets>
        <fileSet>
            <directory>${project.basedir}/config</directory>
            <outputDirectory>/config</outputDirectory>
            <includes>
                <include>*</include>
            </includes>
        </fileSet>
        <fileSet>
            <directory>${project.build.directory}</directory>
            <outputDirectory>/</outputDirectory>
            <includes>
                <include>${project.name}-${project.version}.jar</include>
            </includes>
        </fileSet>
    </fileSets>
</assembly>

================================================
FILE: config/sample.yml
================================================
organization:
  name: # Your name (eg. Open Whisper Systems)
  donationUrl: # A Coinbase link where you can receive donations (eg. https://coinbase.com/checkouts/d29fd4c37ca442393e32fdcb95304701)

github:
  user: # Your BitHub instance's GitHub username.
  token: # Your BitHub instance's GitHub auth token.

  webhook:
    password: # HTTP basic auth. The username defaults to "bithub".

  repositories: # A list of repository URLs to support payouts for.
    - url: # A repository's URL
      mode: # Either MONEYMONEY (default) or FREEBIE.
            # The former will pay out on every commit, unless
            # FREEBIE is specified in the message.  The latter will
            # only pay out if MONEYMONEY is specified in the message.

coinbase:
  apiKey: # Your Coinbase API key.
  apiSecret: # Your Coinbase API secret.


================================================
FILE: pom.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <prerequisites>
        <maven>3.0.0</maven>
    </prerequisites>

    <groupId>org.whispersystems.bithub</groupId>
    <artifactId>BitHub</artifactId>
    <version>0.1</version>

    <properties>
        <dropwizard.version>0.7.0</dropwizard.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-core</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-auth</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-jdbi</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-client</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-migrations</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-testing</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-metrics-graphite</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-views</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-views-mustache</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>
        <dependency>
            <groupId>io.dropwizard</groupId>
            <artifactId>dropwizard-servlets</artifactId>
            <version>${dropwizard.version}</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-json</artifactId>
            <version>1.18.1</version>
        </dependency>
        <dependency>
            <groupId>com.codahale.metrics</groupId>
            <artifactId>metrics-graphite</artifactId>
            <version>3.0.2</version>
        </dependency>

        <dependency>
            <groupId>com.sun.jersey.contribs</groupId>
            <artifactId>jersey-multipart</artifactId>
            <version>1.18.1</version>
        </dependency>
        <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
            <version>3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.1</version>
        </dependency>

        <dependency>
            <groupId>com.coinbase.api</groupId>
            <artifactId>coinbase-java</artifactId>
            <version>1.9.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.4.3</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.2.1</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>1.6</version>
                <configuration>
                    <createDependencyReducedPom>true</createDependencyReducedPom>
                    <filters>
                        <filter>
                            <artifact>*:*</artifact>
                            <excludes>
                                <exclude>META-INF/*.SF</exclude>
                                <exclude>META-INF/*.DSA</exclude>
                                <exclude>META-INF/*.RSA</exclude>
                            </excludes>
                        </filter>
                    </filters>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>org.whispersystems.bithub.BithubService</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.4</version>
                <configuration>
                    <descriptors>
                        <descriptor>assembly.xml</descriptor>
                    </descriptors>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
                        <phase>package</phase> <!-- bind to the packaging phase -->
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

</project>



================================================
FILE: src/main/java/org/whispersystems/bithub/BithubServerConfiguration.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.whispersystems.bithub.config.BithubConfiguration;
import org.whispersystems.bithub.config.CoinbaseConfiguration;
import org.whispersystems.bithub.config.GithubConfiguration;
import org.whispersystems.bithub.config.OrganizationConfiguration;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import io.dropwizard.Configuration;

public class BithubServerConfiguration extends Configuration {

  @Valid
  @NotNull
  @JsonProperty
  private GithubConfiguration github;

  @Valid
  @NotNull
  @JsonProperty
  private CoinbaseConfiguration coinbase;

  @JsonProperty
  @Valid
  private BithubConfiguration bithub = new BithubConfiguration();

  @Valid
  @NotNull
  @JsonProperty
  private OrganizationConfiguration organization;


  public GithubConfiguration getGithubConfiguration() {
    return github;
  }

  public CoinbaseConfiguration getCoinbaseConfiguration() {
    return coinbase;
  }

  public BithubConfiguration getBithubConfiguration() {
    return bithub;
  }

  public OrganizationConfiguration getOrganizationConfiguration() {
    return organization;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/BithubService.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub;

import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.whispersystems.bithub.auth.GithubWebhookAuthenticator;
import org.whispersystems.bithub.client.CoinbaseClient;
import org.whispersystems.bithub.client.GithubClient;
import org.whispersystems.bithub.config.CoinbaseConfiguration;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.controllers.DashboardController;
import org.whispersystems.bithub.controllers.GithubController;
import org.whispersystems.bithub.controllers.StatusController;
import org.whispersystems.bithub.mappers.IOExceptionMapper;
import org.whispersystems.bithub.mappers.UnauthorizedHookExceptionMapper;
import org.whispersystems.bithub.storage.CacheManager;

import javax.servlet.DispatcherType;
import java.math.BigDecimal;
import java.util.EnumSet;
import java.util.List;

import io.dropwizard.Application;
import io.dropwizard.auth.basic.BasicAuthProvider;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.dropwizard.views.ViewBundle;

/**
 * The main entry point for the service.
 *
 * @author Moxie Marlinspike
 */
public class BithubService extends Application<BithubServerConfiguration> {

  @Override
  public void initialize(Bootstrap<BithubServerConfiguration> bootstrap) {
    bootstrap.addBundle(new ViewBundle());
  }

  @Override
  public void run(BithubServerConfiguration config, Environment environment)
      throws Exception
  {
    String                        githubUser         = config.getGithubConfiguration().getUser();
    String                        githubToken        = config.getGithubConfiguration().getToken();
    String                        githubWebhookUser  = config.getGithubConfiguration().getWebhookConfiguration().getUsername();
    String                        githubWebhookPwd   = config.getGithubConfiguration().getWebhookConfiguration().getPassword();
    List<RepositoryConfiguration> githubRepositories = config.getGithubConfiguration().getRepositories();
    BigDecimal                    payoutRate         = config.getBithubConfiguration().getPayoutRate();
    String                        organizationName   = config.getOrganizationConfiguration().getName();
    String                        donationUrl        = config.getOrganizationConfiguration().getDonationUrl().toExternalForm();
    String                        coinbaseApiKey     = config.getCoinbaseConfiguration().getApiKey();
    String                        coinbaseApiSecret  = config.getCoinbaseConfiguration().getApiSecret();

    GithubClient   githubClient   = new GithubClient(githubUser, githubToken);
    CoinbaseClient coinbaseClient = new CoinbaseClient(coinbaseApiKey, coinbaseApiSecret);
    CacheManager   cacheManager   = new CacheManager(coinbaseClient, githubClient, githubRepositories, payoutRate);

    environment.servlets().addFilter("CORS", CrossOriginFilter.class)
               .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

    environment.lifecycle().manage(cacheManager);

    environment.jersey().register(new GithubController(githubRepositories, githubClient, coinbaseClient, payoutRate));
    environment.jersey().register(new StatusController(cacheManager, githubRepositories));
    environment.jersey().register(new DashboardController(organizationName, donationUrl, cacheManager));

    environment.jersey().register(new IOExceptionMapper());
    environment.jersey().register(new UnauthorizedHookExceptionMapper());
    environment.jersey().register(new BasicAuthProvider<>(new GithubWebhookAuthenticator(githubWebhookUser, githubWebhookPwd),
                                                          GithubWebhookAuthenticator.REALM));
  }

  public static void main(String[] args) throws Exception {
    new BithubService().run(args);
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/auth/GithubWebhookAuthenticator.java
================================================
package org.whispersystems.bithub.auth;

import com.google.common.base.Optional;

import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;

/**
 * Accepts only one fixed username/password combination.
 */
public class GithubWebhookAuthenticator implements Authenticator<BasicCredentials, GithubWebhookAuthenticator.Authentication> {

  /**
   * Represents a successful basic HTTP authentication.
   */
  public static class Authentication {
  }

  public static final String REALM = "bithub";

  private final BasicCredentials correctCredentials;

  public GithubWebhookAuthenticator(String username, String password) {
    this.correctCredentials = new BasicCredentials(username, password);
  }

  @Override
  public Optional<Authentication> authenticate(BasicCredentials clientCredentials) {
    if (correctCredentials.equals(clientCredentials)) {
      return Optional.of(new Authentication());
    } else {
      return Optional.absent();
    }
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.client;

import com.coinbase.api.Coinbase;
import com.coinbase.api.CoinbaseBuilder;
import com.coinbase.api.entity.Account;
import com.coinbase.api.entity.Transaction;
import com.coinbase.api.exception.CoinbaseException;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.whispersystems.bithub.entities.Author;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

/**
 * Handles interaction with the Coinbase API.
 *
 * @author Moxie Marlinspike
 */
public class CoinbaseClient {

  private final Coinbase coinbase;

  public CoinbaseClient(String apiKey, String apiSecret) {
    this.coinbase = new CoinbaseBuilder().withApiKey(apiKey, apiSecret).build();
  }

  public List<Transaction> getRecentTransactions()
      throws CoinbaseException, IOException
  {
    return coinbase.getTransactions().getTransactions();
  }

  public BigDecimal getExchangeRate() throws IOException, CoinbaseException {
    return coinbase.getExchangeRates().get("btc_to_usd");
  }

  public void sendPayment(Author author, BigDecimal amount, String url)
      throws TransferFailedException
  {
    try {
      String note = "Commit payment:\n__" + author.getUsername() + "__ " + url;

      Transaction transaction = new Transaction();
      transaction.setTo(author.getEmail());
      transaction.setAmount(Money.of(CurrencyUnit.of("BTC"), amount, RoundingMode.DOWN));
      transaction.setNotes(note);

      Transaction response = coinbase.sendMoney(transaction);

      if (response.getStatus() != Transaction.Status.COMPLETE) {
        throw new TransferFailedException();
      }
    } catch (CoinbaseException | IOException e) {
      throw new TransferFailedException(e);
    }
  }

  public BigDecimal getAccountBalance() throws IOException, CoinbaseException {
    List<Account> accounts = coinbase.getAccounts().getAccounts();
    Account       primary  = null;

    for (Account account : accounts) {
      if (account.isPrimary()) {
        primary = account;
        break;
      }
    }

    if (primary != null) return coinbase.getBalance(primary.getId()).getAmount();
    else                 return new BigDecimal(0.0);
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/client/GithubClient.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.client;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.core.util.Base64;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.bithub.entities.Commit;
import org.whispersystems.bithub.entities.CommitComment;
import org.whispersystems.bithub.entities.Repository;

import javax.ws.rs.core.MediaType;

/**
 * Handles interaction with the GitHub API.
 *
 * @author Moxie Marlinspike
 */
public class GithubClient {

  private static final String GITHUB_URL      = "https://api.github.com/";
  private static final String COMMENT_PATH    = "/repos/%s/%s/commits/%s/comments";
  private static final String COMMIT_PATH     = "/repos/%s/%s/git/commits/%s";
  private static final String REPOSITORY_PATH = "/repos/%s/%s";

  private final Logger logger = LoggerFactory.getLogger(GithubClient.class);

  private final String authorizationHeader;
  private final Client client;

  public GithubClient(String user, String token) {
    this.authorizationHeader = getAuthorizationHeader(user, token);
    this.client              = Client.create(getClientConfig());
  }

  public String getCommitDescription(String commitUrl) {
    String[] commitUrlParts = commitUrl.split("/");
    String   owner          = commitUrlParts[commitUrlParts.length - 4];
    String   repository     = commitUrlParts[commitUrlParts.length - 3];
    String   commit         = commitUrlParts[commitUrlParts.length - 1];

    String      path     = String.format(COMMIT_PATH, owner, repository, commit);
    WebResource resource = client.resource(GITHUB_URL).path(path);
    Commit      response = resource.type(MediaType.APPLICATION_JSON_TYPE)
                                   .accept(MediaType.APPLICATION_JSON_TYPE)
                                   .header("Authorization", authorizationHeader)
                                   .get(Commit.class);

    return response.getMessage();
  }

  public Repository getRepository(String url) {
    String[] urlParts = url.split("/");
    String   owner    = urlParts[urlParts.length - 2];
    String   name     = urlParts[urlParts.length - 1];

    String      path     = String.format(REPOSITORY_PATH, owner, name);
    WebResource resource = client.resource(GITHUB_URL).path(path);

    return resource.type(MediaType.APPLICATION_JSON_TYPE)
                   .accept(MediaType.APPLICATION_JSON_TYPE)
                   .header("Authorization", authorizationHeader)
                   .get(Repository.class);

  }

  public void addCommitComment(Repository repository, Commit commit, String comment) {
    try {
      String path = String.format(COMMENT_PATH, repository.getOwner().getName(),
                                  repository.getName(), commit.getSha());

      WebResource    resource = client.resource(GITHUB_URL).path(path);
      ClientResponse response = resource.type(MediaType.APPLICATION_JSON_TYPE)
                                        .accept(MediaType.APPLICATION_JSON)
                                        .header("Authorization", authorizationHeader)
                                        .entity(new CommitComment(comment))
                                        .post(ClientResponse.class);

      if (response.getStatus() < 200 || response.getStatus() >=300) {
        logger.warn("Commit comment failed: " + response.getClientResponseStatus().getReasonPhrase());
      }

    } catch (UniformInterfaceException | ClientHandlerException e) {
      logger.warn("Comment failed", e);
    }
  }

  private ClientConfig getClientConfig() {
    ClientConfig config = new DefaultClientConfig();
    config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);

    return config;
  }

  private String getAuthorizationHeader(String user, String token) {
    return "Basic " + new String(Base64.encode(user + ":" + token));
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/client/TransferFailedException.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.client;

public class TransferFailedException extends Exception {

  public TransferFailedException() {
    super();
  }

  public TransferFailedException(Throwable e) {
    super(e);
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/config/BithubConfiguration.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.config;


import com.fasterxml.jackson.annotation.JsonProperty;

import org.hibernate.validator.constraints.NotEmpty;

import java.math.BigDecimal;

public class BithubConfiguration {

  @JsonProperty
  @NotEmpty
  private String payout = "0.02";

  public BigDecimal getPayoutRate() {
    return new BigDecimal(payout);
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/config/CoinbaseConfiguration.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.config;


import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

public class CoinbaseConfiguration {

  @JsonProperty
  @NotEmpty
  private String apiKey;

  @JsonProperty
  @NotEmpty
  private String apiSecret;

  public String getApiKey() {
    return apiKey;
  }

  public String getApiSecret() {
    return apiSecret;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/config/GithubConfiguration.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hibernate.validator.constraints.NotEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

public class GithubConfiguration {

  private final Logger logger = LoggerFactory.getLogger(GithubConfiguration.class);

  @JsonProperty
  @NotEmpty
  private String user;

  @JsonProperty
  @NotEmpty
  private String token;

  @JsonProperty
  private List<RepositoryConfiguration> repositories;

  @JsonProperty
  private String repositories_heroku;

  @Valid
  @NotNull
  @JsonProperty
  private WebhookConfiguration webhook;

  public String getUser() {
    return user;
  }

  public String getToken() {
    return token;
  }

  public List<RepositoryConfiguration> getRepositories() {
    if (repositories != null) {
      return repositories;
    }

    if (repositories_heroku != null) {
      try {
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(repositories_heroku, new TypeReference<List<RepositoryConfiguration>>() {});
      } catch (IOException e) {
        logger.warn("Error deserializing", e);
      }
    }

    return new LinkedList<>();
  }

  public WebhookConfiguration getWebhookConfiguration() {
    return webhook;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/config/OrganizationConfiguration.java
================================================
package org.whispersystems.bithub.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.Valid;
import java.net.URL;

public class OrganizationConfiguration {

  @JsonProperty
  @NotEmpty
  private String name;

  @JsonProperty
  @Valid
  private URL donationUrl;

  public String getName() {
    return name;
  }

  public URL getDonationUrl() {
    return donationUrl;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/config/RepositoryConfiguration.java
================================================
package org.whispersystems.bithub.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

public class RepositoryConfiguration {

  @JsonProperty
  @NotEmpty
  private String url;

  @JsonProperty
  @NotEmpty
  private String mode = "MONEYMONEY";

  public RepositoryConfiguration(String url, String mode) {
    this.url  = url;
    this.mode = mode;
  }

  public RepositoryConfiguration(String url) {
    this.url = url;
  }

  public RepositoryConfiguration() {}

  public String getUrl() {
    return url;
  }

  public String getMode() {
    return mode;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/config/WebhookConfiguration.java
================================================
package org.whispersystems.bithub.config;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

public class WebhookConfiguration {

  @JsonProperty
  @NotEmpty
  private String username = "bithub";

  @JsonProperty
  @NotEmpty
  private String password;

  public String getUsername() { return username; }

  public String getPassword() { return password; }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/controllers/DashboardController.java
================================================
package org.whispersystems.bithub.controllers;

import com.codahale.metrics.annotation.Timed;
import org.whispersystems.bithub.storage.CacheManager;
import org.whispersystems.bithub.views.DashboardView;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/")
public class DashboardController {

  private final CacheManager cacheManager;
  private final String       organizationName;
  private final String       donationUrl;



  public DashboardController(String organizationName, String donationUrl,
                             CacheManager cacheManager)
  {
    this.organizationName = organizationName;
    this.donationUrl      = donationUrl;
    this.cacheManager     = cacheManager;
  }

  @Timed
  @GET
  @Produces(MediaType.TEXT_HTML)
  public DashboardView getDashboard() {
    return new DashboardView(organizationName, donationUrl,
                             cacheManager.getCurrentPaymentAmount(),
                             cacheManager.getRepositories(),
                             cacheManager.getRecentTransactions());
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/controllers/GithubController.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.controllers;

import com.codahale.metrics.annotation.Timed;
import com.coinbase.api.exception.CoinbaseException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.net.util.SubnetUtils;
import org.apache.commons.net.util.SubnetUtils.SubnetInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.bithub.auth.GithubWebhookAuthenticator.Authentication;
import org.whispersystems.bithub.client.CoinbaseClient;
import org.whispersystems.bithub.client.GithubClient;
import org.whispersystems.bithub.client.TransferFailedException;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.entities.Commit;
import org.whispersystems.bithub.entities.PushEvent;
import org.whispersystems.bithub.entities.Repository;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.dropwizard.auth.Auth;

/**
 * Handles incoming API calls from GitHub.  These are currently only
 * PushEvent webhooks.
 *
 * @author Moxie Marlinspike
 */
@Path("/v1/github")
public class GithubController {

  private static final String GITHUB_WEBOOK_CIDR = "192.30.252.0/22";
  private static final String MASTER_REF         = "refs/heads/master";

  private final Logger     logger         = LoggerFactory.getLogger(GithubController.class);
  private final SubnetInfo trustedNetwork = new SubnetUtils(GITHUB_WEBOOK_CIDR).getInfo();

  private final CoinbaseClient      coinbaseClient;
  private final GithubClient        githubClient;
  private final Map<String, String> repositories;
  private final BigDecimal          payoutRate;

  public GithubController(List<RepositoryConfiguration> repositories,
                          GithubClient githubClient,
                          CoinbaseClient coinbaseClient,
                          BigDecimal payoutRate)
  {
    this.coinbaseClient = coinbaseClient;
    this.githubClient   = githubClient;
    this.repositories   = new HashMap<>();
    this.payoutRate     = payoutRate;

    for (RepositoryConfiguration repository : repositories) {
      this.repositories.put(repository.getUrl().toLowerCase(),
                            repository.getMode().toUpperCase());
    }
  }

  @Timed
  @POST
  @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
  @Path("/commits/")
  public void handleCommits(@Auth Authentication auth,
                            @HeaderParam("X-Forwarded-For") String clientIp,
                            @FormParam("payload") String eventString)
      throws IOException, UnauthorizedHookException, TransferFailedException, CoinbaseException
  {
    authenticate(clientIp);
    PushEvent event = getEventFromPayload(eventString);

    if (!repositories.containsKey(event.getRepository().getUrl().toLowerCase())) {
      throw new UnauthorizedHookException("Not a valid repository: " +
                                          event.getRepository().getUrl());
    }

    if (!event.getRef().equals(MASTER_REF)) {
      logger.info("Not a push to master: " + event.getRef());
      return;
    }

    Repository   repository   = event.getRepository();
    String       defaultMode  = repositories.get(repository.getUrl().toLowerCase());
    List<Commit> commits      = getQualifyingCommits(event, defaultMode);
    BigDecimal   balance      = coinbaseClient.getAccountBalance();
    BigDecimal   exchangeRate = coinbaseClient.getExchangeRate();

    logger.info("Retrieved balance: " + balance.toPlainString());

    sendPaymentsFor(repository, commits, balance, exchangeRate);
  }


  private void sendPaymentsFor(Repository repository, List<Commit> commits,
                               BigDecimal balance, BigDecimal exchangeRate)
  {
    for (Commit commit : commits) {
      try {
        BigDecimal payout = balance.multiply(payoutRate);

        if (isViablePaymentAmount(payout)) {
          coinbaseClient.sendPayment(commit.getAuthor(), payout, commit.getUrl());
        }

        balance = balance.subtract(payout);

        githubClient.addCommitComment(repository, commit,
                                      getCommitCommentStringForPayment(payout, exchangeRate));
      } catch (TransferFailedException e) {
        logger.warn("Transfer failed", e);
      }
    }
  }

  private PushEvent getEventFromPayload(String payload) throws IOException {
    ObjectMapper     objectMapper = new ObjectMapper();
    PushEvent        event        = objectMapper.readValue(payload, PushEvent.class);
    ValidatorFactory factory      = Validation.buildDefaultValidatorFactory();
    Validator        validator    = factory.getValidator();

    validator.validate(event);
    return event;
  }

  private List<Commit> getQualifyingCommits(PushEvent event, String defaultMode) {
    List<Commit> commits = new LinkedList<>();
    Set<String>  emails  = new HashSet<>();

    for (Commit commit : event.getCommits()) {
      logger.info(commit.getUrl());
      if (!emails.contains(commit.getAuthor().getEmail())) {
        logger.info("Unique author: "+ commit.getAuthor().getEmail());
        if (isViableMessage(commit.getMessage(), defaultMode)) {
          logger.info("Not a merge commit or freebie...");

          emails.add(commit.getAuthor().getEmail());
          commits.add(commit);
        }
      }
    }

    return commits;
  }

  private boolean isViableMessage(String message, String defaultMode) {
    if (message == null || message.startsWith("Merge"))
      return false;

    return (!message.contains("FREEBIE") && defaultMode.equals("MONEYMONEY")) ||
           (message.contains("MONEYMONEY") && defaultMode.equals("FREEBIE"));
  }

  private boolean isViablePaymentAmount(BigDecimal payment) {
    return payment.compareTo(new BigDecimal(0)) == 1;
  }

  private String getCommitCommentStringForPayment(BigDecimal payment, BigDecimal exchangeRate) {
    if (isViablePaymentAmount(payment)) {
      BigDecimal paymentUsd = payment.multiply(exchangeRate).setScale(2, RoundingMode.CEILING);
      return "Thanks! BitHub has sent payment of  $" + paymentUsd.toPlainString() + "USD for this commit.";
    } else {
      return "Thanks! Unfortunately our BitHub balance is $0.00, so no payout can be made.";
    }
  }

  private void authenticate(String clientIp) throws UnauthorizedHookException {
    if (clientIp == null) {
      throw new UnauthorizedHookException("No X-Forwarded-For!");
    }

    if (!trustedNetwork.isInRange(clientIp)) {
      throw new UnauthorizedHookException("Untrusted IP: " + clientIp);
    }
 }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/controllers/StatusController.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.controllers;

import com.codahale.metrics.annotation.Timed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.entities.Repositories;
import org.whispersystems.bithub.entities.Repository;
import org.whispersystems.bithub.entities.Transaction;
import org.whispersystems.bithub.entities.Transactions;
import org.whispersystems.bithub.storage.CacheManager;
import org.whispersystems.bithub.storage.CurrentPayment;
import org.whispersystems.bithub.views.TransactionsView;

import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import io.dropwizard.jersey.caching.CacheControl;

/**
 * Handles incoming API calls for BitHub instance status information.
 *
 * @author Moxie Marlinspike
 */
@Path("/v1/status")
public class StatusController {

  private final Logger logger = LoggerFactory.getLogger(StatusController.class);

  private final List<RepositoryConfiguration> repositoryConfiguration;
  private final CacheManager coinbaseManager;

  public StatusController(CacheManager coinbaseManager,
                          List<RepositoryConfiguration> repositoryConfiguration)
      throws IOException
  {
    this.coinbaseManager         = coinbaseManager;
    this.repositoryConfiguration = repositoryConfiguration;
  }

  @Timed
  @GET
  @Path("/transactions")
  public Response getTransactions(@QueryParam("format") @DefaultValue("html") String format)
        throws IOException
  {
    List<Transaction> recentTransactions = coinbaseManager.getRecentTransactions();

    switch (format) {
      case "html": return Response.ok(new TransactionsView(recentTransactions), MediaType.TEXT_HTML_TYPE).build();
      case "json":
      default:     return Response.ok(new Transactions(recentTransactions), MediaType.APPLICATION_JSON_TYPE).build();
    }
  }

  @Timed
  @GET
  @Path("/repositories")
  @Produces(MediaType.APPLICATION_JSON)
  public Repositories getRepositories() {
    List<Repository> repositories = new LinkedList<>();

    for (RepositoryConfiguration configuration : repositoryConfiguration) {
      repositories.add(new Repository(configuration.getUrl()));
    }

    return new Repositories(repositories);
  }


  @Timed
  @GET
  @Path("/payment/commit")
  @CacheControl(noCache = true)
  public Response getCurrentCommitPrice(@QueryParam("format") @DefaultValue("png") String format)
      throws IOException
  {
    CurrentPayment currentPayment = coinbaseManager.getCurrentPaymentAmount();

    switch (format) {
      case "json":
        return Response.ok(currentPayment.getEntity(), MediaType.APPLICATION_JSON_TYPE).build();
      case "png_small":
        return Response.ok(currentPayment.getSmallBadge(), "image/png").build();
      default:
        return Response.ok(currentPayment.getBadge(), "image/png").build();
    }
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/controllers/UnauthorizedHookException.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.controllers;

public class UnauthorizedHookException extends Throwable {
  public UnauthorizedHookException(String s) {
    super(s);
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/Author.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.entities;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Author {

  @JsonProperty
  private String name;

  @JsonProperty
  @NotEmpty
  private String email;

  @JsonProperty
  private String username;

  public String getName() {
    return name;
  }

  public String getEmail() {
    return email;
  }

  public String getUsername() {
    return username;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/Commit.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.entities;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import javax.validation.constraints.NotNull;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Commit {

  @JsonProperty
  private String  id;

  @JsonProperty
  private String  message;

  @JsonProperty
  @NotNull
  private Author  author;

  @JsonProperty
  private String  url;

  @JsonProperty
  private boolean distinct;

  public String getSha() {
    return id;
  }

  public String getMessage() {
    return message;
  }

  public Author getAuthor() {
    return author;
  }

  public String getUrl() {
    return url;
  }

  public boolean isDistinct() {
    return distinct;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/CommitComment.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.entities;

import com.fasterxml.jackson.annotation.JsonProperty;

public class CommitComment {

  @JsonProperty
  private String body;

  public CommitComment(String body) {
    this.body = body;
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/Payment.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.entities;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Payment {
  @JsonProperty
  private String payment;

  public Payment(String payment) {
    this.payment = payment;
  }

  public String getPayment() {
    return payment;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/PushEvent.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.entities;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

import javax.validation.constraints.NotNull;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
public class PushEvent {

  @JsonProperty
  private String head;

  @JsonProperty
  private String ref;

  @JsonProperty
  private int size;

  @JsonProperty
  @NotNull
  List<Commit> commits;

  @JsonProperty
  @NotNull
  Repository repository;

  public Repository getRepository() {
    return repository;
  }

  public String getHead() {
    return head;
  }

  public String getRef() {
    return ref;
  }

  public int getSize() {
    return size;
  }

  public List<Commit> getCommits() {
    return commits;
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/Repositories.java
================================================
package org.whispersystems.bithub.entities;

import java.util.List;

public class Repositories {

  public List<Repository> repositories;

  public Repositories() {}

  public Repositories(List<Repository> repositories) {
    this.repositories = repositories;
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/Repository.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.entities;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

import javax.validation.constraints.NotNull;

@JsonIgnoreProperties(ignoreUnknown = true)
public class Repository {

  @JsonProperty
  @NotEmpty
  private String url;

  @JsonProperty
  @NotNull
  private Author owner;

  @JsonProperty
  @NotEmpty
  private String name;

  @JsonProperty
  private String description;

  public Repository() {}

  public Repository(String url) {
    this.url = url;
  }

  public Author getOwner() {
    return owner;
  }

  public String getName() {
    return name;
  }

  public String getUrl() {
    return url;
  }

  public String getDescription() {
    return description;
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/Transaction.java
================================================
package org.whispersystems.bithub.entities;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Transaction {

  @JsonProperty
  private String destination;

  @JsonProperty
  private String amount;

  @JsonProperty
  private String commitUrl;

  @JsonProperty
  private String commitSha;

  @JsonProperty
  private String timestamp;

  @JsonProperty
  private String description;

  public Transaction() {}

  public Transaction(String destination, String amount, String commitUrl,
                     String commitSha, String timestamp, String description)
  {
    this.destination = destination;
    this.amount      = amount;
    this.commitUrl   = commitUrl;
    this.commitSha   = commitSha;
    this.timestamp   = timestamp;
    this.description = description;
  }

  public String getDestination() {
    return destination;
  }

  public String getAmount() {
    return amount;
  }

  public String getCommitUrl() {
    return commitUrl;
  }

  public String getCommitSha() {
    return commitSha;
  }

  public String getTimestamp() {
    return timestamp;
  }

  public String getDescription() {
    return description;
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/entities/Transactions.java
================================================
package org.whispersystems.bithub.entities;


import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public class Transactions {

  @JsonProperty
  private List<Transaction> transactions;

  public Transactions() {}

  public Transactions(List<Transaction> transactions) {
    this.transactions = transactions;
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/mappers/IOExceptionMapper.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.mappers;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
public class IOExceptionMapper implements ExceptionMapper<IOException> {

  private final Logger logger = LoggerFactory.getLogger(IOExceptionMapper.class);

  @Override
  public Response toResponse(IOException e) {
    logger.warn("IOExceptionMapper", e);
    return Response.status(503).build();
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/mappers/UnauthorizedHookExceptionMapper.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.mappers;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.bithub.controllers.UnauthorizedHookException;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class UnauthorizedHookExceptionMapper implements ExceptionMapper<UnauthorizedHookException> {

  private final Logger logger = LoggerFactory.getLogger(IOExceptionMapper.class);

  @Override
  public Response toResponse(UnauthorizedHookException e) {
    logger.warn("IOExceptionMapper", e);
    return Response.status(401).build();
  }
}

================================================
FILE: src/main/java/org/whispersystems/bithub/storage/CacheManager.java
================================================
package org.whispersystems.bithub.storage;

import com.coinbase.api.exception.CoinbaseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.bithub.client.CoinbaseClient;
import org.whispersystems.bithub.client.GithubClient;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.entities.Payment;
import org.whispersystems.bithub.entities.Repository;
import org.whispersystems.bithub.entities.Transaction;
import org.whispersystems.bithub.util.Badge;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import io.dropwizard.lifecycle.Managed;

public class CacheManager implements Managed {

  private static final int UPDATE_FREQUENCY_MILLIS = 60 * 1000;

  private final Logger                   logger   = LoggerFactory.getLogger(CacheManager.class);
  private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

  private final CoinbaseClient                coinbaseClient;
  private final GithubClient                  githubClient;
  private final BigDecimal                    payoutRate;
  private final List<RepositoryConfiguration> repositories;

  private AtomicReference<CurrentPayment>    cachedPaymentStatus;
  private AtomicReference<List<Transaction>> cachedTransactions;
  private AtomicReference<List<Repository>>  cachedRepositories;

  public CacheManager(CoinbaseClient coinbaseClient,
                      GithubClient githubClient,
                      List<RepositoryConfiguration> repositories,
                      BigDecimal payoutRate)
  {
    this.coinbaseClient = coinbaseClient;
    this.githubClient   = githubClient;
    this.payoutRate     = payoutRate;
    this.repositories   = repositories;
  }

  @Override
  public void start() throws Exception {
    this.cachedPaymentStatus = new AtomicReference<>(createCurrentPaymentForBalance(coinbaseClient));
    this.cachedTransactions  = new AtomicReference<>(createRecentTransactions(coinbaseClient));
    this.cachedRepositories  = new AtomicReference<>(createRepositories(githubClient, repositories));

    initializeUpdates(coinbaseClient, githubClient, repositories);
  }

  @Override
  public void stop() throws Exception {
    this.executor.shutdownNow();
  }

  public List<Transaction> getRecentTransactions() {
    return cachedTransactions.get();
  }

  public CurrentPayment getCurrentPaymentAmount() {
    return cachedPaymentStatus.get();
  }

  public List<Repository> getRepositories() {
    return cachedRepositories.get();
  }

  public void initializeUpdates(final CoinbaseClient coinbaseClient,
                                final GithubClient githubClient,
                                final List<RepositoryConfiguration> repoConfigs)
  {
    executor.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        logger.warn("Running cache update...");
        try {
          CurrentPayment    currentPayment = createCurrentPaymentForBalance(coinbaseClient);
          List<Transaction> transactions   = createRecentTransactions      (coinbaseClient);
          List<Repository>  repositories   = createRepositories(githubClient, repoConfigs);

          cachedPaymentStatus.set(currentPayment);
          cachedTransactions.set(transactions);
          cachedRepositories.set(repositories);

        } catch (IOException | CoinbaseException e) {
          logger.warn("Failed to update badge", e);
        }
      }
    }, UPDATE_FREQUENCY_MILLIS, UPDATE_FREQUENCY_MILLIS, TimeUnit.MILLISECONDS);
  }

  private List<Repository> createRepositories(GithubClient githubClient,
                                              List<RepositoryConfiguration> configured)
  {
    List<Repository> repositoryList = new LinkedList<>();

    for (RepositoryConfiguration repository : configured) {
      repositoryList.add(githubClient.getRepository(repository.getUrl()));
    }

    return repositoryList;
  }

  private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseClient)
      throws IOException, CoinbaseException
  {
    BigDecimal currentBalance = coinbaseClient.getAccountBalance();
    BigDecimal paymentBtc     = currentBalance.multiply(payoutRate);
    BigDecimal exchangeRate   = coinbaseClient.getExchangeRate();
    BigDecimal paymentUsd     = paymentBtc.multiply(exchangeRate);

    paymentUsd = paymentUsd.setScale(2, RoundingMode.CEILING);
    return new CurrentPayment(Badge.createFor(paymentUsd.toPlainString()),
                              Badge.createSmallFor(paymentUsd.toPlainString()),
                              new Payment(paymentUsd.toPlainString()));
  }

  private List<Transaction> createRecentTransactions(CoinbaseClient coinbaseClient)
          throws IOException, CoinbaseException
  {
    List<com.coinbase.api.entity.Transaction> recentTransactions = coinbaseClient.getRecentTransactions();
    BigDecimal                                exchangeRate       = coinbaseClient.getExchangeRate();
    List<Transaction>                         transactions       = new LinkedList<>();

    for (com.coinbase.api.entity.Transaction coinbaseTransaction : recentTransactions) {
      try {
        if (isSentTransaction(coinbaseTransaction)) {
          CoinbaseTransactionParser parser      = new CoinbaseTransactionParser(coinbaseTransaction);
          String                    url         = parser.parseUrlFromMessage();
          String                    sha         = parser.parseShaFromUrl(url);
          String                    description = githubClient.getCommitDescription(url);

          transactions.add(new Transaction(parser.parseDestinationFromMessage(),
                                           parser.parseAmountInDollars(exchangeRate),
                                           url, sha, parser.parseTimestamp(),
                                           description));

          if (transactions.size() >= 10)
            break;
        }
      } catch (ParseException e) {
        logger.warn("Parse", e);
      }
    }

    return transactions;
  }

  private boolean isSentTransaction(com.coinbase.api.entity.Transaction coinbaseTransaction) {
    BigDecimal amount = coinbaseTransaction.getAmount().getAmount();
    return amount.compareTo(new BigDecimal(0.0)) < 0;
  }


}


================================================
FILE: src/main/java/org/whispersystems/bithub/storage/CoinbaseTransactionParser.java
================================================
package org.whispersystems.bithub.storage;

import com.coinbase.api.entity.Transaction;
import org.apache.commons.lang3.StringEscapeUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;

public class CoinbaseTransactionParser {

  private final Transaction coinbaseTransaction;

  public CoinbaseTransactionParser(Transaction coinbaseTransaction) {
    this.coinbaseTransaction = coinbaseTransaction;
  }

  public String parseAmountInDollars(BigDecimal exchangeRate) {
    return coinbaseTransaction.getAmount().getAmount().abs()
                              .multiply(exchangeRate)
                              .setScale(2, RoundingMode.CEILING)
                              .toPlainString();
  }

  public String parseTimestamp() throws ParseException {
    DateTime          timestamp = coinbaseTransaction.getCreatedAt();
    DateTimeFormatter fmt       = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ");
    return fmt.print(timestamp);
  }

  public String parseDestinationFromMessage() {
    String message = StringEscapeUtils.unescapeHtml4(coinbaseTransaction.getNotes());
    int startToken = message.indexOf("__");

    if (startToken == -1) {
      return "Unknown";
    }

    int endToken = message.indexOf("__", startToken + 1);

    if (endToken == -1) {
      return "Unknown";
    }

    return message.substring(startToken+2, endToken);
  }

  public String parseUrlFromMessage() throws ParseException {
    String message = StringEscapeUtils.unescapeHtml4(coinbaseTransaction.getNotes());
    int urlIndex = message.indexOf("https://");

    return message.substring(urlIndex).trim();
  }

  public String parseShaFromUrl(String url) throws ParseException {
    if (url == null) {
      throw new ParseException("No url", 0);
    }

    String[] parts    = url.split("/");
    String   fullHash = parts[parts.length-1];

    if (fullHash.length() < 8) {
      throw new ParseException("Not long enough", 0);
    }

    return fullHash.substring(0, 8);
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/storage/CurrentPayment.java
================================================
package org.whispersystems.bithub.storage;

import org.whispersystems.bithub.entities.Payment;

public class CurrentPayment {

  private final byte[]  badge;
  private final byte[]  smallBadge;
  private final Payment entity;

  protected CurrentPayment(byte[] badge, byte[] smallBadge, Payment entity) {
    this.badge      = badge;
    this.smallBadge = smallBadge;
    this.entity     = entity;
  }

  public byte[] getBadge() {
    return badge;
  }

  public byte[] getSmallBadge() {
    return smallBadge;
  }

  public Payment getEntity() {
    return entity;
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/util/AdvancedAtomicLong.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.util;

import java.util.concurrent.atomic.AtomicLong;

public class AdvancedAtomicLong extends AtomicLong {

  public AdvancedAtomicLong(long initial) {
    super(initial);
  }

  public boolean setIfGreater(long compare, long update) {
    while(true) {
      long current = get();

      if (compare > current) {
        if (compareAndSet(current, update)) {
          return true;
        }
      } else {
        return false;
      }
    }
  }
}


================================================
FILE: src/main/java/org/whispersystems/bithub/util/Badge.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.util;

import com.google.common.io.Resources;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class Badge {

  public static byte[] createFor(String price) throws IOException {
    byte[]        badgeBackground = Resources.toByteArray(Resources.getResource("assets/badge.png"));
    BufferedImage bufferedImage   = ImageIO.read(new ByteArrayInputStream(badgeBackground));
    Graphics2D    graphics        = bufferedImage.createGraphics();

    graphics.setFont(new Font("OpenSans", Font.PLAIN, 34));
    graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                              RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
    graphics.drawString(price + " USD", 86, 45);
    graphics.dispose();

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageIO.write(bufferedImage, "png", baos);

    return baos.toByteArray();
  }

  public static byte[] createSmallFor(String price) throws IOException {
    byte[]        badgeBackground = Resources.toByteArray(Resources.getResource("assets/badge-small.png"));
    BufferedImage bufferedImage   = ImageIO.read(new ByteArrayInputStream(badgeBackground));
    Graphics2D    graphics        = bufferedImage.createGraphics();

    graphics.setFont(new Font("OpenSans", Font.PLAIN, 9));
    graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                              RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
    graphics.drawString(price + " USD", 22, 14);
    graphics.dispose();

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageIO.write(bufferedImage, "png", baos);

    return baos.toByteArray();
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/views/DashboardView.java
================================================
package org.whispersystems.bithub.views;

import org.whispersystems.bithub.entities.Repository;
import org.whispersystems.bithub.entities.Transaction;
import org.whispersystems.bithub.storage.CurrentPayment;

import java.util.List;

import io.dropwizard.views.View;

public class DashboardView extends View {

  private final String            organizationName;
  private final String            donationUrl;
  private final CurrentPayment    currentPayment;
  private final List<Repository>  repositories;
  private final List<Transaction> transactions;

  public DashboardView(String organizationName, String donationUrl,
                       CurrentPayment currentPayment,
                       List<Repository> repositories,
                       List<Transaction> transactions)
  {
    super("dashboard.mustache");
    this.organizationName = organizationName;
    this.donationUrl      = donationUrl;
    this.currentPayment   = currentPayment;
    this.repositories     = repositories;
    this.transactions     = transactions;
  }

  public String getPayment() {
    return currentPayment.getEntity().getPayment();
  }

  public String getOrganizationName() {
    return organizationName;
  }

  public String getDonationUrl() {
    return donationUrl;
  }

  public List<Repository> getRepositories() {
    return repositories;
  }

  public List<Transaction> getTransactions() {
    return transactions;
  }

  public String getRepositoriesCount() {
    return String.valueOf(repositories.size());
  }

}


================================================
FILE: src/main/java/org/whispersystems/bithub/views/TransactionsView.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.views;

import org.whispersystems.bithub.entities.Transaction;

import java.util.List;

import io.dropwizard.views.View;

/**
 * A rendered HTML view of recent BitHub transactions.
 *
 * @author Moxie Marlinspike
 */
public class TransactionsView extends View {

  private final List<Transaction> transactions;

  public TransactionsView(List<Transaction> transactions) {
    super("recent_transactions.mustache");
    this.transactions = transactions;
  }

  public List<Transaction> getTransactions() {
    return transactions;
  }
}


================================================
FILE: src/main/resources/banner.txt
================================================
888888b.   d8b 888    888    888          888      
888  "88b  Y8P 888    888    888          888      
888  .88P      888    888    888          888      
8888888K.  888 888888 8888888888 888  888 88888b.  
888  "Y88b 888 888    888    888 888  888 888 "88b 
888    888 888 888    888    888 888  888 888  888 
888   d88P 888 Y88b.  888    888 Y88b 888 888 d88P 
8888888P"  888  "Y888 888    888  "Y88888 88888P"  
                                                   
                                                   


================================================
FILE: src/main/resources/org/whispersystems/bithub/views/dashboard.mustache
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="../../favicon.ico">

    <title>BitHub :: Dashboard </title>

    <!-- Bootstrap core CSS -->
    <link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->

    <style type="text/css">
        body {
            padding-top: 20px;
        }

        pre {
            white-space: pre-wrap;       /* css-3 */
            white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
            white-space: -pre-wrap;      /* Opera 4-6 */
            white-space: -o-pre-wrap;    /* Opera 7 */
            word-wrap: break-word;       /* Internet Explorer 5.5+ */

            padding: 0px;
            border: 0px;
            background-color: transparent;
            font-family: monospace;
        }

         /* top tags */

        .hero-widget { text-align: center; padding-top: 20px; padding-bottom: 20px; }
        .hero-widget .icon { display: block; font-size: 96px; line-height: 96px; margin-bottom: 10px; text-align: center; }
        .hero-widget var { display: block; height: 64px; font-size: 64px; line-height: 64px; font-style: normal; }
        .hero-widget label { font-size: 17px; }
        .hero-widget .options { margin-top: 10px; }

         /* repositories */

        .repository {
            margin-top: 20px;
            padding: 40px 0px 20px 0px;
            background-color: #f7f7f7;
            -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
            -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
            box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
        }

        .repository-name {
            font-size: 50px;
            padding: 15px;
        }

        .repository-name a {
            text-decoration: none;
        }

        .repository-description {
            font-size: 20px;
            padding: 15px;
        }
    </style>
</head>

<body>

<div class="container">
    <div class="row">
        <div class="col-sm-3">
            <h1>BitHub</h1>
            <p class="lead">{{organizationName}}</p>
            <p><a class="btn btn-lg btn-success" href="{{donationUrl}}" role="button">Donate BTC today</a></p>
        </div>
        <div class="col-sm-3">
            <div class="hero-widget well well-sm">
                <div class="icon">
                    <i class="glyphicon glyphicon-usd"></i>
                </div>
                <div class="text">
                    <var>{{payment}}</var>
                    <label class="text-muted">USD per commit</label>
                </div>
            </div>
        </div>
        <div class="col-sm-3">
            <div class="hero-widget well well-sm">
                <div class="icon">
                    <i class="glyphicon glyphicon-tags"></i>
                </div>
                <div class="text">
                    <var>0</var>
                    <label class="text-muted">open bounties</label>
                </div>
            </div>
        </div>
        <div class="col-sm-3">
            <div class="hero-widget well well-sm">
                <div class="icon">
                    <i class="glyphicon glyphicon-book"></i>
                </div>
                <div class="text">
                    <var>{{repositoriesCount}}</var>
                    <label class="text-muted">repositories</label>
                </div>
            </div>
        </div>
    </div>

    <h2 class="sub-header">Recent payments</h2>
    <div class="table-responsive">
        <table class="table table-striped">
            <thead>
            <tr>
                <th>USD</th>
                <th>Author</th>
                <th>Description</th>
                <th>Commit</th>
            </tr>
            </thead>
            <tbody id="transactions_target">
                {{#transactions}}
                <tr>
                    <td>${{amount}} USD</td>
                    <td><a href="https://github.com/{{destination}}">{{destination}}</a></td>
                    <td><pre>{{description}}</pre></td>
                    <td><a href="{{commitUrl}}">{{commitSha}}</a></td>
                </tr>
                {{/transactions}}
            </tbody>
        </table>
    </div>

    <h2 class="sub-header">Repositories</h2>
        <div class="row">
            {{#repositories}}
            <div class="col-xs-12 col-sm-6 col-md-6 col-lg-6">
                <div class="repository">
                    <div class="repository-name">
                        <a href="{{url}}">{{name}}</a>
                    </div>
                    <div class="repository-description">
                        <p>{{description}}</p>
                    </div>
                </div>
            </div>
            {{/repositories}}
        </div>
</div>
</body>
</html>


================================================
FILE: src/main/resources/org/whispersystems/bithub/views/recent_transactions.mustache
================================================
<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Security-Policy" content="script-src none">
    <meta charset="utf-8">

    <link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css'>

    <style>
        body {
            font-family: 'Open Sans', sans-serif;
            letter-spacing: .01rem;
            font-weight: 400;
            font-style: normal;
            font-size: 22px;
            line-height: 1.5;
            color: #333332;
        }

        sha {
            font-family: Consolas, "Liberation Mono", Courier, monospace;
            color: #4183c4;

            margin: 2px;
            padding: 3px;
            border: 1px solid #ddd;
            background-color: #f8f8f8;
            border-radius: 3px;
            font-size: 20px;
        }

        ul { list-style-type: none;}

        li { padding: 5px; padding-left: 20px;}

        li:nth-child(even) { background-color: #ffffff; }
        li:nth-child(odd)  { background-color: #eeeeee;}

        a { text-decoration: none; color: #555; }
    </style>

</head>
<body>

<ul>
    {{#transactions}}
    <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>
    {{/transactions}}
</ul>

</body>
</html>


================================================
FILE: src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java
================================================
/**
 * Copyright (C) 2013 Open WhisperSystems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.whispersystems.bithub.tests.controllers;

import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import org.apache.commons.codec.binary.Base64;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.whispersystems.bithub.auth.GithubWebhookAuthenticator;
import org.whispersystems.bithub.client.CoinbaseClient;
import org.whispersystems.bithub.client.GithubClient;
import org.whispersystems.bithub.client.TransferFailedException;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.controllers.GithubController;
import org.whispersystems.bithub.entities.Author;
import org.whispersystems.bithub.mappers.UnauthorizedHookExceptionMapper;

import javax.ws.rs.core.MediaType;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

import io.dropwizard.auth.basic.BasicAuthProvider;
import io.dropwizard.testing.junit.ResourceTestRule;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;

public class GithubControllerTest {

  private static final BigDecimal BALANCE = new BigDecimal(10.01);
  private static final BigDecimal EXCHANGE_RATE = new BigDecimal(1.0);

  private final CoinbaseClient coinbaseClient = mock(CoinbaseClient.class);
  private final GithubClient   githubClient   = mock(GithubClient.class);

  // HTTP Basic Authentication data
  private final String authUsername = "TestUser";
  private final String authPassword = "TestPassword";
  private final String authRealm = GithubWebhookAuthenticator.REALM;
  private final String authString = "Basic " + Base64.encodeBase64String((authUsername + ":" + authPassword).getBytes());
  private final String invalidUserAuthString = "Basic " + Base64.encodeBase64(("wrong:" + authPassword).getBytes());
  private final String invalidPasswordAuthString = "Basic " + Base64.encodeBase64((authUsername + ":wrong").getBytes());

  private final List<RepositoryConfiguration> repositories = new LinkedList<RepositoryConfiguration>() {{
    add(new RepositoryConfiguration("https://github.com/moxie0/test"));
    add(new RepositoryConfiguration("https://github.com/moxie0/optin", "FREEBIE"));
  }};

  @Rule
  public final ResourceTestRule resources = ResourceTestRule.builder()
                                                            .addProvider(new UnauthorizedHookExceptionMapper())
                                                            .addProvider(new BasicAuthProvider<>(new GithubWebhookAuthenticator(authUsername, authPassword), authRealm))
                                                            .addResource(new GithubController(repositories, githubClient, coinbaseClient, new BigDecimal(0.02)))
                                                            .build();


  @Before
  public void setup() throws Exception, TransferFailedException {
    when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE);
    when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE);
  }

  protected String payload(String path) {
    InputStream is = this.getClass().getResourceAsStream(path);
    Scanner s = new Scanner(is).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
  }

  @Test
  public void testInvalidRepository() throws Exception {
    String payloadValue = payload("/payloads/invalid_repo.json");
    MultivaluedMapImpl post = new MultivaluedMapImpl();
    post.add("payload", payloadValue);
    ClientResponse response = resources.client().resource("/v1/github/commits/")
        .header("X-Forwarded-For", "192.30.252.1")
        .header("Authorization", authString)
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
        .post(ClientResponse.class, post);

    assertThat(response.getStatus()).isEqualTo(401);
  }

  @Test
  public void testInvalidOrigin() throws Exception {
    String payloadValue = payload("/payloads/invalid_origin.json");
    MultivaluedMapImpl post = new MultivaluedMapImpl();
    post.add("payload", payloadValue);
    ClientResponse response = resources.client().resource("/v1/github/commits/")
        .header("X-Forwarded-For", "192.30.242.1")
        .header("Authorization", authString)
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
        .post(ClientResponse.class, post);

    assertThat(response.getStatus()).isEqualTo(401);
  }

  @Test
  public void testMissingAuth() throws Exception, TransferFailedException {
      String payloadValue = payload("/payloads/valid_commit.json");
      MultivaluedMapImpl post = new MultivaluedMapImpl();
      post.add("payload", payloadValue);
      ClientResponse response = resources.client().resource("/v1/github/commits/")
              .header("X-Forwarded-For", "192.30.252.1")
              .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
              .post(ClientResponse.class, post);

    assertThat(response.getStatus()).isEqualTo(401);
  }

  @Test
  public void testInvalidAuthUser() throws Exception, TransferFailedException {
      String payloadValue = payload("/payloads/valid_commit.json");
      MultivaluedMapImpl post = new MultivaluedMapImpl();
      post.add("payload", payloadValue);
      ClientResponse response = resources.client().resource("/v1/github/commits/")
              .header("X-Forwarded-For", "192.30.252.1")
              .header("Authorization", invalidUserAuthString)
              .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
              .post(ClientResponse.class, post);

    assertThat(response.getStatus()).isEqualTo(401);
  }

  @Test
  public void testInvalidAuthPassword() throws Exception, TransferFailedException {
      String payloadValue = payload("/payloads/valid_commit.json");
      MultivaluedMapImpl post = new MultivaluedMapImpl();
      post.add("payload", payloadValue);
      ClientResponse response = resources.client().resource("/v1/github/commits/")
              .header("X-Forwarded-For", "192.30.252.1")
              .header("Authorization", invalidPasswordAuthString)
              .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
              .post(ClientResponse.class, post);

    assertThat(response.getStatus()).isEqualTo(401);
  }

  @Test
  public void testOptOutCommit() throws Exception, TransferFailedException {
    String payloadValue = payload("/payloads/opt_out_commit.json");
    MultivaluedMapImpl post = new MultivaluedMapImpl();
    post.add("payload", payloadValue);
    ClientResponse response = resources.client().resource("/v1/github/commits/")
        .header("X-Forwarded-For", "192.30.252.1")
        .header("Authorization", authString)
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
        .post(ClientResponse.class, post);

    verify(coinbaseClient, never()).sendPayment(any(Author.class),
                                       any(BigDecimal.class),
                                       anyString());
  }

  @Test
  public void testValidCommit() throws Exception, TransferFailedException {
    String payloadValue = payload("/payloads/valid_commit.json");
    MultivaluedMapImpl post = new MultivaluedMapImpl();
    post.add("payload", payloadValue);
    ClientResponse response = resources.client().resource("/v1/github/commits/")
        .header("X-Forwarded-For", "192.30.252.1")
        .header("Authorization", authString)
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
        .post(ClientResponse.class, post);

    verify(coinbaseClient).sendPayment(any(Author.class),
                                       eq(BALANCE.multiply(new BigDecimal(0.02))),
                                       anyString());
  }

  @Test
  public void testNonMaster() throws Exception, TransferFailedException {
    String payloadValue = payload("/payloads/non_master_push.json");
    MultivaluedMapImpl post = new MultivaluedMapImpl();
    post.add("payload", payloadValue);
    ClientResponse response = resources.client().resource("/v1/github/commits/")
        .header("X-Forwarded-For", "192.30.252.1")
        .header("Authorization", authString)
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
        .post(ClientResponse.class, post);

    verify(coinbaseClient, never()).sendPayment(any(Author.class),
                                       eq(BALANCE.multiply(new BigDecimal(0.02))),
                                       anyString());
  }

  @Test
  public void testValidMultipleCommitsMultipleAuthors() throws Exception, TransferFailedException {
    String payloadValue = payload("/payloads/multiple_commits_authors.json");
    MultivaluedMapImpl post = new MultivaluedMapImpl();
    post.add("payload", payloadValue);
    ClientResponse response = resources.client().resource("/v1/github/commits/")
        .header("X-Forwarded-For", "192.30.252.1")
        .header("Authorization", authString)
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
        .post(ClientResponse.class, post);

    verify(coinbaseClient, times(1)).sendPayment(any(Author.class), eq(BALANCE.multiply(new BigDecimal(0.02))),
        anyString());
    verify(coinbaseClient, times(1)).sendPayment(any(Author.class), eq(BALANCE.subtract(BALANCE.multiply(new BigDecimal(0.02)))
        .multiply(new BigDecimal(0.02))), anyString());
  }

  @Test
  public void testOptInCommit() throws Exception, TransferFailedException {
    String payloadValue = payload("/payloads/opt_in_commit.json");
    MultivaluedMapImpl post = new MultivaluedMapImpl();
    post.add("payload", payloadValue);
    ClientResponse response = resources.client().resource("/v1/github/commits/")
        .header("X-Forwarded-For", "192.30.252.1")
        .header("Authorization", authString)
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
        .post(ClientResponse.class, post);

    verify(coinbaseClient).sendPayment(any(Author.class),
                                       eq(BALANCE.multiply(new BigDecimal(0.02))),
                                       anyString());
  }

  @Test
  public void testNoOptInCommit() throws Exception, TransferFailedException {
    String payloadValue = payload("/payloads/no_opt_in_commit.json");
    MultivaluedMapImpl post = new MultivaluedMapImpl();
    post.add("payload", payloadValue);
    ClientResponse response = resources.client().resource("/v1/github/commits/")
        .header("X-Forwarded-For", "192.30.252.1")
        .header("Authorization", authString)
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)
        .post(ClientResponse.class, post);

    verify(coinbaseClient, never()).sendPayment(any(Author.class),
                                                any(BigDecimal.class),
                                                anyString());
  }


}


================================================
FILE: src/test/java/org/whispersystems/bithub/tests/controllers/StatusControllerTest.java
================================================
package org.whispersystems.bithub.tests.controllers;

import com.coinbase.api.ObjectMapperProvider;
import com.coinbase.api.entity.TransactionsResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.jersey.api.client.ClientResponse;
import org.junit.ClassRule;
import org.junit.Test;
import org.whispersystems.bithub.client.CoinbaseClient;
import org.whispersystems.bithub.client.GithubClient;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.controllers.StatusController;
import org.whispersystems.bithub.storage.CacheManager;

import javax.ws.rs.core.MediaType;
import java.math.BigDecimal;
import java.util.LinkedList;

import io.dropwizard.testing.junit.ResourceTestRule;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class StatusControllerTest {

  private static final BigDecimal PAYOUT_RATE   = new BigDecimal(0.02 );
  private static final BigDecimal BALANCE       = new BigDecimal(10.01);
  private static final BigDecimal EXCHANGE_RATE = new BigDecimal(1.0  );

  private static final CoinbaseClient coinbaseClient = mock(CoinbaseClient.class);
  private static final GithubClient   githubClient   = mock(GithubClient.class  );

  @ClassRule
  public static ResourceTestRule resources;

  static {
    try {
      ObjectMapper objectMapper = ObjectMapperProvider.createDefaultMapper();
      TransactionsResponse transactionsResponse = objectMapper.readValue(StatusControllerTest.class.getResourceAsStream("/payloads/transactions.json"), TransactionsResponse.class);
      when(coinbaseClient.getRecentTransactions()).thenReturn(transactionsResponse.getTransactions());
      when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE);
      when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE);

      CacheManager coinbaseManager = new CacheManager(coinbaseClient, githubClient,
                                                      new LinkedList<RepositoryConfiguration>(),
                                                      PAYOUT_RATE);
      coinbaseManager.start();

      resources = ResourceTestRule.builder()
                                  .addResource(new StatusController(coinbaseManager, null))
                                  .build();
    } catch (Exception e) {
      throw new AssertionError(e);
    }
  }

//  @Before
//  public void setup() throws Exception {
//    when(coinbaseClient.getRecentTransactions()).thenReturn(fromJson(jsonFixture("payloads/transactions.json"), RecentTransactionsResponse.class).getTransactions());
//    when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE);
//    when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE);
//
//  }

//  @Test
//  public void testTransactionsHtml() throws Exception {
//    ClientResponse response = resources.client().resource("/v1/status/transactions/")
//        .get(ClientResponse.class);
//
//    assertThat(response.getStatus()).isEqualTo(200);
//    assertThat(response.getType()).isEqualTo(MediaType.TEXT_HTML_TYPE);
//  }

  @Test
  public void testTransactionsJson() throws Exception {
    ClientResponse response = resources.client().resource("/v1/status/transactions/?format=json").accept(MediaType.APPLICATION_JSON_TYPE)
        .get(ClientResponse.class);

    assertThat(response.getStatus()).isEqualTo(200);
    assertThat(response.getType()).isEqualTo(MediaType.APPLICATION_JSON_TYPE);
  }

}


================================================
FILE: src/test/java/org/whispersystems/bithub/tests/util/JsonHelper.java
================================================
package org.whispersystems.bithub.tests.util;


import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

import static io.dropwizard.testing.FixtureHelpers.fixture;

public class JsonHelper {

  private static final ObjectMapper objectMapper = new ObjectMapper();

  public static String asJson(Object object) throws JsonProcessingException {
    return objectMapper.writeValueAsString(object);
  }

  public static <T> T fromJson(String value, Class<T> clazz) throws IOException {
    return objectMapper.readValue(value, clazz);
  }

  public static String jsonFixture(String filename) throws IOException {
    return objectMapper.writeValueAsString(objectMapper.readValue(fixture(filename), JsonNode.class));
  }
}


================================================
FILE: src/test/resources/payloads/invalid_origin.json
================================================
{
    "after": "100e9859651b35a3505cc278e9a98a076f79940b",
    "before": "6626766348ab245bdb3351989f753bd6e792524a",
    "commits": [
        {
            "added": [],
            "author": {
                "email": "info@whispersystems.org",
                "name": "WhisperBTC",
                "username": "WhisperBTC"
            },
            "committer": {
                "email": "info@whispersystems.org",
                "name": "WhisperBTC",
                "username": "WhisperBTC"
            },
            "distinct": true,
            "id": "fd7daeb1de6d72220b1313a7f1112d43885013aa",
            "message": "Update foo",
            "modified": [
                "foo"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:27:00-08:00",
            "url": "https://github.com/moxie0/tempt/commit/fd7daeb1de6d72220b1313a7f1112d43885013aa"
        },
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "100e9859651b35a3505cc278e9a98a076f79940b",
            "message": "Merge pull request #2 from WhisperBTC/patch-2\n\nUpdate foo",
            "modified": [
                "foo"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:27:28-08:00",
            "url": "https://github.com/moxie0/tempt/commit/100e9859651b35a3505cc278e9a98a076f79940b"
        }
    ],
    "compare": "https://github.com/moxie0/tempt/compare/6626766348ab...100e9859651b",
    "created": false,
    "deleted": false,
    "forced": false,
    "head_commit": {
        "added": [],
        "author": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "committer": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "distinct": true,
        "id": "100e9859651b35a3505cc278e9a98a076f79940b",
        "message": "Merge pull request #2 from WhisperBTC/patch-2\n\nUpdate foo",
        "modified": [
            "foo"
        ],
        "removed": [],
        "timestamp": "2013-12-14T11:27:28-08:00",
        "url": "https://github.com/moxie0/tempt/commit/100e9859651b35a3505cc278e9a98a076f79940b"
    },
    "pusher": {
        "email": "moxie@thoughtcrime.org",
        "name": "moxie0"
    },
    "ref": "refs/heads/master",
    "repository": {
        "created_at": 1386866024,
        "description": "test",
        "fork": false,
        "forks": 1,
        "has_downloads": true,
        "has_issues": true,
        "has_wiki": true,
        "id": 15141344,
        "master_branch": "master",
        "name": "tempt",
        "open_issues": 0,
        "owner": {
            "email": "moxie@thoughtcrime.org",
            "name": "moxie0"
        },
        "private": false,
        "pushed_at": 1387049248,
        "size": 216,
        "stargazers": 1,
        "url": "https://github.com/moxie0/test",
        "watchers": 1
    }
}


================================================
FILE: src/test/resources/payloads/invalid_repo.json
================================================
{
    "after": "100e9859651b35a3505cc278e9a98a076f79940b",
    "before": "6626766348ab245bdb3351989f753bd6e792524a",
    "commits": [
        {
            "added": [],
            "author": {
                "email": "info@whispersystems.org",
                "name": "WhisperBTC",
                "username": "WhisperBTC"
            },
            "committer": {
                "email": "info@whispersystems.org",
                "name": "WhisperBTC",
                "username": "WhisperBTC"
            },
            "distinct": true,
            "id": "fd7daeb1de6d72220b1313a7f1112d43885013aa",
            "message": "Update foo",
            "modified": [
                "foo"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:27:00-08:00",
            "url": "https://github.com/moxie0/tempt/commit/fd7daeb1de6d72220b1313a7f1112d43885013aa"
        },
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "100e9859651b35a3505cc278e9a98a076f79940b",
            "message": "Merge pull request #2 from WhisperBTC/patch-2\n\nUpdate foo",
            "modified": [
                "foo"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:27:28-08:00",
            "url": "https://github.com/moxie0/tempt/commit/100e9859651b35a3505cc278e9a98a076f79940b"
        }
    ],
    "compare": "https://github.com/moxie0/tempt/compare/6626766348ab...100e9859651b",
    "created": false,
    "deleted": false,
    "forced": false,
    "head_commit": {
        "added": [],
        "author": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "committer": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "distinct": true,
        "id": "100e9859651b35a3505cc278e9a98a076f79940b",
        "message": "Merge pull request #2 from WhisperBTC/patch-2\n\nUpdate foo",
        "modified": [
            "foo"
        ],
        "removed": [],
        "timestamp": "2013-12-14T11:27:28-08:00",
        "url": "https://github.com/moxie0/tempt/commit/100e9859651b35a3505cc278e9a98a076f79940b"
    },
    "pusher": {
        "email": "moxie@thoughtcrime.org",
        "name": "moxie0"
    },
    "ref": "refs/heads/master",
    "repository": {
        "created_at": 1386866024,
        "description": "test",
        "fork": false,
        "forks": 1,
        "has_downloads": true,
        "has_issues": true,
        "has_wiki": true,
        "id": 15141344,
        "master_branch": "master",
        "name": "tempt",
        "open_issues": 0,
        "owner": {
            "email": "moxie@thoughtcrime.org",
            "name": "moxie0"
        },
        "private": false,
        "pushed_at": 1387049248,
        "size": 216,
        "stargazers": 1,
        "url": "https://github.com/moxie0/tempt",
        "watchers": 1
    }
}


================================================
FILE: src/test/resources/payloads/multiple_commits_authors.json
================================================
{
    "after": "1481a2de7b2a7d02428ad93446ab166be7793fbb",
    "before": "17c497ccc7cca9c2f735aa07e9e3813060ce9a6a",
    "commits": [
        {
            "added": [],
            "author": {
                "email": "otherauthor@noway.biz",
                "name": "Garen Torikian",
                "username": "octokitty"
            },
            "committer": {
                "email": "lolwut@noway.biz",
                "name": "Garen Torikian",
                "username": "octokitty"
            },
            "distinct": true,
            "id": "c441029cf673f84c8b7db52d0a5944ee5c52ff89",
            "message": "Test",
            "modified": [
                "README.md"
            ],
            "removed": [],
            "timestamp": "2013-02-22T13:50:07-08:00",
            "url": "https://github.com/octokitty/testing/commit/c441029cf673f84c8b7db52d0a5944ee5c52ff89"
        },
        {
            "added": [],
            "author": {
                "email": "lolwut@noway.biz",
                "name": "Garen Torikian",
                "username": "octokitty"
            },
            "committer": {
                "email": "lolwut@noway.biz",
                "name": "Garen Torikian",
                "username": "octokitty"
            },
            "distinct": true,
            "id": "36c5f2243ed24de58284a96f2a643bed8c028658",
            "message": "This is me testing the windows client.",
            "modified": [
                "README.md"
            ],
            "removed": [],
            "timestamp": "2013-02-22T14:07:13-08:00",
            "url": "https://github.com/octokitty/testing/commit/36c5f2243ed24de58284a96f2a643bed8c028658"
        },
        {
            "added": [
                "words/madame-bovary.txt"
            ],
            "author": {
                "email": "lolwut@noway.biz",
                "name": "Garen Torikian",
                "username": "octokitty"
            },
            "committer": {
                "email": "lolwut@noway.biz",
                "name": "Garen Torikian",
                "username": "octokitty"
            },
            "distinct": true,
            "id": "1481a2de7b2a7d02428ad93446ab166be7793fbb",
            "message": "Rename madame-bovary.txt to words/madame-bovary.txt",
            "modified": [],
            "removed": [
                "madame-bovary.txt"
            ],
            "timestamp": "2013-03-12T08:14:29-07:00",
            "url": "https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb"
        }
    ],
    "compare": "https://github.com/octokitty/testing/compare/17c497ccc7cc...1481a2de7b2a",
    "created": false,
    "deleted": false,
    "forced": false,
    "head_commit": {
        "added": [
            "words/madame-bovary.txt"
        ],
        "author": {
            "email": "lolwut@noway.biz",
            "name": "Garen Torikian",
            "username": "octokitty"
        },
        "committer": {
            "email": "lolwut@noway.biz",
            "name": "Garen Torikian",
            "username": "octokitty"
        },
        "distinct": true,
        "id": "1481a2de7b2a7d02428ad93446ab166be7793fbb",
        "message": "Rename madame-bovary.txt to words/madame-bovary.txt",
        "modified": [],
        "removed": [
            "madame-bovary.txt"
        ],
        "timestamp": "2013-03-12T08:14:29-07:00",
        "url": "https://github.com/octokitty/testing/commit/1481a2de7b2a7d02428ad93446ab166be7793fbb"
    },
    "pusher": {
        "email": "lolwut@noway.biz",
        "name": "Garen Torikian"
    },
    "ref": "refs/heads/master",
    "repository": {
        "created_at": 1332977768,
        "description": "",
        "fork": false,
        "forks": 0,
        "has_downloads": true,
        "has_issues": true,
        "has_wiki": true,
        "homepage": "",
        "id": 3860742,
        "language": "Ruby",
        "master_branch": "master",
        "name": "testing",
        "open_issues": 2,
        "owner": {
            "email": "lolwut@noway.biz",
            "name": "octokitty"
        },
        "private": false,
        "pushed_at": 1363295520,
        "size": 2156,
        "stargazers": 1,
        "url": "https://github.com/moxie0/test",
        "watchers": 1
    }
}


================================================
FILE: src/test/resources/payloads/no_opt_in_commit.json
================================================
{
    "after": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
    "before": "1b141aa068165dd1ed376f483cd5fdc2c64f32b1",
    "commits": [
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "ba1b681c71db4fcd461954b1bf344bc6e29411e5",
            "message": "Update path",
            "modified": [
                "README.md"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:42:28-08:00",
            "url": "https://github.com/moxie0/optin/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5"
        },
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
            "message": "Merge branch 'master' of github.com:moxie0/tempt",
            "modified": [],
            "removed": [],
            "timestamp": "2013-12-14T11:42:44-08:00",
            "url": "https://github.com/moxie0/optin/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
        }
    ],
    "compare": "https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32",
    "created": false,
    "deleted": false,
    "forced": false,
    "head_commit": {
        "added": [],
        "author": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "committer": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "distinct": true,
        "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
        "message": "Merge branch 'master' of github.com:moxie0/optin",
        "modified": [],
        "removed": [],
        "timestamp": "2013-12-14T11:42:44-08:00",
        "url": "https://github.com/moxie0/optin/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
    },
    "pusher": {
        "email": "moxie@thoughtcrime.org",
        "name": "moxie0"
    },
    "ref": "refs/heads/master",
    "repository": {
        "created_at": 1386866024,
        "description": "test",
        "fork": false,
        "forks": 1,
        "has_downloads": true,
        "has_issues": true,
        "has_wiki": true,
        "id": 15141344,
        "master_branch": "master",
        "name": "tempt",
        "open_issues": 0,
        "owner": {
            "email": "moxie@thoughtcrime.org",
            "name": "moxie0"
        },
        "private": false,
        "pushed_at": 1387050173,
        "size": 216,
        "stargazers": 1,
        "url": "https://github.com/moxie0/optin",
        "watchers": 1
    }
}


================================================
FILE: src/test/resources/payloads/non_master_push.json
================================================
{
    "after": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
    "before": "1b141aa068165dd1ed376f483cd5fdc2c64f32b1",
    "commits": [
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "ba1b681c71db4fcd461954b1bf344bc6e29411e5",
            "message": "Update path",
            "modified": [
                "README.md"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:42:28-08:00",
            "url": "https://github.com/moxie0/tempt/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5"
        },
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
            "message": "Merge branch 'master' of github.com:moxie0/tempt",
            "modified": [],
            "removed": [],
            "timestamp": "2013-12-14T11:42:44-08:00",
            "url": "https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
        }
    ],
    "compare": "https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32",
    "created": false,
    "deleted": false,
    "forced": false,
    "head_commit": {
        "added": [],
        "author": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "committer": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "distinct": true,
        "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
        "message": "Merge branch 'master' of github.com:moxie0/tempt",
        "modified": [],
        "removed": [],
        "timestamp": "2013-12-14T11:42:44-08:00",
        "url": "https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
    },
    "pusher": {
        "email": "moxie@thoughtcrime.org",
        "name": "moxie0"
    },
    "ref": "refs/heads/lilia_doing_something",
    "repository": {
        "created_at": 1386866024,
        "description": "test",
        "fork": false,
        "forks": 1,
        "has_downloads": true,
        "has_issues": true,
        "has_wiki": true,
        "id": 15141344,
        "master_branch": "master",
        "name": "tempt",
        "open_issues": 0,
        "owner": {
            "email": "moxie@thoughtcrime.org",
            "name": "moxie0"
        },
        "private": false,
        "pushed_at": 1387050173,
        "size": 216,
        "stargazers": 1,
        "url": "https://github.com/moxie0/test",
        "watchers": 1
    }
}


================================================
FILE: src/test/resources/payloads/opt_in_commit.json
================================================
{
    "after": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
    "before": "1b141aa068165dd1ed376f483cd5fdc2c64f32b1",
    "commits": [
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "ba1b681c71db4fcd461954b1bf344bc6e29411e5",
            "message": "Update path MONEYMONEY",
            "modified": [
                "README.md"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:42:28-08:00",
            "url": "https://github.com/moxie0/optin/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5"
        },
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
            "message": "Merge branch 'master' of github.com:moxie0/tempt",
            "modified": [],
            "removed": [],
            "timestamp": "2013-12-14T11:42:44-08:00",
            "url": "https://github.com/moxie0/optin/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
        }
    ],
    "compare": "https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32",
    "created": false,
    "deleted": false,
    "forced": false,
    "head_commit": {
        "added": [],
        "author": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "committer": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "distinct": true,
        "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
        "message": "Merge branch 'master' of github.com:moxie0/optin",
        "modified": [],
        "removed": [],
        "timestamp": "2013-12-14T11:42:44-08:00",
        "url": "https://github.com/moxie0/optin/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
    },
    "pusher": {
        "email": "moxie@thoughtcrime.org",
        "name": "moxie0"
    },
    "ref": "refs/heads/master",
    "repository": {
        "created_at": 1386866024,
        "description": "test",
        "fork": false,
        "forks": 1,
        "has_downloads": true,
        "has_issues": true,
        "has_wiki": true,
        "id": 15141344,
        "master_branch": "master",
        "name": "tempt",
        "open_issues": 0,
        "owner": {
            "email": "moxie@thoughtcrime.org",
            "name": "moxie0"
        },
        "private": false,
        "pushed_at": 1387050173,
        "size": 216,
        "stargazers": 1,
        "url": "https://github.com/moxie0/optin",
        "watchers": 1
    }
}


================================================
FILE: src/test/resources/payloads/opt_out_commit.json
================================================
{
    "after": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
    "before": "1b141aa068165dd1ed376f483cd5fdc2c64f32b1",
    "commits": [
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "ba1b681c71db4fcd461954b1bf344bc6e29411e5",
            "message": "Update path FREEBIE",
            "modified": [
                "README.md"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:42:28-08:00",
            "url": "https://github.com/moxie0/tempt/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5"
        },
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
            "message": "Merge branch 'master' of github.com:moxie0/tempt FREEBIE",
            "modified": [],
            "removed": [],
            "timestamp": "2013-12-14T11:42:44-08:00",
            "url": "https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
        }
    ],
    "compare": "https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32",
    "created": false,
    "deleted": false,
    "forced": false,
    "head_commit": {
        "added": [],
        "author": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "committer": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "distinct": true,
        "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
        "message": "Merge branch 'master' of github.com:moxie0/tempt",
        "modified": [],
        "removed": [],
        "timestamp": "2013-12-14T11:42:44-08:00",
        "url": "https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
    },
    "pusher": {
        "email": "moxie@thoughtcrime.org",
        "name": "moxie0"
    },
    "ref": "refs/heads/master",
    "repository": {
        "created_at": 1386866024,
        "description": "test",
        "fork": false,
        "forks": 1,
        "has_downloads": true,
        "has_issues": true,
        "has_wiki": true,
        "id": 15141344,
        "master_branch": "master",
        "name": "tempt",
        "open_issues": 0,
        "owner": {
            "email": "moxie@thoughtcrime.org",
            "name": "moxie0"
        },
        "private": false,
        "pushed_at": 1387050173,
        "size": 216,
        "stargazers": 1,
        "url": "https://github.com/moxie0/test",
        "watchers": 1
    }
}


================================================
FILE: src/test/resources/payloads/transactions.json
================================================
{
    "current_user": {
        "id": "5011f33df8182b142400000e",
        "email": "user2@example.com",
        "name": "User Two"
    },
    "balance": {
        "amount": "50.00000000",
        "currency": "BTC"
    },
    "total_count": 2,
    "num_pages": 1,
    "current_page": 1,
    "transactions": [
        {
            "transaction": {
                "id": "5018f833f8182b129c00002f",
                "created_at": "2012-08-01T02:34:43-07:00",
                "amount": {
                    "amount": "-1.10000000",
                    "currency": "BTC"
                },
                "request": true,
                "status": "pending",
                "sender": {
                    "id": "5011f33df8182b142400000e",
                    "name": "User Two",
                    "email": "user2@example.com"
                },
                "recipient": {
                    "id": "5011f33df8182b142400000a",
                    "name": "User One",
                    "email": "user1@example.com"
                },
                "notes": "Commit payment:__moxie0__ https://github.com/WhisperSystems/BitHub/commit/88edf54e5b57c80ac05093a9be90965fd41291c2"
            }
        },
        {
            "transaction": {
                "id": "5018f833f8182b129c00002e",
                "created_at": "2012-08-01T02:36:43-07:00",
                "hsh": "9d6a7d1112c3db9de5315b421a5153d71413f5f752aff75bf504b77df4e646a3",
                "amount": {
                    "amount": "-1.00000000",
                    "currency": "BTC"
                },
                "request": false,
                "status": "complete",
                "sender": {
                    "id": "5011f33df8182b142400000e",
                    "name": "User Two",
                    "email": "user2@example.com"
                },
                "recipient_address": "37muSN5ZrukVTvyVh3mT5Zc5ew9L9CBare",
                "notes": "Commit payment:__moxie0__ https://github.com/WhisperSystems/BitHub/commit/88edf54e5b57c80ac05093a9be90965fd41291c2"
            }
        }
    ]
}

================================================
FILE: src/test/resources/payloads/valid_commit.json
================================================
{
    "after": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
    "before": "1b141aa068165dd1ed376f483cd5fdc2c64f32b1",
    "commits": [
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "ba1b681c71db4fcd461954b1bf344bc6e29411e5",
            "message": "Update path",
            "modified": [
                "README.md"
            ],
            "removed": [],
            "timestamp": "2013-12-14T11:42:28-08:00",
            "url": "https://github.com/moxie0/tempt/commit/ba1b681c71db4fcd461954b1bf344bc6e29411e5"
        },
        {
            "added": [],
            "author": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "committer": {
                "email": "moxie@thoughtcrime.org",
                "name": "Moxie Marlinspike",
                "username": "moxie0"
            },
            "distinct": true,
            "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
            "message": "Merge branch 'master' of github.com:moxie0/tempt",
            "modified": [],
            "removed": [],
            "timestamp": "2013-12-14T11:42:44-08:00",
            "url": "https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
        }
    ],
    "compare": "https://github.com/moxie0/tempt/compare/1b141aa06816...bcf09f8b4a32",
    "created": false,
    "deleted": false,
    "forced": false,
    "head_commit": {
        "added": [],
        "author": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "committer": {
            "email": "moxie@thoughtcrime.org",
            "name": "Moxie Marlinspike",
            "username": "moxie0"
        },
        "distinct": true,
        "id": "bcf09f8b4a32921114587e4814a3f0849aa9900f",
        "message": "Merge branch 'master' of github.com:moxie0/tempt",
        "modified": [],
        "removed": [],
        "timestamp": "2013-12-14T11:42:44-08:00",
        "url": "https://github.com/moxie0/tempt/commit/bcf09f8b4a32921114587e4814a3f0849aa9900f"
    },
    "pusher": {
        "email": "moxie@thoughtcrime.org",
        "name": "moxie0"
    },
    "ref": "refs/heads/master",
    "repository": {
        "created_at": 1386866024,
        "description": "test",
        "fork": false,
        "forks": 1,
        "has_downloads": true,
        "has_issues": true,
        "has_wiki": true,
        "id": 15141344,
        "master_branch": "master",
        "name": "tempt",
        "open_issues": 0,
        "owner": {
            "email": "moxie@thoughtcrime.org",
            "name": "moxie0"
        },
        "private": false,
        "pushed_at": 1387050173,
        "size": 216,
        "stargazers": 1,
        "url": "https://github.com/moxie0/test",
        "watchers": 1
    }
}


================================================
FILE: system.properties
================================================
java.runtime.version=1.7
Download .txt
gitextract_2c75r1ji/

├── .gitignore
├── .travis.yml
├── Procfile
├── README.md
├── assembly.xml
├── config/
│   └── sample.yml
├── design/
│   ├── badge-small.xcf
│   └── badge.xcf
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── org/
│   │   │       └── whispersystems/
│   │   │           └── bithub/
│   │   │               ├── BithubServerConfiguration.java
│   │   │               ├── BithubService.java
│   │   │               ├── auth/
│   │   │               │   └── GithubWebhookAuthenticator.java
│   │   │               ├── client/
│   │   │               │   ├── CoinbaseClient.java
│   │   │               │   ├── GithubClient.java
│   │   │               │   └── TransferFailedException.java
│   │   │               ├── config/
│   │   │               │   ├── BithubConfiguration.java
│   │   │               │   ├── CoinbaseConfiguration.java
│   │   │               │   ├── GithubConfiguration.java
│   │   │               │   ├── OrganizationConfiguration.java
│   │   │               │   ├── RepositoryConfiguration.java
│   │   │               │   └── WebhookConfiguration.java
│   │   │               ├── controllers/
│   │   │               │   ├── DashboardController.java
│   │   │               │   ├── GithubController.java
│   │   │               │   ├── StatusController.java
│   │   │               │   └── UnauthorizedHookException.java
│   │   │               ├── entities/
│   │   │               │   ├── Author.java
│   │   │               │   ├── Commit.java
│   │   │               │   ├── CommitComment.java
│   │   │               │   ├── Payment.java
│   │   │               │   ├── PushEvent.java
│   │   │               │   ├── Repositories.java
│   │   │               │   ├── Repository.java
│   │   │               │   ├── Transaction.java
│   │   │               │   └── Transactions.java
│   │   │               ├── mappers/
│   │   │               │   ├── IOExceptionMapper.java
│   │   │               │   └── UnauthorizedHookExceptionMapper.java
│   │   │               ├── storage/
│   │   │               │   ├── CacheManager.java
│   │   │               │   ├── CoinbaseTransactionParser.java
│   │   │               │   └── CurrentPayment.java
│   │   │               ├── util/
│   │   │               │   ├── AdvancedAtomicLong.java
│   │   │               │   └── Badge.java
│   │   │               └── views/
│   │   │                   ├── DashboardView.java
│   │   │                   └── TransactionsView.java
│   │   └── resources/
│   │       ├── banner.txt
│   │       └── org/
│   │           └── whispersystems/
│   │               └── bithub/
│   │                   └── views/
│   │                       ├── dashboard.mustache
│   │                       └── recent_transactions.mustache
│   └── test/
│       ├── java/
│       │   └── org/
│       │       └── whispersystems/
│       │           └── bithub/
│       │               └── tests/
│       │                   ├── controllers/
│       │                   │   ├── GithubControllerTest.java
│       │                   │   └── StatusControllerTest.java
│       │                   └── util/
│       │                       └── JsonHelper.java
│       └── resources/
│           └── payloads/
│               ├── invalid_origin.json
│               ├── invalid_repo.json
│               ├── multiple_commits_authors.json
│               ├── no_opt_in_commit.json
│               ├── non_master_push.json
│               ├── opt_in_commit.json
│               ├── opt_out_commit.json
│               ├── transactions.json
│               └── valid_commit.json
└── system.properties
Download .txt
SYMBOL INDEX (179 symbols across 37 files)

FILE: src/main/java/org/whispersystems/bithub/BithubServerConfiguration.java
  class BithubServerConfiguration (line 31) | public class BithubServerConfiguration extends Configuration {
    method getGithubConfiguration (line 53) | public GithubConfiguration getGithubConfiguration() {
    method getCoinbaseConfiguration (line 57) | public CoinbaseConfiguration getCoinbaseConfiguration() {
    method getBithubConfiguration (line 61) | public BithubConfiguration getBithubConfiguration() {
    method getOrganizationConfiguration (line 65) | public OrganizationConfiguration getOrganizationConfiguration() {

FILE: src/main/java/org/whispersystems/bithub/BithubService.java
  class BithubService (line 49) | public class BithubService extends Application<BithubServerConfiguration> {
    method initialize (line 51) | @Override
    method run (line 56) | @Override
    method main (line 90) | public static void main(String[] args) throws Exception {

FILE: src/main/java/org/whispersystems/bithub/auth/GithubWebhookAuthenticator.java
  class GithubWebhookAuthenticator (line 11) | public class GithubWebhookAuthenticator implements Authenticator<BasicCr...
    class Authentication (line 16) | public static class Authentication {
    method GithubWebhookAuthenticator (line 23) | public GithubWebhookAuthenticator(String username, String password) {
    method authenticate (line 27) | @Override

FILE: src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java
  class CoinbaseClient (line 39) | public class CoinbaseClient {
    method CoinbaseClient (line 43) | public CoinbaseClient(String apiKey, String apiSecret) {
    method getRecentTransactions (line 47) | public List<Transaction> getRecentTransactions()
    method getExchangeRate (line 53) | public BigDecimal getExchangeRate() throws IOException, CoinbaseExcept...
    method sendPayment (line 57) | public void sendPayment(Author author, BigDecimal amount, String url)
    method getAccountBalance (line 78) | public BigDecimal getAccountBalance() throws IOException, CoinbaseExce...

FILE: src/main/java/org/whispersystems/bithub/client/GithubClient.java
  class GithubClient (line 43) | public class GithubClient {
    method GithubClient (line 55) | public GithubClient(String user, String token) {
    method getCommitDescription (line 60) | public String getCommitDescription(String commitUrl) {
    method getRepository (line 76) | public Repository getRepository(String url) {
    method addCommitComment (line 91) | public void addCommitComment(Repository repository, Commit commit, Str...
    method getClientConfig (line 112) | private ClientConfig getClientConfig() {
    method getAuthorizationHeader (line 119) | private String getAuthorizationHeader(String user, String token) {

FILE: src/main/java/org/whispersystems/bithub/client/TransferFailedException.java
  class TransferFailedException (line 20) | public class TransferFailedException extends Exception {
    method TransferFailedException (line 22) | public TransferFailedException() {
    method TransferFailedException (line 26) | public TransferFailedException(Throwable e) {

FILE: src/main/java/org/whispersystems/bithub/config/BithubConfiguration.java
  class BithubConfiguration (line 27) | public class BithubConfiguration {
    method getPayoutRate (line 33) | public BigDecimal getPayoutRate() {

FILE: src/main/java/org/whispersystems/bithub/config/CoinbaseConfiguration.java
  class CoinbaseConfiguration (line 24) | public class CoinbaseConfiguration {
    method getApiKey (line 34) | public String getApiKey() {
    method getApiSecret (line 38) | public String getApiSecret() {

FILE: src/main/java/org/whispersystems/bithub/config/GithubConfiguration.java
  class GithubConfiguration (line 33) | public class GithubConfiguration {
    method getUser (line 56) | public String getUser() {
    method getToken (line 60) | public String getToken() {
    method getRepositories (line 64) | public List<RepositoryConfiguration> getRepositories() {
    method getWebhookConfiguration (line 81) | public WebhookConfiguration getWebhookConfiguration() {

FILE: src/main/java/org/whispersystems/bithub/config/OrganizationConfiguration.java
  class OrganizationConfiguration (line 9) | public class OrganizationConfiguration {
    method getName (line 19) | public String getName() {
    method getDonationUrl (line 23) | public URL getDonationUrl() {

FILE: src/main/java/org/whispersystems/bithub/config/RepositoryConfiguration.java
  class RepositoryConfiguration (line 6) | public class RepositoryConfiguration {
    method RepositoryConfiguration (line 16) | public RepositoryConfiguration(String url, String mode) {
    method RepositoryConfiguration (line 21) | public RepositoryConfiguration(String url) {
    method RepositoryConfiguration (line 25) | public RepositoryConfiguration() {}
    method getUrl (line 27) | public String getUrl() {
    method getMode (line 31) | public String getMode() {

FILE: src/main/java/org/whispersystems/bithub/config/WebhookConfiguration.java
  class WebhookConfiguration (line 6) | public class WebhookConfiguration {
    method getUsername (line 16) | public String getUsername() { return username; }
    method getPassword (line 18) | public String getPassword() { return password; }

FILE: src/main/java/org/whispersystems/bithub/controllers/DashboardController.java
  class DashboardController (line 12) | @Path("/")
    method DashboardController (line 21) | public DashboardController(String organizationName, String donationUrl,
    method getDashboard (line 29) | @Timed

FILE: src/main/java/org/whispersystems/bithub/controllers/GithubController.java
  class GithubController (line 63) | @Path("/v1/github")
    method GithubController (line 77) | public GithubController(List<RepositoryConfiguration> repositories,
    method handleCommits (line 93) | @Timed
    method sendPaymentsFor (line 127) | private void sendPaymentsFor(Repository repository, List<Commit> commits,
    method getEventFromPayload (line 148) | private PushEvent getEventFromPayload(String payload) throws IOExcepti...
    method getQualifyingCommits (line 158) | private List<Commit> getQualifyingCommits(PushEvent event, String defa...
    method isViableMessage (line 178) | private boolean isViableMessage(String message, String defaultMode) {
    method isViablePaymentAmount (line 186) | private boolean isViablePaymentAmount(BigDecimal payment) {
    method getCommitCommentStringForPayment (line 190) | private String getCommitCommentStringForPayment(BigDecimal payment, Bi...
    method authenticate (line 199) | private void authenticate(String clientIp) throws UnauthorizedHookExce...

FILE: src/main/java/org/whispersystems/bithub/controllers/StatusController.java
  class StatusController (line 50) | @Path("/v1/status")
    method StatusController (line 58) | public StatusController(CacheManager coinbaseManager,
    method getTransactions (line 66) | @Timed
    method getRepositories (line 81) | @Timed
    method getCurrentCommitPrice (line 96) | @Timed

FILE: src/main/java/org/whispersystems/bithub/controllers/UnauthorizedHookException.java
  class UnauthorizedHookException (line 20) | public class UnauthorizedHookException extends Throwable {
    method UnauthorizedHookException (line 21) | public UnauthorizedHookException(String s) {

FILE: src/main/java/org/whispersystems/bithub/entities/Author.java
  class Author (line 24) | @JsonIgnoreProperties(ignoreUnknown = true)
    method getName (line 37) | public String getName() {
    method getEmail (line 41) | public String getEmail() {
    method getUsername (line 45) | public String getUsername() {

FILE: src/main/java/org/whispersystems/bithub/entities/Commit.java
  class Commit (line 25) | @JsonIgnoreProperties(ignoreUnknown = true)
    method getSha (line 44) | public String getSha() {
    method getMessage (line 48) | public String getMessage() {
    method getAuthor (line 52) | public Author getAuthor() {
    method getUrl (line 56) | public String getUrl() {
    method isDistinct (line 60) | public boolean isDistinct() {

FILE: src/main/java/org/whispersystems/bithub/entities/CommitComment.java
  class CommitComment (line 22) | public class CommitComment {
    method CommitComment (line 27) | public CommitComment(String body) {

FILE: src/main/java/org/whispersystems/bithub/entities/Payment.java
  class Payment (line 22) | public class Payment {
    method Payment (line 26) | public Payment(String payment) {
    method getPayment (line 30) | public String getPayment() {

FILE: src/main/java/org/whispersystems/bithub/entities/PushEvent.java
  class PushEvent (line 26) | @JsonIgnoreProperties(ignoreUnknown = true)
    method getRepository (line 46) | public Repository getRepository() {
    method getHead (line 50) | public String getHead() {
    method getRef (line 54) | public String getRef() {
    method getSize (line 58) | public int getSize() {
    method getCommits (line 62) | public List<Commit> getCommits() {

FILE: src/main/java/org/whispersystems/bithub/entities/Repositories.java
  class Repositories (line 5) | public class Repositories {
    method Repositories (line 9) | public Repositories() {}
    method Repositories (line 11) | public Repositories(List<Repository> repositories) {

FILE: src/main/java/org/whispersystems/bithub/entities/Repository.java
  class Repository (line 26) | @JsonIgnoreProperties(ignoreUnknown = true)
    method Repository (line 44) | public Repository() {}
    method Repository (line 46) | public Repository(String url) {
    method getOwner (line 50) | public Author getOwner() {
    method getName (line 54) | public String getName() {
    method getUrl (line 58) | public String getUrl() {
    method getDescription (line 62) | public String getDescription() {

FILE: src/main/java/org/whispersystems/bithub/entities/Transaction.java
  class Transaction (line 5) | public class Transaction {
    method Transaction (line 25) | public Transaction() {}
    method Transaction (line 27) | public Transaction(String destination, String amount, String commitUrl,
    method getDestination (line 38) | public String getDestination() {
    method getAmount (line 42) | public String getAmount() {
    method getCommitUrl (line 46) | public String getCommitUrl() {
    method getCommitSha (line 50) | public String getCommitSha() {
    method getTimestamp (line 54) | public String getTimestamp() {
    method getDescription (line 58) | public String getDescription() {

FILE: src/main/java/org/whispersystems/bithub/entities/Transactions.java
  class Transactions (line 8) | public class Transactions {
    method Transactions (line 13) | public Transactions() {}
    method Transactions (line 15) | public Transactions(List<Transaction> transactions) {

FILE: src/main/java/org/whispersystems/bithub/mappers/IOExceptionMapper.java
  class IOExceptionMapper (line 28) | @Provider
    method toResponse (line 33) | @Override

FILE: src/main/java/org/whispersystems/bithub/mappers/UnauthorizedHookExceptionMapper.java
  class UnauthorizedHookExceptionMapper (line 28) | @Provider
    method toResponse (line 33) | @Override

FILE: src/main/java/org/whispersystems/bithub/storage/CacheManager.java
  class CacheManager (line 27) | public class CacheManager implements Managed {
    method CacheManager (line 43) | public CacheManager(CoinbaseClient coinbaseClient,
    method start (line 54) | @Override
    method stop (line 63) | @Override
    method getRecentTransactions (line 68) | public List<Transaction> getRecentTransactions() {
    method getCurrentPaymentAmount (line 72) | public CurrentPayment getCurrentPaymentAmount() {
    method getRepositories (line 76) | public List<Repository> getRepositories() {
    method initializeUpdates (line 80) | public void initializeUpdates(final CoinbaseClient coinbaseClient,
    method createRepositories (line 104) | private List<Repository> createRepositories(GithubClient githubClient,
    method createCurrentPaymentForBalance (line 116) | private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient c...
    method createRecentTransactions (line 130) | private List<Transaction> createRecentTransactions(CoinbaseClient coin...
    method isSentTransaction (line 161) | private boolean isSentTransaction(com.coinbase.api.entity.Transaction ...

FILE: src/main/java/org/whispersystems/bithub/storage/CoinbaseTransactionParser.java
  class CoinbaseTransactionParser (line 13) | public class CoinbaseTransactionParser {
    method CoinbaseTransactionParser (line 17) | public CoinbaseTransactionParser(Transaction coinbaseTransaction) {
    method parseAmountInDollars (line 21) | public String parseAmountInDollars(BigDecimal exchangeRate) {
    method parseTimestamp (line 28) | public String parseTimestamp() throws ParseException {
    method parseDestinationFromMessage (line 34) | public String parseDestinationFromMessage() {
    method parseUrlFromMessage (line 51) | public String parseUrlFromMessage() throws ParseException {
    method parseShaFromUrl (line 58) | public String parseShaFromUrl(String url) throws ParseException {

FILE: src/main/java/org/whispersystems/bithub/storage/CurrentPayment.java
  class CurrentPayment (line 5) | public class CurrentPayment {
    method CurrentPayment (line 11) | protected CurrentPayment(byte[] badge, byte[] smallBadge, Payment enti...
    method getBadge (line 17) | public byte[] getBadge() {
    method getSmallBadge (line 21) | public byte[] getSmallBadge() {
    method getEntity (line 25) | public Payment getEntity() {

FILE: src/main/java/org/whispersystems/bithub/util/AdvancedAtomicLong.java
  class AdvancedAtomicLong (line 22) | public class AdvancedAtomicLong extends AtomicLong {
    method AdvancedAtomicLong (line 24) | public AdvancedAtomicLong(long initial) {
    method setIfGreater (line 28) | public boolean setIfGreater(long compare, long update) {

FILE: src/main/java/org/whispersystems/bithub/util/Badge.java
  class Badge (line 29) | public class Badge {
    method createFor (line 31) | public static byte[] createFor(String price) throws IOException {
    method createSmallFor (line 48) | public static byte[] createSmallFor(String price) throws IOException {

FILE: src/main/java/org/whispersystems/bithub/views/DashboardView.java
  class DashboardView (line 11) | public class DashboardView extends View {
    method DashboardView (line 19) | public DashboardView(String organizationName, String donationUrl,
    method getPayment (line 32) | public String getPayment() {
    method getOrganizationName (line 36) | public String getOrganizationName() {
    method getDonationUrl (line 40) | public String getDonationUrl() {
    method getRepositories (line 44) | public List<Repository> getRepositories() {
    method getTransactions (line 48) | public List<Transaction> getTransactions() {
    method getRepositoriesCount (line 52) | public String getRepositoriesCount() {

FILE: src/main/java/org/whispersystems/bithub/views/TransactionsView.java
  class TransactionsView (line 31) | public class TransactionsView extends View {
    method TransactionsView (line 35) | public TransactionsView(List<Transaction> transactions) {
    method getTransactions (line 40) | public List<Transaction> getTransactions() {

FILE: src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java
  class GithubControllerTest (line 50) | public class GithubControllerTest {
    method setup (line 79) | @Before
    method payload (line 85) | protected String payload(String path) {
    method testInvalidRepository (line 91) | @Test
    method testInvalidOrigin (line 105) | @Test
    method testMissingAuth (line 119) | @Test
    method testInvalidAuthUser (line 132) | @Test
    method testInvalidAuthPassword (line 146) | @Test
    method testOptOutCommit (line 160) | @Test
    method testValidCommit (line 176) | @Test
    method testNonMaster (line 192) | @Test
    method testValidMultipleCommitsMultipleAuthors (line 208) | @Test
    method testOptInCommit (line 225) | @Test
    method testNoOptInCommit (line 241) | @Test

FILE: src/test/java/org/whispersystems/bithub/tests/controllers/StatusControllerTest.java
  class StatusControllerTest (line 24) | public class StatusControllerTest {
    method testTransactionsJson (line 74) | @Test

FILE: src/test/java/org/whispersystems/bithub/tests/util/JsonHelper.java
  class JsonHelper (line 13) | public class JsonHelper {
    method asJson (line 17) | public static String asJson(Object object) throws JsonProcessingExcept...
    method fromJson (line 21) | public static <T> T fromJson(String value, Class<T> clazz) throws IOEx...
    method jsonFixture (line 25) | public static String jsonFixture(String filename) throws IOException {
Condensed preview — 59 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (140K chars).
[
  {
    "path": ".gitignore",
    "chars": 42,
    "preview": ".idea\n*.iml\ntarget\nconfig/production.yml\n\n"
  },
  {
    "path": ".travis.yml",
    "chars": 15,
    "preview": "language: java\n"
  },
  {
    "path": "Procfile",
    "chars": 527,
    "preview": "web:    java $JAVA_OPTS -Ddw.server.type=simple -Ddw.server.applicationContextPath=/ -Ddw.server.connector.type=http -Dd"
  },
  {
    "path": "README.md",
    "chars": 1901,
    "preview": "BitHub\n=================\n\n[![Build Status](https://travis-ci.org/WhisperSystems/BitHub.png?branch=master)](https://travi"
  },
  {
    "path": "assembly.xml",
    "chars": 980,
    "preview": "<assembly xmlns=\"http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0\"\n          xmlns:xsi=\"http://www.w"
  },
  {
    "path": "config/sample.yml",
    "chars": 830,
    "preview": "organization:\n  name: # Your name (eg. Open Whisper Systems)\n  donationUrl: # A Coinbase link where you can receive dona"
  },
  {
    "path": "pom.xml",
    "chars": 7667,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/BithubServerConfiguration.java",
    "chars": 1921,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/BithubService.java",
    "chars": 4604,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/auth/GithubWebhookAuthenticator.java",
    "chars": 989,
    "preview": "package org.whispersystems.bithub.auth;\n\nimport com.google.common.base.Optional;\n\nimport io.dropwizard.auth.Authenticato"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java",
    "chars": 2967,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/client/GithubClient.java",
    "chars": 4976,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/client/TransferFailedException.java",
    "chars": 945,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/BithubConfiguration.java",
    "chars": 1083,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/CoinbaseConfiguration.java",
    "chars": 1137,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/GithubConfiguration.java",
    "chars": 2272,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/OrganizationConfiguration.java",
    "chars": 463,
    "preview": "package org.whispersystems.bithub.config;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.va"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/RepositoryConfiguration.java",
    "chars": 627,
    "preview": "package org.whispersystems.bithub.config;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.va"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/config/WebhookConfiguration.java",
    "chars": 416,
    "preview": "package org.whispersystems.bithub.config;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport org.hibernate.va"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/controllers/DashboardController.java",
    "chars": 1122,
    "preview": "package org.whispersystems.bithub.controllers;\n\nimport com.codahale.metrics.annotation.Timed;\nimport org.whispersystems."
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/controllers/GithubController.java",
    "chars": 7735,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/controllers/StatusController.java",
    "chars": 3856,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/controllers/UnauthorizedHookException.java",
    "chars": 895,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Author.java",
    "chars": 1301,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Commit.java",
    "chars": 1482,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/CommitComment.java",
    "chars": 958,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Payment.java",
    "chars": 1012,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/PushEvent.java",
    "chars": 1518,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Repositories.java",
    "chars": 267,
    "preview": "package org.whispersystems.bithub.entities;\n\nimport java.util.List;\n\npublic class Repositories {\n\n  public List<Reposito"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Repository.java",
    "chars": 1555,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Transaction.java",
    "chars": 1155,
    "preview": "package org.whispersystems.bithub.entities;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\npublic class Transac"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/entities/Transactions.java",
    "chars": 342,
    "preview": "package org.whispersystems.bithub.entities;\n\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\n\nimport java.util.Li"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/mappers/IOExceptionMapper.java",
    "chars": 1272,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/mappers/UnauthorizedHookExceptionMapper.java",
    "chars": 1357,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/storage/CacheManager.java",
    "chars": 6608,
    "preview": "package org.whispersystems.bithub.storage;\n\nimport com.coinbase.api.exception.CoinbaseException;\nimport org.slf4j.Logger"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/storage/CoinbaseTransactionParser.java",
    "chars": 2155,
    "preview": "package org.whispersystems.bithub.storage;\n\nimport com.coinbase.api.entity.Transaction;\nimport org.apache.commons.lang3."
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/storage/CurrentPayment.java",
    "chars": 574,
    "preview": "package org.whispersystems.bithub.storage;\n\nimport org.whispersystems.bithub.entities.Payment;\n\npublic class CurrentPaym"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/util/AdvancedAtomicLong.java",
    "chars": 1206,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/util/Badge.java",
    "chars": 2540,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/views/DashboardView.java",
    "chars": 1519,
    "preview": "package org.whispersystems.bithub.views;\n\nimport org.whispersystems.bithub.entities.Repository;\nimport org.whispersystem"
  },
  {
    "path": "src/main/java/org/whispersystems/bithub/views/TransactionsView.java",
    "chars": 1291,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/main/resources/banner.txt",
    "chars": 520,
    "preview": "888888b.   d8b 888    888    888          888      \n888  \"88b  Y8P 888    888    888          888      \n888  .88P      8"
  },
  {
    "path": "src/main/resources/org/whispersystems/bithub/views/dashboard.mustache",
    "chars": 5322,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=ed"
  },
  {
    "path": "src/main/resources/org/whispersystems/bithub/views/recent_transactions.mustache",
    "chars": 1393,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta http-equiv=\"Content-Security-Policy\" content=\"script-src none\">\n    <m"
  },
  {
    "path": "src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java",
    "chars": 11635,
    "preview": "/**\n * Copyright (C) 2013 Open WhisperSystems\n *\n * This program is free software: you can redistribute it and/or modify"
  },
  {
    "path": "src/test/java/org/whispersystems/bithub/tests/controllers/StatusControllerTest.java",
    "chars": 3514,
    "preview": "package org.whispersystems.bithub.tests.controllers;\n\nimport com.coinbase.api.ObjectMapperProvider;\nimport com.coinbase."
  },
  {
    "path": "src/test/java/org/whispersystems/bithub/tests/util/JsonHelper.java",
    "chars": 904,
    "preview": "package org.whispersystems.bithub.tests.util;\n\n\nimport com.fasterxml.jackson.core.JsonParseException;\nimport com.fasterx"
  },
  {
    "path": "src/test/resources/payloads/invalid_origin.json",
    "chars": 3349,
    "preview": "{\n    \"after\": \"100e9859651b35a3505cc278e9a98a076f79940b\",\n    \"before\": \"6626766348ab245bdb3351989f753bd6e792524a\",\n   "
  },
  {
    "path": "src/test/resources/payloads/invalid_repo.json",
    "chars": 3350,
    "preview": "{\n    \"after\": \"100e9859651b35a3505cc278e9a98a076f79940b\",\n    \"before\": \"6626766348ab245bdb3351989f753bd6e792524a\",\n   "
  },
  {
    "path": "src/test/resources/payloads/multiple_commits_authors.json",
    "chars": 4299,
    "preview": "{\n    \"after\": \"1481a2de7b2a7d02428ad93446ab166be7793fbb\",\n    \"before\": \"17c497ccc7cca9c2f735aa07e9e3813060ce9a6a\",\n   "
  },
  {
    "path": "src/test/resources/payloads/no_opt_in_commit.json",
    "chars": 3277,
    "preview": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n   "
  },
  {
    "path": "src/test/resources/payloads/non_master_push.json",
    "chars": 3291,
    "preview": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n   "
  },
  {
    "path": "src/test/resources/payloads/opt_in_commit.json",
    "chars": 3288,
    "preview": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n   "
  },
  {
    "path": "src/test/resources/payloads/opt_out_commit.json",
    "chars": 3292,
    "preview": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n   "
  },
  {
    "path": "src/test/resources/payloads/transactions.json",
    "chars": 2085,
    "preview": "{\n    \"current_user\": {\n        \"id\": \"5011f33df8182b142400000e\",\n        \"email\": \"user2@example.com\",\n        \"name\": "
  },
  {
    "path": "src/test/resources/payloads/valid_commit.json",
    "chars": 3276,
    "preview": "{\n    \"after\": \"bcf09f8b4a32921114587e4814a3f0849aa9900f\",\n    \"before\": \"1b141aa068165dd1ed376f483cd5fdc2c64f32b1\",\n   "
  },
  {
    "path": "system.properties",
    "chars": 25,
    "preview": "java.runtime.version=1.7\n"
  }
]

// ... and 2 more files (download for full content)

About this extraction

This page contains the full source code of the WhisperSystems/BitHub GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 59 files (125.9 KB), approximately 32.4k tokens, and a symbol index with 179 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!