Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/main/java/org/kohsuke/github/GHGraphQLException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.kohsuke.github;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* A GraphQL request that GitHub answered with errors.
*/
public class GHGraphQLException extends GHIOException {

/** The Constant serialVersionUID. */
static final long serialVersionUID = 1L;

/** The messages of the GraphQL errors. */
private final List<String> errorMessages;

GHGraphQLException(List<String> errorMessages) {
super("GraphQL request failed by:" + errorMessages);
this.errorMessages = Collections.unmodifiableList(new ArrayList<>(errorMessages));
}

/**
* Gets the messages of the GraphQL errors.
*
* @return the error messages
*/
public List<String> getErrorMessages() {
return errorMessages;
}
}
98 changes: 98 additions & 0 deletions src/main/java/org/kohsuke/github/GHPerson.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
Expand Down Expand Up @@ -279,6 +280,18 @@ public Instant getUpdatedAt() throws IOException {
return super.getUpdatedAt();
}

/**
* Checks if this user or organization has a GitHub Sponsors listing.
*
* @return true if there is a sponsors listing
* @throws IOException
* the io exception
*/
@BetaApi
public boolean hasSponsorsListing() throws IOException {
return GHSponsors.hasSponsorsListing(this);
}

/**
* Gets the siteAdmin field.
*
Expand All @@ -291,6 +304,34 @@ public boolean isSiteAdmin() throws IOException {
return siteAdmin;
}

/**
* Checks if the given account is currently sponsoring this user or organization.
*
* @param sponsorLogin
* the login of the potential sponsor
* @return true if the account has an active sponsorship of this user or organization
* @throws IOException
* the io exception
*/
@BetaApi
public boolean isSponsoredBy(String sponsorLogin) throws IOException {
return GHSponsors.isSponsoredBy(this, sponsorLogin);
}

/**
* Checks if this user or organization currently sponsors the given account.
*
* @param sponsorableLogin
* the login of the user or organization that may be sponsored
* @return true if this user or organization has an active sponsorship of the account
* @throws IOException
* the io exception
*/
@BetaApi
public boolean isSponsoring(String sponsorableLogin) throws IOException {
return GHSponsors.isSponsoring(this, sponsorableLogin);
}

/**
* Lists events for an organization or an user.
*
Expand Down Expand Up @@ -328,6 +369,63 @@ public PagedIterable<GHRepository> listRepositories(final int pageSize) {
return listRepositories().withPageSize(pageSize);
}

/**
* Lists the active, public sponsorships this user or organization receives.
*
* @return an unmodifiable list of active public sponsorships received
* @throws IOException
* the io exception
*/
@BetaApi
public List<GHSponsorship> listSponsorshipsAsMaintainer() throws IOException {
return listSponsorshipsAsMaintainer(true, false);
}

/**
* Lists the sponsorships this user or organization receives.
*
* @param activeOnly
* if true, only active sponsorships; if false, cancelled sponsorships are included
* @param includePrivate
* if true, private sponsorships are included. Their sponsor is only revealed to the maintainer or an
* admin of the organization; GitHub App installation tokens cannot read them at all and the request
* fails with "Resource not accessible by integration"
* @return an unmodifiable list of sponsorships received
* @throws IOException
* the io exception
*/
@BetaApi
public List<GHSponsorship> listSponsorshipsAsMaintainer(boolean activeOnly, boolean includePrivate)
throws IOException {
return GHSponsors.listSponsorshipsAsMaintainer(this, activeOnly, includePrivate);
}

/**
* Lists the active sponsorships this user or organization gives.
*
* @return an unmodifiable list of active sponsorships given
* @throws IOException
* the io exception
*/
@BetaApi
public List<GHSponsorship> listSponsorshipsAsSponsor() throws IOException {
return listSponsorshipsAsSponsor(true);
}

/**
* Lists the sponsorships this user or organization gives.
*
* @param activeOnly
* if true, only active sponsorships; if false, cancelled sponsorships are included
* @return an unmodifiable list of sponsorships given
* @throws IOException
* the io exception
*/
@BetaApi
public List<GHSponsorship> listSponsorshipsAsSponsor(boolean activeOnly) throws IOException {
return GHSponsors.listSponsorshipsAsSponsor(this, activeOnly);
}

