diff options
author | 2025-04-09 13:24:53 -0700 | |
---|---|---|
committer | 2025-04-11 00:15:18 -0700 | |
commit | f277f41d83d450a1f28d350bb7d6da075d1d2741 (patch) | |
tree | e197a7de7e26b47cec57a46acdde4b6c74889ad5 /src/github.c | |
download | github-mirror-f277f41d83d450a1f28d350bb7d6da075d1d2741.tar.gz github-mirror-f277f41d83d450a1f28d350bb7d6da075d1d2741.tar.zst github-mirror-f277f41d83d450a1f28d350bb7d6da075d1d2741.zip |
Initial Commit
Add cmake github workflow
Install dependencies in gh action
Fix missing directory
Fix compiler errors
Fix group name to gid conversion
Force cjson to statically link
Add header guards to query headers
Add install and cpack to CMakeLists.txt
Add config search and CLI arg parsing
Improve docs for git.c
Fix program continuing on -h flag
Diffstat (limited to 'src/github.c')
-rw-r--r-- | src/github.c | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/src/github.c b/src/github.c new file mode 100644 index 0000000..18bd992 --- /dev/null +++ b/src/github.c @@ -0,0 +1,290 @@ +// +// Created by Anshul Gupta on 4/4/25. +// + +#include "github.h" + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> + +#include <cJSON.h> +#include <curl/curl.h> + +#include "queries/identity.h" +#include "queries/list_repos.h" + +#include "buffer.h" +#include "github_types.h" + +struct gh_impl { + struct github_ctx ctx; + CURL *curl; +}; + +static CURLcode gh_impl_send(const struct gh_impl *client, const char *query, + cJSON *args, buffer_t *buf); + +static int gh_handle_error(const cJSON *root); + +github_client *github_client_new(const struct github_ctx ctx) +{ + struct gh_impl *c = malloc(sizeof(*c)); + if (!c) + return NULL; + + c->ctx.endpoint = strdup(ctx.endpoint); + c->ctx.token = strdup(ctx.token); + c->ctx.user_agent = strdup(ctx.user_agent); + c->curl = curl_easy_init(); + return c; +} + +github_client *github_client_dup(github_client *client) +{ + struct gh_impl *c = client; + if (!c) + return NULL; + + struct gh_impl *dup = malloc(sizeof(*dup)); + if (!dup) + return NULL; + + dup->ctx.endpoint = strdup(c->ctx.endpoint); + dup->ctx.token = strdup(c->ctx.token); + dup->ctx.user_agent = strdup(c->ctx.user_agent); + dup->curl = curl_easy_duphandle(c->curl); + + return dup; +} + +void github_client_free(github_client *client) +{ + struct gh_impl *c = client; + if (!c) + return; + curl_easy_cleanup(c->curl); + free((char *) c->ctx.endpoint); + free((char *) c->ctx.token); + free((char *) c->ctx.user_agent); + free(c); +} + +char *github_client_identity(const github_client *client) +{ + char *login = NULL; + const struct gh_impl *c = client; + buffer_t buf = buffer_new(4096); + + const CURLcode ret = gh_impl_send(c, identity, NULL, &buf); + if (ret != CURLE_OK) { + fprintf(stderr, "Failed to send request: %s\n", + curl_easy_strerror(ret)); + goto fail; + } + + // Parse the response + cJSON *root = cJSON_Parse((const char *) buf.data); + if (!root) { + const char *err = cJSON_GetErrorPtr(); + if (err) + fprintf(stderr, "Error parsing response: %s\n", err); + goto fail; + } + + // Check for errors + if (gh_handle_error(root) < 0) + goto fail1; + + // Get login from json + login = identity_from_json(root); + +fail1: + cJSON_Delete(root); +fail: + buffer_free(buf); + return login; +} + +int github_client_list_user_repos(const github_client *client, + const char *username, const char *after, + struct list_repos_res *res) +{ + int status = 0; + const struct gh_impl *c = client; + buffer_t buf = buffer_new(4096); + + cJSON *args = cJSON_CreateObject(); + if (!args) { + status = -1; + goto end; + } + cJSON_AddItemToObject(args, "username", cJSON_CreateString(username)); + cJSON_AddItemToObject(args, "after", cJSON_CreateString(after)); + + const CURLcode ret = gh_impl_send(c, list_repos, args, &buf); + if (ret != CURLE_OK) { + fprintf(stderr, "Failed to send request: %s\n", + curl_easy_strerror(ret)); + status = -1; + goto end; + } + + // Parse the response + cJSON *root = cJSON_Parse((const char *) buf.data); + if (!root) { + const char *err = cJSON_GetErrorPtr(); + if (err) + fprintf(stderr, "Error parsing response: %s\n", err); + status = -1; + goto end; + } + + // Check for errors + if (gh_handle_error(root) < 0) { + status = -1; + goto end; + } + + // Convert json to struct + if (list_repos_from_json(root, res) < 0) { + fprintf(stderr, "Failed to parse response\n"); + status = -1; + } + +end: + buffer_free(buf); + return status; +} + +/** + * Wraps the query in a JSON object with a "query" key and an optional + * "variables" key + * @param query GraphQL query + * @param args GraphQL arguments + * @return JSON string + */ +static char *wrap_query(const char *query, cJSON *args) +{ + cJSON *root = NULL, *query_str = NULL; + char *str = NULL; + + root = cJSON_CreateObject(); + if (!root) + return NULL; + + query_str = cJSON_CreateString(query); + if (!query_str) + goto end; + + // Transfer ownership of the string to root + cJSON_AddItemToObject(root, "query", query_str); + + // Add the args object if it exists + if (args) + cJSON_AddItemToObject(root, "variables", args); + + str = cJSON_Print(root); +end: + cJSON_Delete(root); + return str; +} + +static size_t write_data(const void *ptr, const size_t size, size_t nmemb, + void *stream) +{ + (void) size; // unused + + buffer_t *buf = stream; + buffer_append(buf, ptr, nmemb); + return nmemb; +} + +/** + * Send a GraphQL query to the GitHub API + * @param client Github Client + * @param query GraphQL query + * @return CURLcode + */ +static CURLcode gh_impl_send(const struct gh_impl *client, const char *query, + cJSON *args, buffer_t *buf) +{ + struct curl_slist *headers = NULL; + char auth[1024]; + + // Set the URL + curl_easy_setopt(client->curl, CURLOPT_URL, client->ctx.endpoint); + + // Set the authorization header + snprintf(auth, sizeof(auth), "Authorization: bearer %s", + client->ctx.token); + headers = curl_slist_append(headers, auth); + // Set the content type to JSON + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(client->curl, CURLOPT_HTTPHEADER, headers); + + // Set user agent + curl_easy_setopt(client->curl, CURLOPT_USERAGENT, + client->ctx.user_agent); + + // Set the request type to POST + curl_easy_setopt(client->curl, CURLOPT_CUSTOMREQUEST, "POST"); + + // Prepare request body + char *wrapped_query = wrap_query(query, args); + + // Set the request body + curl_easy_setopt(client->curl, CURLOPT_POSTFIELDS, wrapped_query); + curl_easy_setopt(client->curl, CURLOPT_POSTFIELDSIZE, + strlen(wrapped_query)); + + // Set the write function to capture the response + curl_easy_setopt(client->curl, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(client->curl, CURLOPT_WRITEDATA, (void *) buf); + + // Perform the request + const CURLcode ret = curl_easy_perform(client->curl); + + // Append null terminator to the buffer + if (ret == CURLE_OK) + buffer_append(buf, "\0", 1); + + // Cleanup + free(wrapped_query); + curl_slist_free_all(headers); + + return ret; +} + +/** + * Handle errors in the response. + * Will check for the "errors" key in the response and print the error messages. + * Returns failure if any errors are found. + * @param root Parsed JSON response + * @return 0 on success (no errors), -1 on failure + */ +static int gh_handle_error(const cJSON *root) +{ + // Check for errors + cJSON *errors = cJSON_GetObjectItemCaseSensitive(root, "errors"); + if (!errors || !cJSON_IsArray(errors)) { + // No errors + return 0; + } + + cJSON *err; + cJSON_ArrayForEach(err, errors) + { + // Get the error message + cJSON *message = cJSON_GetObjectItemCaseSensitive(err, + "message"); + if (message && cJSON_IsString(message)) { + fprintf(stderr, "Github Error: %s\n", + message->valuestring); + } else { + fprintf(stderr, "Github Error: Unknown error\n"); + } + } + + return -1; +} |