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
=================
[](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:
=================
[](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
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
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[](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.