/**
* Fully populate the data by retrieving missing data.
* <p>
Expand Down
90 changes: 90 additions & 0 deletions src/main/java/org/kohsuke/github/GHSponsors.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.kohsuke.github;

import org.kohsuke.github.internal.graphql.response.GHGraphQLResponse;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
* GraphQL queries behind the GitHub Sponsors methods of {@link GHPerson}.
*/
class GHSponsors {

private static final String SPONSORSHIP_PAGE = "{ pageInfo { hasNextPage endCursor } nodes {"
+ " sponsorEntity { ... on User { login } ... on Organization { login } }"
+ " sponsorable { ... on User { login } ... on Organization { login } }"
+ " tier { id name description monthlyPriceInDollars monthlyPriceInCents isOneTime isCustomAmount }"
+ " isActive isOneTimePayment privacyLevel createdAt } }";

@SuppressWarnings("unchecked")
private static Map<String, Object> asMap(Object value) {
return (Map<String, Object>) value;
}

private static String escape(String value) {
return value.replace("\\", "\\\\").replace("\"", "\\\"");
}

private static List<GHSponsorship> listSponsorships(GHPerson person, String connection, String arguments)
throws IOException {
List<GHSponsorship> sponsorships = new ArrayList<>();
String cursor = null;
do {
String after = cursor == null ? "" : ", after: \"" + escape(cursor) + "\"";
Map<String, Object> page = asMap(
query(person, connection + "(" + arguments + after + ") " + SPONSORSHIP_PAGE).get(connection));
GHSponsorship[] nodes = GitHubClient.getMappingObjectReader(person.root())
.forType(GHSponsorship[].class)
.readValue(GitHubClient.getMappingObjectWriter().writeValueAsString(page.get("nodes")));
Collections.addAll(sponsorships, nodes);
Map<String, Object> pageInfo = asMap(page.get("pageInfo"));
cursor = (boolean) pageInfo.get("hasNextPage") ? (String) pageInfo.get("endCursor") : null;
} while (cursor != null);
return Collections.unmodifiableList(sponsorships);
}

private static Map<String, Object> query(GHPerson person, String fields) throws IOException {
boolean organization = person instanceof GHOrganization || "Organization".equals(person.getType());
String type = organization ? "organization" : "user";
String query = "{ " + type + "(login: \"" + escape(person.login) + "\") { " + fields + " } }";
Object data = person.root().createGraphQLRequest(query).fetchGraphQL(GHGraphQLResponse.ObjectResponse.class);
return asMap(asMap(data).get(type));
}

static boolean hasSponsorsListing(GHPerson person) throws IOException {
return (boolean) query(person, "hasSponsorsListing").get("hasSponsorsListing");
}

static boolean isSponsoredBy(GHPerson person, String sponsorLogin) throws IOException {
return (boolean) query(person, "isSponsoredBy(accountLogin: \"" + escape(sponsorLogin) + "\")")
.get("isSponsoredBy");
}

static boolean isSponsoring(GHPerson person, String sponsorableLogin) throws IOException {
String query = "{ repositoryOwner(login: \"" + escape(sponsorableLogin)
+ "\") { ... on Sponsorable { isSponsoredBy(accountLogin: \"" + escape(person.login) + "\") } } }";
Object data = person.root().createGraphQLRequest(query).fetchGraphQL(GHGraphQLResponse.ObjectResponse.class);
Map<String, Object> owner = asMap(asMap(data).get("repositoryOwner"));
if (owner == null) {
throw new GHFileNotFoundException(sponsorableLogin);
}
return (boolean) owner.get("isSponsoredBy");
}

static List<GHSponsorship> listSponsorshipsAsMaintainer(GHPerson person, boolean activeOnly, boolean includePrivate)
throws IOException {
return listSponsorships(person,
"sponsorshipsAsMaintainer",
"first: 100, activeOnly: " + activeOnly + ", includePrivate: " + includePrivate);
}

static List<GHSponsorship> listSponsorshipsAsSponsor(GHPerson person, boolean activeOnly) throws IOException {
return listSponsorships(person, "sponsorshipsAsSponsor", "first: 100, activeOnly: " + activeOnly);
}

private GHSponsors() {
}
}
90 changes: 90 additions & 0 deletions src/main/java/org/kohsuke/github/GHSponsorsTier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.kohsuke.github;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
* A GitHub Sponsors tier.
*
* @see <a href="https://docs.github.com/en/graphql/reference/objects#sponsorstier">SponsorsTier</a>
*/
@BetaApi
@JsonNaming(PropertyNamingStrategies.LowerCamelCaseStrategy.class)
@SuppressFBWarnings(value = { "UWF_UNWRITTEN_FIELD", "NP_UNWRITTEN_FIELD" }, justification = "JSON API")
public class GHSponsorsTier {

private String description;
private String id;
private boolean isCustomAmount;
private boolean isOneTime;
private int monthlyPriceInCents;
private int monthlyPriceInDollars;
private String name;

GHSponsorsTier() {
}

/**
* Gets the description of this tier.
*
* @return the description
*/
public String getDescription() {
return description;
}

/**
* Gets the node id of this tier.
*
* @return the node id
*/
public String getId() {
return id;
}

/**
* Gets the monthly price in cents.
*
* @return the monthly price in cents
*/
public int getMonthlyPriceInCents() {
return monthlyPriceInCents;
}

/**
* Gets the monthly price in dollars.
*
* @return the monthly price in dollars
*/
public int getMonthlyPriceInDollars() {
return monthlyPriceInDollars;
}

/**
* Gets the name of this tier.
*
* @return the name
*/
public String getName() {
return name;
}

/**
* Whether this tier lets the sponsor choose the amount.
*
* @return true if this is a custom amount tier
*/
public boolean isCustomAmount() {
return isCustomAmount;
}

/**
* Whether this tier is a one-time payment.
*
* @return true if this is a one-time tier
*/
public boolean isOneTime() {
return isOneTime;
}
}
Loading
Loading