From 5b98db23fb4e1d820ed5063a604af50f67548808 Mon Sep 17 00:00:00 2001 From: Scott Griv Date: Sun, 28 Apr 2024 23:39:46 -0400 Subject: [PATCH] Update application to remove star and commit loop to adhere to Rate Limits --- README.md | 14 +- ...Hub API Collection.postman_collection.json | 271 +++++++++++++++++- src/app/app.component.html | 9 +- src/app/app.component.ts | 16 +- src/environments/environment.prod.ts | 2 +- src/environments/environment.ts | 2 +- 6 files changed, 294 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index d73a7f1..a1346b0 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ This dynamic web application leverages the GitHub API to provide a comprehensive and interactive search experience, enabling users to effortlessly find and explore profiles of both individual developers and organizations on GitHub. Featuring a sleek, user-friendly interface built with Angular, the application not only displays basic profile information but also offers insights into repositories, contributions, and activity timelines, allowing for in-depth understanding of a user's or organization's coding journey and open-source contributions. - Built with GitHub API and Angular. - View a demo of the project on GitHub Pages **[Here](https://scottgriv.github.io/GitHub-User-Info)**. +- A helpful Postman Collection is included in the `/docs/api` folder.
@@ -37,14 +38,24 @@ This dynamic web application leverages the GitHub API to provide a comprehensive Application Preview
+> [!NOTE] +> Total Commits and Total Stars is hidden without using a Personal Access Token due to the multiple API requests/loops required to obtain the data for demo purposes. Change `personalAccessToken` to `true` vs. the default `false` to see this information. + > [!IMPORTANT] -> If you get a "Rate limit exceeded" error, it is because the application is not using a personal access token, therefore, API requests are limited. +> If you get a "Rate limit exceeded" error, it is because the application is not using a personal access token, therefore, API requests are limited . > See [GitHub Rate Limits for REST API](https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28) for more information. +> The primary rate limit for unauthenticated requests is 60 requests per hour > [!TIP] > If you would like to use the application with a personal access token, which has higher rate limits, update the `usePersonalAccessToken` flag to `true` and add your own token to the `personalAccessToken` variable in the `src/environments/environment.prod.ts` file. > Build and serve the application for the changes to take place. +> [!WARNING] +> By default, the application hosted on GitHub Pages does not use a Personal Access Token, thus, it is subject to Rate Limits which is displayed in the output. + +> [!CAUTION] +> Do not commit your repo when testing using a Personal Access token above; it should be used for local testing purposes only. + --------------- ## Table of Contents @@ -96,6 +107,7 @@ To get more help on the Angular CLI use `ng help` or go check out the [Angular C - [TypeScript](https://www.typescriptlang.org/) - A programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript and adds optional static typing to the language. - [HTML](https://developer.mozilla.org/en-US/docs/Web/HTML) - The standard markup language for documents designed to be displayed in a web browser. - [CSS](https://developer.mozilla.org/en-US/docs/Web/CSS) - A style sheet language used for describing the presentation of a document written in a markup language such as HTML. +- [Postman](https://www.postman.com/) - Postman is an API platform for building and using APIs. Postman simplifies each step of the API lifecycle and streamlines collaboration so you can create better APIs—faster. ## License diff --git a/docs/api/GitHub API Collection.postman_collection.json b/docs/api/GitHub API Collection.postman_collection.json index 7f2a295..895bc9f 100644 --- a/docs/api/GitHub API Collection.postman_collection.json +++ b/docs/api/GitHub API Collection.postman_collection.json @@ -1,11 +1,10 @@ { "info": { - "_postman_id": "e1509fb2-5f05-439b-9789-3257eaf912c6", + "_postman_id": "6fe30c2f-38d3-4fc0-b210-7e622b6b6507", "name": "GitHub API Collection", - "description": "> A simple collection based on [GitHub API V3](https://docs.github.com/en/rest/reference)\n\n## Features\n\n- Get Profile Information\n- Create repository on GitHub\n- Create Issues on any repository in GitHub\n- Delete your repository\n- Check health of GitHub API\n\n## Getting Started\n\nGenerate a [Personal Access Token](https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api#authentication) with the following grants:\n\n- Repo:all\n- Delete Repo\n\n![GitHub Token link](https://docs.github.com/assets/images/personal_token.png)\n\nThe Collection uses the following Collection Variables:\n\n- `username` : Fill in your GitHub Username\n- `url` : The base URL endpoint of GitHub v3 API\n- `repoName`: [OPTIONAL] the repo to delete, and create issues on. Is automatically set using Postman Tests.\n\nCollection By: [Hemanth Krishna](https://github.com/DarthBenro008)", + "description": "> A simple collection based on [GitHub API V3](https://docs.github.com/en/rest/reference) \n \n\n## Features\n\n- Get Profile Information\n- Create repository on GitHub\n- Create Issues on any repository in GitHub\n- Delete your repository\n- Check health of GitHub API\n \n\n## Getting Started\n\nGenerate a [Personal Access Token](https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api#authentication) with the following grants:\n\n- Repo:all\n- Delete Repo\n \n\n\n\nThe Collection uses the following Collection Variables:\n\n- `username` : Fill in your GitHub Username\n- `url` : The base URL endpoint of GitHub v3 API\n- `repoName`: \\[OPTIONAL\\] the repo to delete, and create issues on. Is automatically set using Postman Tests.\n \n\nCollection By: [Hemanth Krishna](https://github.com/DarthBenro008)", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "24571367", - "_collection_link": "https://www.postman.com/indianpost/workspace/postman-api-101-workspace/collection/12292853-e1509fb2-5f05-439b-9789-3257eaf912c6?action=share&source=collection_link&creator=24571367" + "_exporter_id": "24571367" }, "item": [ { @@ -153,6 +152,154 @@ } ] }, + { + "name": "Rate Limit Check", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"GitHub API Health\", function () {\r", + " pm.response.to.have.status(200);\r", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "https://api.github.com/rate_limit", + "protocol": "https", + "host": [ + "api", + "github", + "com" + ], + "path": [ + "rate_limit" + ] + }, + "description": "This API Endpoint checks the Health of GitHub API. It returns a random design philosophical quote" + }, + "response": [ + { + "name": "Check API Health (Get a random philosophical quote)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://api.github.com/zen", + "protocol": "https", + "host": [ + "api", + "github", + "com" + ], + "path": [ + "zen" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "plain", + "header": [ + { + "key": "Server", + "value": "GitHub.com" + }, + { + "key": "Date", + "value": "Sun, 18 Jul 2021 07:31:06 GMT" + }, + { + "key": "Content-Type", + "value": "text/plain;charset=utf-8" + }, + { + "key": "Access-Control-Expose-Headers", + "value": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset" + }, + { + "key": "Access-Control-Allow-Origin", + "value": "*" + }, + { + "key": "Strict-Transport-Security", + "value": "max-age=31536000; includeSubdomains; preload" + }, + { + "key": "X-Frame-Options", + "value": "deny" + }, + { + "key": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "key": "X-XSS-Protection", + "value": "0" + }, + { + "key": "Referrer-Policy", + "value": "origin-when-cross-origin, strict-origin-when-cross-origin" + }, + { + "key": "Content-Security-Policy", + "value": "default-src 'none'" + }, + { + "key": "Vary", + "value": "Accept-Encoding, Accept, X-Requested-With" + }, + { + "key": "Content-Encoding", + "value": "gzip" + }, + { + "key": "X-RateLimit-Limit", + "value": "60" + }, + { + "key": "X-RateLimit-Remaining", + "value": "54" + }, + { + "key": "X-RateLimit-Reset", + "value": "1626594015" + }, + { + "key": "X-RateLimit-Resource", + "value": "core" + }, + { + "key": "X-RateLimit-Used", + "value": "6" + }, + { + "key": "Accept-Ranges", + "value": "bytes" + }, + { + "key": "Content-Length", + "value": "51" + }, + { + "key": "X-GitHub-Request-Id", + "value": "5E32:306F:A531B:D1E52:60F3D8BA" + } + ], + "cookie": [], + "body": "Responsive is better than fast." + } + ] + }, { "name": "Get User Information", "request": { @@ -833,14 +980,115 @@ "description": "Deletes and repository from GitHub.\n\nThis needs admin permission granted token!" }, "response": [] + }, + { + "name": "Get Repos", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{url}}/user/repos?per_page=100", + "host": [ + "{{url}}" + ], + "path": [ + "user", + "repos" + ], + "query": [ + { + "key": "per_page", + "value": "100" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Org Repos", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{url}}/users/{{orgname}}/repos?per_page=100", + "host": [ + "{{url}}" + ], + "path": [ + "users", + "{{orgname}}", + "repos" + ], + "query": [ + { + "key": "per_page", + "value": "100" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Org Repos 2", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{url}}/orgs/{{orgname}}/repos?page={page}&per_page=100", + "host": [ + "{{url}}" + ], + "path": [ + "orgs", + "{{orgname}}", + "repos" + ], + "query": [ + { + "key": "page", + "value": "{page}" + }, + { + "key": "per_page", + "value": "100" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Orgs", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{url}}/user/orgs", + "host": [ + "{{url}}" + ], + "path": [ + "user", + "orgs" + ] + } + }, + "response": [] } ], "auth": { - "type": "bearer", - "bearer": [ + "type": "apikey", + "apikey": [ + { + "key": "value", + "value": "", + "type": "string" + }, { - "key": "token", - "value": "{{token}}", + "key": "key", + "value": "Authorization", "type": "string" } ] @@ -868,7 +1116,7 @@ "variable": [ { "key": "username", - "value": "DarthBenro008" + "value": "scottgriv" }, { "key": "url", @@ -877,6 +1125,11 @@ { "key": "repoName", "value": "Postman-API-101-Reposiory" + }, + { + "key": "orgname", + "value": "Night-Owl-Labs\n", + "type": "string" } ] } \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html index f8b7fbf..73adec5 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -35,10 +35,11 @@

{{ userInf

Public Gists: {{ userInfo.public_gists ?? 'N/A'}}

Followers: {{ userInfo.followers ?? 'N/A'}}

Following: {{ userInfo.following ?? 'N/A'}}

-

Total Stars: {{ totalStars }}

-

Total Commits: {{ totalCommits }}

-

Created At: {{ userInfo.created_at ?? 'N/A'}}

-

Updated At: {{ userInfo.updated_at ?? 'N/A'}}

+ +

Total Stars: {{ totalStars }}

+

Total Commits: {{ totalCommits }}

+

Account Created At: {{ userInfo.created_at ?? 'N/A'}}

+

Account Updated At: {{ userInfo.updated_at ?? 'N/A'}}

API Rate Info:

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index af8674c..b7dcc46 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -37,6 +37,8 @@ export class AppComponent { } ); + // Only fetch stars and commits if a personal access token is being used + if (this.usePersonalAccessToken) { this.githubService.getTotalStars(this.username, this.usePersonalAccessToken).subscribe( stars => { this.totalStars = stars; @@ -55,6 +57,7 @@ export class AppComponent { } ); } + } getCompleteUrl(url: string): string { if (!url.includes('http://') && !url.includes('https://')) { @@ -64,15 +67,20 @@ export class AppComponent { } handleError(error: HttpErrorResponse) { + this.userInfo = null; // Clear out the userInfo if (error.status === 404) { - this.userInfo = null; // Clear out the userInfo this.errorMessage = 'User profile not found.'; } else if (error.status === 403) { - this.userInfo = null; // Clear out the userInfo - this.errorMessage = 'Rate limit exceeded. Please use a Personal Access Token for more requests.'; + const rateLimitReset = error.headers.get('X-RateLimit-Reset'); + if (rateLimitReset) { + const resetTime = new Date(parseInt(rateLimitReset) * 1000); + this.errorMessage = `Rate limit exceeded. Please wait until ${resetTime.toLocaleTimeString()} to make more requests or use a Personal Access Token for more requests.`; + } else { + this.errorMessage = 'Rate limit exceeded, but reset time is unavailable.'; + } } else { - this.userInfo = null; // Clear out the userInfo this.errorMessage = 'An unknown error occurred.'; } } + } diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index c7b2d36..e830c59 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -2,6 +2,6 @@ export const environment = { production: true, usePersonalAccessToken: false, // true will use the personalAccessToken, false will use unauthenticated access - personalAccessToken: '' + personalAccessToken: '' // Only utilize this for local testing purposes - DO NOT PUSH TO GITHUB }; \ No newline at end of file diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 83c03eb..40d2214 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -2,6 +2,6 @@ export const environment = { production: false, usePersonalAccessToken: false, // true will use the personalAccessToken, false will use unauthenticated access - personalAccessToken: '' + personalAccessToken: '' // Only utilize this for local testing purposes - DO NOT PUSH TO GITHUB }; \ No newline at end of file