From ed1e514e772ae9b4a960e0b31fc88d9865d24221 Mon Sep 17 00:00:00 2001
From: Alex Oliveira
Date: Fri, 29 Sep 2023 21:57:55 +0100
Subject: [PATCH] fix: type check error
---
.github/workflows/testing.yml | 23 +--
.gitignore | 1 +
CONTRIBUTING.md | 15 +-
README.md | 159 +++++++++---------
api/index.ts | 13 +-
deno.json | 9 +
src/Helpers/Logger.ts | 19 +++
src/Helpers/Retry.ts | 4 +-
src/Helpers/__tests__/Retry.test.ts | 1 -
src/Repository/GithubRepository.ts | 13 +-
src/Services/GithubApiService.ts | 64 ++++---
src/StaticRenderRegeneration/cache_manager.ts | 3 +-
src/StaticRenderRegeneration/index.ts | 46 ++---
src/StaticRenderRegeneration/types.ts | 10 +-
src/StaticRenderRegeneration/utils.ts | 58 +++----
src/Types/EServiceKindError.ts | 4 +
src/Types/Request.ts | 6 +
src/Types/ServiceError.ts | 20 +++
src/Types/index.ts | 4 +-
src/error_page.ts | 5 +
src/icons.ts | 2 +-
src/pages/Error.ts | 23 +++
src/theme.ts | 5 +-
src/trophy.ts | 51 +++---
src/trophy_list.ts | 31 ++--
src/user_info.ts | 11 +-
vercel.json | 26 +--
27 files changed, 381 insertions(+), 245 deletions(-)
create mode 100644 deno.json
create mode 100644 src/Helpers/Logger.ts
create mode 100644 src/Types/EServiceKindError.ts
create mode 100644 src/Types/ServiceError.ts
create mode 100644 src/pages/Error.ts
diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index b8215530..6432a532 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -7,16 +7,19 @@ on:
jobs:
install-dependencies:
runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ deno-version: [1.37.0]
+
steps:
- - name: Check if PR is in draft mode
- run: |
- if [ "${{ github.event.pull_request.draft }}" == "true" ]; then
- echo "PR is in draft mode, skipping workflow"
- exit 78
- fi
- - name: Set up Deno
+ - name: Git Checkout Deno Module
+ uses: actions/checkout@v2
+ - name: Use Deno Version ${{ matrix.deno-version }}
uses: denolib/setup-deno@v2
with:
- deno-version: "1.37.0"
- - name: Run tests
- run: deno test --allow-env
+ deno-version: ${{ matrix.deno-version }}
+ - name: Lint Deno Module
+ run: deno fmt --check
+ - name: Test Deno Module
+ run: deno task test
diff --git a/.gitignore b/.gitignore
index 3c497e35..3cd929e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.vscode
.env
.idea
+deno.lock
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 95eaad16..e8cf04a8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,14 +2,14 @@
## Environment
-* Deno >= v1.9.2
-* [Vercel](https://vercel.com/)
-* GitHub API v4
+- Deno >= v1.9.2
+- [Vercel](https://vercel.com/)
+- GitHub API v4
## Local Run
-Create `.env` file to project root directory, and write your GitHub token to the `.env` file.
-Please select the authority of `repo` when creating token.
+Create `.env` file to project root directory, and write your GitHub token to the
+`.env` file. Please select the authority of `repo` when creating token.
```properties
GITHUB_TOKEN1=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
@@ -23,7 +23,7 @@ GITHUB_API=https://github.example.com/api/graphql
Run local server.
```sh
-deno run --allow-net --allow-read --allow-env --allow-write debug.ts
+deno task start
```
Open localhost from your browser.
@@ -36,7 +36,8 @@ Read the [.editorconfig](./.editorconfig)
## Run deno lint
-If you want to contribute to my project, you should check the lint with the following command.
+If you want to contribute to my project, you should check the lint with the
+following command.
```sh
deno lint --unstable
diff --git a/README.md b/README.md
index b4727c8b..96bc8cfd 100644
--- a/README.md
+++ b/README.md
@@ -1,26 +1,26 @@
-
+
GitHub Profile Trophy
🏆 Add dynamically generated GitHub Stat Trophies on your readme
-
+
-
-
+
+
-
+
-
+
-
+
@@ -28,13 +28,14 @@
-
+
# Quick Start
-Add the following code to your readme. When pasting the code into your profile's readme, change the `?username=` value to your GitHub's username.
+Add the following code to your readme. When pasting the code into your profile's
+readme, change the `?username=` value to your GitHub's username.
```
[![trophy](https://github-profile-trophy.vercel.app/?username=ryo-ma)](https://github.com/ryo-ma/github-profile-trophy)
@@ -51,6 +52,7 @@ Add optional parameter of the theme.
```
[![trophy](https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=onedark)](https://github.com/ryo-ma/github-profile-trophy)
```
+
@@ -61,22 +63,25 @@ Add optional parameter of the theme.
Ranks are `SSS` `SS` `S` `AAA` `AA` `A` `B` `C` `UNKNOWN` `SECRET`.
-| Rank | Description |
-| ---- | ---- |
-| SSS, SS, S | You are at a hard to reach rank. You can brag. |
-| AAA, AA, A | You will reach this rank if you do your best. Let's aim here first. |
-| B, C | You are currently making good progress. Let's aim a bit higher. |
-| UNKNOWN | You have not taken action yet. Let's act first. |
-| SECRET | This rank is very rare. The trophy will not be displayed until certain conditions are met. |
+| Rank | Description |
+| ---------- | ------------------------------------------------------------------------------------------ |
+| SSS, SS, S | You are at a hard to reach rank. You can brag. |
+| AAA, AA, A | You will reach this rank if you do your best. Let's aim here first. |
+| B, C | You are currently making good progress. Let's aim a bit higher. |
+| UNKNOWN | You have not taken action yet. Let's act first. |
+| SECRET | This rank is very rare. The trophy will not be displayed until certain conditions are met. |
## Secret Rank
-The acquisition condition is secret, but you can know the condition by reading this code.
+
+The acquisition condition is secret, but you can know the condition by reading
+this code.
-There are only a few secret trophies. Therefore, if you come up with interesting conditions, I will consider adding a trophy. I am waiting for contributions.
+There are only a few secret trophies. Therefore, if you come up with interesting
+conditions, I will consider adding a trophy. I am waiting for contributions.
# About Display details
@@ -90,23 +95,21 @@ There are only a few secret trophies. Therefore, if you come up with interesting
4. Target aggregation result.
5. Next Rank Bar. The road from the current rank to the next rank.
-
# Optional Request Parameters
-* [title](#filter-by-titles)
-* [rank](#filter-by-ranks)
-* [column](#specify-the-maximum-row--column-size)
-* [row](#specify-the-maximum-row--column-size)
-* [theme](#apply-theme)
-* [margin-w](#margin-width)
-* [margin-h](#margin-height)
-* [no-bg](#transparent-background)
-* [no-frame](#hide-frames)
-
+- [title](#filter-by-titles)
+- [rank](#filter-by-ranks)
+- [column](#specify-the-maximum-row--column-size)
+- [row](#specify-the-maximum-row--column-size)
+- [theme](#apply-theme)
+- [margin-w](#margin-width)
+- [margin-h](#margin-height)
+- [no-bg](#transparent-background)
+- [no-frame](#hide-frames)
## Filter by titles
-You can filter the display by specifying the titles of trophy.
+You can filter the display by specifying the titles of trophy.
```
https://github-profile-trophy.vercel.app/?username=ryo-ma&title=Followers
@@ -124,12 +127,13 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&title=Stars,Followers
## Filter by ranks
-You can filter the display by specifying the ranks.
+You can filter the display by specifying the ranks.\
`Available values: SECRET SSS SS S AAA AA A B C`
```
https://github-profile-trophy.vercel.app/?username=ryo-ma&rank=S
```
+
@@ -146,26 +150,28 @@ You can also exclude ranks.
https://github-profile-trophy.vercel.app/?username=ryo-ma&rank=-C,-B
```
-
## Specify the maximum row & column size
-You can specify the maximum row and column size.
+You can specify the maximum row and column size.\
Trophy will be hidden if it exceeds the range of both row and column.
-`Available value: number type`
+`Available value: number type`\
`Default: column=6 row=3`
Restrict only row
+
```
https://github-profile-trophy.vercel.app/?username=ryo-ma&row=2
```
Restrict only column
+
```
https://github-profile-trophy.vercel.app/?username=ryo-ma&column=2
```
Restrict row & column
+
```
https://github-profile-trophy.vercel.app/?username=ryo-ma&row=2&column=3
```
@@ -175,47 +181,49 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&row=2&column=3
Adaptive column
+
```
https://github-profile-trophy.vercel.app/?username=ryo-ma&column=-1
```
-You can set `columns` to `-1` to adapt the width to the number of trophies, the parameter `row` will be ignored.
+You can set `columns` to `-1` to adapt the width to the number of trophies, the
+parameter `row` will be ignored.
## Apply theme
Available themes.
-| theme |
-| ---- |
-| [flat](#flat) |
-| [onedark](#onedark) |
-| [gruvbox](#gruvbox) |
-| [dracula](#dracula) |
-| [monokai](#monokai) |
-| [chalk](#chalk) |
-| [nord](#nord) |
-| [alduin](#alduin) |
-| [darkhub](#darkhub) |
-| [juicyfresh](#juicyfresh) |
-| [buddhism](#buddhism) |
-| [oldie](#oldie) |
-| [radical](#radical) |
-| [onestar](#onestar) |
-| [discord](#discord) |
-| [algolia](#algolia) |
-| [gitdimmed](#gitdimmed) |
-| [tokyonight](#tokyonight) |
-| [matrix](#matrix) |
-| [apprentice](#apprentice) |
+| theme |
+| --------------------------- |
+| [flat](#flat) |
+| [onedark](#onedark) |
+| [gruvbox](#gruvbox) |
+| [dracula](#dracula) |
+| [monokai](#monokai) |
+| [chalk](#chalk) |
+| [nord](#nord) |
+| [alduin](#alduin) |
+| [darkhub](#darkhub) |
+| [juicyfresh](#juicyfresh) |
+| [buddhism](#buddhism) |
+| [oldie](#oldie) |
+| [radical](#radical) |
+| [onestar](#onestar) |
+| [discord](#discord) |
+| [algolia](#algolia) |
+| [gitdimmed](#gitdimmed) |
+| [tokyonight](#tokyonight) |
+| [matrix](#matrix) |
+| [apprentice](#apprentice) |
| [dark_dimmed](#dark_dimmed) |
-| [dark_lover](#dark_lover) |
-
+| [dark_lover](#dark_lover) |
### flat
```
https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=flat
```
+
@@ -280,7 +288,6 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=nord
-
### alduin
```
@@ -388,7 +395,7 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=tokyonight
```
-
+
### matrix
@@ -398,7 +405,7 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=matrix
```
-
+
### apprentice
@@ -408,7 +415,7 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=apprentice
```
-
+
### dark_dimmed
@@ -418,7 +425,7 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=dark_dimmed
```
-
+
### dark_lover
@@ -428,13 +435,13 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&theme=dark_lover
```
-
+
## Margin Width
-You can put a margin in the width between trophies.
-`Available value: number type`
+You can put a margin in the width between trophies.\
+`Available value: number type`\
`Default: margin-w=0`
```
@@ -447,8 +454,8 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&margin-w=15
## Margin Height
-You can put a margin in the height between trophies.
-`Available value: number type`
+You can put a margin in the height between trophies.\
+`Available value: number type`\
`Default: margin-h=0`
```
@@ -467,8 +474,8 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&column=3&margin-w=15&m
## Transparent background
-You can turn the background transparent.
-`Available value: boolean type (true or false)`
+You can turn the background transparent.\
+`Available value: boolean type (true or false)`\
`Default: no-bg=false`
```
@@ -479,12 +486,10 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&no-bg=true
-
-
## Hide frames
-You can hide the frames around the trophies.
-`Available value: boolean type (true or false)`
+You can hide the frames around the trophies.\
+`Available value: boolean type (true or false)`\
`Default: no-frame=false`
```
@@ -495,12 +500,12 @@ https://github-profile-trophy.vercel.app/?username=ryo-ma&no-frame=true
-
# Contribution Guide
+
Check [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
# Testing
```bash
-deno test --allow-env
+deno task test
```
diff --git a/api/index.ts b/api/index.ts
index 4a095c78..68d5215a 100644
--- a/api/index.ts
+++ b/api/index.ts
@@ -1,11 +1,13 @@
import { Card } from "../src/card.ts";
import { CONSTANTS, parseParams } from "../src/utils.ts";
import { COLORS, Theme } from "../src/theme.ts";
-import { Error400, Error404 } from "../src/error_page.ts";
+import { Error400 } from "../src/error_page.ts";
import "https://deno.land/x/dotenv@v0.5.0/load.ts";
import { staticRenderRegeneration } from "../src/StaticRenderRegeneration/index.ts";
import { GithubRepositoryService } from "../src/Repository/GithubRepository.ts";
import { GithubApiService } from "../src/Services/GithubApiService.ts";
+import { ServiceError } from "../src/Types/index.ts";
+import { ErrorPage } from "../src/pages/Error.ts";
const serviceProvider = new GithubApiService();
const client = new GithubRepositoryService(serviceProvider).repository;
@@ -74,14 +76,11 @@ async function app(req: Request): Promise {
);
}
const userInfo = await client.requestUserInfo(username);
- if (userInfo === null) {
- const error = new Error404(
- "Can not find a user with username: " + username,
- );
+ if (userInfo instanceof ServiceError) {
return new Response(
- error.render(),
+ ErrorPage({ error: userInfo, username }).render(),
{
- status: error.status,
+ status: userInfo.code,
headers: new Headers({ "Content-Type": "text" }),
},
);
diff --git a/deno.json b/deno.json
new file mode 100644
index 00000000..3b3d07a4
--- /dev/null
+++ b/deno.json
@@ -0,0 +1,9 @@
+{
+ "tasks": {
+ "start": "deno -A debug.ts",
+ "debug": "deno --inspect-brk -A debug.ts",
+ "format": "deno fmt",
+ "lint": "deno lint",
+ "test": "ENV_TYPE=test deno test --allow-env"
+ }
+}
diff --git a/src/Helpers/Logger.ts b/src/Helpers/Logger.ts
new file mode 100644
index 00000000..896192a5
--- /dev/null
+++ b/src/Helpers/Logger.ts
@@ -0,0 +1,19 @@
+const enableLogging = Deno.env.get("ENV_TYPE") !== "test";
+
+export class Logger {
+ public static log(message: unknown): void {
+ if (!enableLogging) return;
+ console.log(message);
+ }
+
+ public static error(message: unknown): void {
+ if (!enableLogging) return;
+
+ console.error(message);
+ }
+ public static warn(message: unknown): void {
+ if (!enableLogging) return;
+
+ console.warn(message);
+ }
+}
diff --git a/src/Helpers/Retry.ts b/src/Helpers/Retry.ts
index be1eb9fc..a7ce9a95 100644
--- a/src/Helpers/Retry.ts
+++ b/src/Helpers/Retry.ts
@@ -1,3 +1,5 @@
+import { Logger } from "./Logger.ts";
+
export type RetryCallbackProps = {
attempt: number;
};
@@ -16,7 +18,7 @@ async function* createAsyncIterable(
return;
} catch (e) {
yield null;
- console.error(e);
+ Logger.error(e);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
diff --git a/src/Helpers/__tests__/Retry.test.ts b/src/Helpers/__tests__/Retry.test.ts
index 7fcad13e..a1fec42b 100644
--- a/src/Helpers/__tests__/Retry.test.ts
+++ b/src/Helpers/__tests__/Retry.test.ts
@@ -44,7 +44,6 @@ Deno.test("Should retry", async () => {
Deno.test("Should retry the asyncronous callback", async () => {
let countErrors = 0;
-
const callbackError = async () => {
countErrors++;
// Mock request in callback
diff --git a/src/Repository/GithubRepository.ts b/src/Repository/GithubRepository.ts
index b345fd35..c2a408c6 100644
--- a/src/Repository/GithubRepository.ts
+++ b/src/Repository/GithubRepository.ts
@@ -1,3 +1,4 @@
+import { ServiceError } from "../Types/index.ts";
import {
GitHubUserActivity,
GitHubUserIssue,
@@ -7,17 +8,19 @@ import {
} from "../user_info.ts";
export abstract class GithubRepository {
- abstract requestUserInfo(username: string): Promise;
+ abstract requestUserInfo(username: string): Promise;
abstract requestUserActivity(
username: string,
- ): Promise;
- abstract requestUserIssue(username: string): Promise;
+ ): Promise;
+ abstract requestUserIssue(
+ username: string,
+ ): Promise;
abstract requestUserPullRequest(
username: string,
- ): Promise;
+ ): Promise;
abstract requestUserRepository(
username: string,
- ): Promise;
+ ): Promise;
}
export class GithubRepositoryService {
diff --git a/src/Services/GithubApiService.ts b/src/Services/GithubApiService.ts
index 4d7a9ccd..685bac58 100644
--- a/src/Services/GithubApiService.ts
+++ b/src/Services/GithubApiService.ts
@@ -14,8 +14,11 @@ import {
} from "../Schemas/index.ts";
import { soxa } from "../../deps.ts";
import { Retry } from "../Helpers/Retry.ts";
-import { QueryDefaultResponse } from "../Types/index.ts";
+import { GithubError, QueryDefaultResponse } from "../Types/index.ts";
import { CONSTANTS } from "../utils.ts";
+import { EServiceKindError } from "../Types/EServiceKindError.ts";
+import { ServiceError } from "../Types/ServiceError.ts";
+import { Logger } from "../Helpers/Logger.ts";
// Need to be here - Exporting from another file makes array of null
export const TOKENS = [
@@ -26,35 +29,40 @@ export const TOKENS = [
export class GithubApiService extends GithubRepository {
async requestUserRepository(
username: string,
- ): Promise {
+ ): Promise {
return await this.executeQuery(queryUserRepository, {
username,
});
}
async requestUserActivity(
username: string,
- ): Promise {
+ ): Promise {
return await this.executeQuery(queryUserActivity, {
username,
});
}
- async requestUserIssue(username: string): Promise {
+ async requestUserIssue(
+ username: string,
+ ): Promise {
return await this.executeQuery(queryUserIssue, {
username,
});
}
async requestUserPullRequest(
username: string,
- ): Promise {
+ ): Promise {
return await this.executeQuery(
queryUserPullRequest,
{ username },
);
}
- async requestUserInfo(username: string): Promise {
+ async requestUserInfo(username: string): Promise {
// Avoid to call others if one of them is null
const repository = await this.requestUserRepository(username);
- if (repository === null) return null;
+ if (repository instanceof ServiceError) {
+ Logger.error(repository);
+ return repository;
+ }
const promises = Promise.allSettled([
this.requestUserActivity(username),
@@ -69,15 +77,34 @@ export class GithubApiService extends GithubRepository {
];
if (status.includes("rejected")) {
- console.error(`Can not find a user with username:' ${username}'`);
- return null;
+ Logger.error(`Can not find a user with username:' ${username}'`);
+ return new ServiceError("not found", EServiceKindError.NOT_FOUND);
}
return new UserInfo(
(activity as PromiseFulfilledResult).value,
(issue as PromiseFulfilledResult).value,
(pullRequest as PromiseFulfilledResult).value,
- repository,
+ repository as GitHubUserRepository,
+ );
+ }
+
+ private handleError(responseErrors: GithubError[]): ServiceError {
+ const errors = responseErrors ?? [];
+ const isRateLimitExceeded = (errors ?? []).some((error) => {
+ error.type.includes(EServiceKindError.RATE_LIMIT);
+ });
+
+ if (isRateLimitExceeded) {
+ throw new ServiceError(
+ "Rate limit exceeded",
+ EServiceKindError.RATE_LIMIT,
+ );
+ }
+
+ throw new ServiceError(
+ "unknown error",
+ EServiceKindError.NOT_FOUND,
);
}
@@ -97,24 +124,23 @@ export class GithubApiService extends GithubRepository {
Authorization: `bearer ${TOKENS[attempt]}`,
},
});
- }) as QueryDefaultResponse<{ user: T; errors?: unknown[] }>;
+ }) as QueryDefaultResponse<{ user: T }>;
- if (response.data.data.errors) {
- throw new Error("Error from Github API", {
- cause: response.data.data.errors,
- });
+ if (response?.data?.errors) {
+ return this.handleError(response?.data?.errors);
}
- return response?.data?.data?.user ?? null;
+ return response?.data?.data?.user ??
+ new ServiceError("not found", EServiceKindError.NOT_FOUND);
} catch (error) {
// TODO: Move this to a logger instance later
if (error instanceof Error && error.cause) {
- console.error(JSON.stringify(error.cause, null, 2));
+ Logger.error(JSON.stringify(error.cause, null, 2));
} else {
- console.error(error);
+ Logger.error(error);
}
- return null;
+ return new ServiceError("not found", EServiceKindError.NOT_FOUND);
}
}
}
diff --git a/src/StaticRenderRegeneration/cache_manager.ts b/src/StaticRenderRegeneration/cache_manager.ts
index 2f11479c..f0a3a87c 100644
--- a/src/StaticRenderRegeneration/cache_manager.ts
+++ b/src/StaticRenderRegeneration/cache_manager.ts
@@ -1,3 +1,4 @@
+import { Logger } from "../Helpers/Logger.ts";
import { existsSync } from "./utils.ts";
export class CacheManager {
@@ -44,7 +45,7 @@ export class CacheManager {
const data = new TextEncoder().encode(text);
Deno.writeFile(this.cacheFilePath, data, { create: true }).catch(() => {
- console.warn("Failed to save cache file");
+ Logger.warn("Failed to save cache file");
});
}
}
diff --git a/src/StaticRenderRegeneration/index.ts b/src/StaticRenderRegeneration/index.ts
index d93fc61f..810eb85d 100644
--- a/src/StaticRenderRegeneration/index.ts
+++ b/src/StaticRenderRegeneration/index.ts
@@ -1,29 +1,33 @@
import { CacheManager } from "./cache_manager.ts";
import { StaticRegenerationOptions } from "./types.ts";
-import { getUrl, readCache, generateUUID } from "./utils.ts";
+import { generateUUID, getUrl, readCache } from "./utils.ts";
-export async function staticRenderRegeneration(request: Request, options: StaticRegenerationOptions, render: (request: Request) => Promise) {
- // avoid TypeError: Invalid URL at deno:core
- const url = getUrl(request)
+export async function staticRenderRegeneration(
+ request: Request,
+ options: StaticRegenerationOptions,
+ render: (request: Request) => Promise,
+) {
+ // avoid TypeError: Invalid URL at deno:core
+ const url = getUrl(request);
- // if more conditions are added, make sure to create a variable to skipCache
- if (url.pathname === "/favicon.ico") {
- return await render(request);
- }
+ // if more conditions are added, make sure to create a variable to skipCache
+ if (url.pathname === "/favicon.ico") {
+ return await render(request);
+ }
- const cacheFile = await generateUUID(url.pathname + (url.search ?? ""));
- const cacheManager = new CacheManager(options.revalidate ?? 0, cacheFile);
- if (cacheManager.isCacheValid) {
- const cache = readCache(cacheManager.cacheFilePath)
- if(cache !== null) {
- return new Response(cache, {
- headers: options.headers ?? new Headers({}),
- });
- }
+ const cacheFile = await generateUUID(url.pathname + (url.search ?? ""));
+ const cacheManager = new CacheManager(options.revalidate ?? 0, cacheFile);
+ if (cacheManager.isCacheValid) {
+ const cache = readCache(cacheManager.cacheFilePath);
+ if (cache !== null) {
+ return new Response(cache, {
+ headers: options.headers ?? new Headers({}),
+ });
}
-
- const response = await render(request)
- cacheManager.save(response)
+ }
+
+ const response = await render(request);
+ cacheManager.save(response);
- return response
+ return response;
}
diff --git a/src/StaticRenderRegeneration/types.ts b/src/StaticRenderRegeneration/types.ts
index e9b79076..c98b66c7 100644
--- a/src/StaticRenderRegeneration/types.ts
+++ b/src/StaticRenderRegeneration/types.ts
@@ -1,6 +1,6 @@
export interface StaticRegenerationOptions {
- // The number of milliseconds before the page should be revalidated
- revalidate?: number
- // The headers to be sent with the response
- headers?: Headers
-}
\ No newline at end of file
+ // The number of milliseconds before the page should be revalidated
+ revalidate?: number;
+ // The headers to be sent with the response
+ headers?: Headers;
+}
diff --git a/src/StaticRenderRegeneration/utils.ts b/src/StaticRenderRegeneration/utils.ts
index ebc19c4b..77f1f907 100644
--- a/src/StaticRenderRegeneration/utils.ts
+++ b/src/StaticRenderRegeneration/utils.ts
@@ -1,42 +1,42 @@
export function getUrl(request: Request) {
- try {
- return new URL(request.url)
- } catch {
- return {
- pathname: request.url,
- search: request.url
- }
- }
+ try {
+ return new URL(request.url);
+ } catch {
+ return {
+ pathname: request.url,
+ search: request.url,
+ };
+ }
}
export function readCache(cacheFilePath: string): Uint8Array | null {
- try {
- return Deno.readFileSync(cacheFilePath)
- } catch {
- return null
- }
+ try {
+ return Deno.readFileSync(cacheFilePath);
+ } catch {
+ return null;
+ }
}
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest
export async function generateUUID(message: string): Promise {
- const encoder = new TextEncoder();
- const data = encoder.encode(message);
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
-
- const hashArray = Array.from(new Uint8Array(hashBuffer));
- const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
+ const encoder = new TextEncoder();
+ const data = encoder.encode(message);
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
- return hashHex;
-}
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(
+ "",
+ );
+ return hashHex;
+}
export const existsSync = (filename: string): boolean => {
- try {
- Deno.statSync(filename);
- // successful, file or directory must exist
- return true;
- } catch {
- return false;
- }
+ try {
+ Deno.statSync(filename);
+ // successful, file or directory must exist
+ return true;
+ } catch {
+ return false;
+ }
};
-
\ No newline at end of file
diff --git a/src/Types/EServiceKindError.ts b/src/Types/EServiceKindError.ts
new file mode 100644
index 00000000..172ed05f
--- /dev/null
+++ b/src/Types/EServiceKindError.ts
@@ -0,0 +1,4 @@
+export const enum EServiceKindError {
+ RATE_LIMIT = "RATE_LIMITED",
+ NOT_FOUND = "NOT_FOUND",
+}
diff --git a/src/Types/Request.ts b/src/Types/Request.ts
index 5ef517e7..85f1362c 100644
--- a/src/Types/Request.ts
+++ b/src/Types/Request.ts
@@ -1,5 +1,11 @@
+export type GithubError = {
+ message: string;
+ type: string;
+};
+
export type QueryDefaultResponse = {
data: {
data: T;
+ errors: GithubError[];
};
};
diff --git a/src/Types/ServiceError.ts b/src/Types/ServiceError.ts
new file mode 100644
index 00000000..8a0db072
--- /dev/null
+++ b/src/Types/ServiceError.ts
@@ -0,0 +1,20 @@
+import { EServiceKindError } from "./EServiceKindError.ts";
+
+export class ServiceError extends Error {
+ constructor(message: string, kind: EServiceKindError) {
+ super(message);
+ this.name = "ServiceError";
+ this.cause = kind;
+ }
+
+ get code(): number {
+ switch (this.cause) {
+ case EServiceKindError.RATE_LIMIT:
+ return 419;
+ case EServiceKindError.NOT_FOUND:
+ return 404;
+ default:
+ return 400;
+ }
+ }
+}
diff --git a/src/Types/index.ts b/src/Types/index.ts
index 07f4ba9c..0e52b91c 100644
--- a/src/Types/index.ts
+++ b/src/Types/index.ts
@@ -1 +1,3 @@
-export * from './Request.ts'
\ No newline at end of file
+export * from "./Request.ts";
+export * from "./ServiceError.ts";
+export * from "./EServiceKindError.ts";
diff --git a/src/error_page.ts b/src/error_page.ts
index 0f3076a4..160bba49 100644
--- a/src/error_page.ts
+++ b/src/error_page.ts
@@ -14,6 +14,11 @@ export class Error400 extends BaseError {
readonly message = "Bad Request";
}
+export class Error419 extends BaseError {
+ readonly status = 419;
+ readonly message = "Rate Limit Exceeded";
+}
+
export class Error404 extends BaseError {
readonly status = 404;
readonly message = "Not Found";
diff --git a/src/icons.ts b/src/icons.ts
index 8e7dbf7f..7cabc097 100644
--- a/src/icons.ts
+++ b/src/icons.ts
@@ -2,7 +2,7 @@ import { RANK } from "./utils.ts";
import { Theme } from "./theme.ts";
const leafIcon = (laurel: string): string => {
- return `
+ return `
Created by potrace 1.15, written by Peter Selinger 2001-2017
diff --git a/src/pages/Error.ts b/src/pages/Error.ts
new file mode 100644
index 00000000..3e93d3dd
--- /dev/null
+++ b/src/pages/Error.ts
@@ -0,0 +1,23 @@
+import { EServiceKindError, ServiceError } from "../Types/index.ts";
+import { Error400, Error404, Error419 } from "../error_page.ts";
+
+interface ErrorPageProps {
+ error: ServiceError;
+ username: string;
+}
+
+export function ErrorPage({ error, username }: ErrorPageProps) {
+ let cause: Error400 | Error404 | Error419 = new Error400();
+
+ if (error.cause === EServiceKindError.RATE_LIMIT) {
+ cause = new Error419();
+ }
+
+ if (error.cause === EServiceKindError.NOT_FOUND) {
+ cause = new Error404(
+ "Can not find a user with username: " + username,
+ );
+ }
+
+ return cause;
+}
diff --git a/src/theme.ts b/src/theme.ts
index 04c4ac07..977938dd 100644
--- a/src/theme.ts
+++ b/src/theme.ts
@@ -1,4 +1,4 @@
-export const COLORS: {[name: string]: Theme} = {
+export const COLORS: { [name: string]: Theme } = {
default: {
BACKGROUND: "#FFF",
TITLE: "#000",
@@ -550,7 +550,7 @@ export const COLORS: {[name: string]: Theme} = {
DEFAULT_RANK_BASE: "#7f6ceb",
DEFAULT_RANK_SHADOW: "#a598ed",
DEFAULT_RANK_TEXT: "#7f6ceb",
- }
+ },
};
export interface Theme {
@@ -577,4 +577,3 @@ export interface Theme {
DEFAULT_RANK_SHADOW: string;
DEFAULT_RANK_TEXT: string;
}
-
diff --git a/src/trophy.ts b/src/trophy.ts
index 66d9f94f..778f30d1 100644
--- a/src/trophy.ts
+++ b/src/trophy.ts
@@ -1,5 +1,5 @@
-import { getTrophyIcon, getNextRankBar } from "./icons.ts";
-import { CONSTANTS, RANK, abridgeScore, RANK_ORDER } from "./utils.ts";
+import { getNextRankBar, getTrophyIcon } from "./icons.ts";
+import { abridgeScore, CONSTANTS, RANK, RANK_ORDER } from "./utils.ts";
import { Theme } from "./theme.ts";
class RankCondition {
@@ -10,7 +10,6 @@ class RankCondition {
) {}
}
-
export class Trophy {
rankCondition: RankCondition | null = null;
rank: RANK = RANK.UNKNOWN;
@@ -59,14 +58,16 @@ export class Trophy {
const result = progress / distance;
return result;
}
- render(theme: Theme,
+ render(
+ theme: Theme,
x = 0,
y = 0,
panelSize = CONSTANTS.DEFAULT_PANEL_SIZE,
noBackground = CONSTANTS.DEFAULT_NO_BACKGROUND,
noFrame = CONSTANTS.DEFAULT_NO_FRAME,
): string {
- const { BACKGROUND: PRIMARY, TITLE: SECONDARY, TEXT, NEXT_RANK_BAR } = theme;
+ const { BACKGROUND: PRIMARY, TITLE: SECONDARY, TEXT, NEXT_RANK_BAR } =
+ theme;
const nextRankBar = getNextRankBar(
this.title,
this.calculateNextRankPercentage(),
@@ -90,8 +91,8 @@ export class Trophy {
height="${panelSize - 1}"
stroke="#e1e4e8"
fill="${PRIMARY}"
- stroke-opacity="${noFrame ? '0' : '1'}"
- fill-opacity="${noBackground ? '0' : '1'}"
+ stroke-opacity="${noFrame ? "0" : "1"}"
+ fill-opacity="${noBackground ? "0" : "1"}"
/>
${getTrophyIcon(theme, this.rank)}
${this.title}
@@ -103,8 +104,8 @@ export class Trophy {
}
}
-export class MultipleLangTrophy extends Trophy{
- constructor(score: number){
+export class MultipleLangTrophy extends Trophy {
+ constructor(score: number) {
const rankConditions = [
new RankCondition(
RANK.SECRET,
@@ -119,8 +120,8 @@ export class MultipleLangTrophy extends Trophy{
}
}
-export class AllSuperRankTrophy extends Trophy{
- constructor(score: number){
+export class AllSuperRankTrophy extends Trophy {
+ constructor(score: number) {
const rankConditions = [
new RankCondition(
RANK.SECRET,
@@ -131,12 +132,12 @@ export class AllSuperRankTrophy extends Trophy{
super(score, rankConditions);
this.title = "AllSuperRank";
this.filterTitles = ["AllSuperRank"];
- this.bottomMessage = "All S Rank"
+ this.bottomMessage = "All S Rank";
this.hidden = true;
}
}
-export class Joined2020Trophy extends Trophy{
- constructor(score: number){
+export class Joined2020Trophy extends Trophy {
+ constructor(score: number) {
const rankConditions = [
new RankCondition(
RANK.SECRET,
@@ -147,12 +148,12 @@ export class Joined2020Trophy extends Trophy{
super(score, rankConditions);
this.title = "Joined2020";
this.filterTitles = ["Joined2020"];
- this.bottomMessage = "Joined 2020"
+ this.bottomMessage = "Joined 2020";
this.hidden = true;
}
}
-export class AncientAccountTrophy extends Trophy{
- constructor(score: number){
+export class AncientAccountTrophy extends Trophy {
+ constructor(score: number) {
const rankConditions = [
new RankCondition(
RANK.SECRET,
@@ -163,12 +164,12 @@ export class AncientAccountTrophy extends Trophy{
super(score, rankConditions);
this.title = "AncientUser";
this.filterTitles = ["AncientUser"];
- this.bottomMessage = "Before 2010"
+ this.bottomMessage = "Before 2010";
this.hidden = true;
}
}
-export class LongTimeAccountTrophy extends Trophy{
- constructor(score: number){
+export class LongTimeAccountTrophy extends Trophy {
+ constructor(score: number) {
const rankConditions = [
new RankCondition(
RANK.SECRET,
@@ -182,8 +183,8 @@ export class LongTimeAccountTrophy extends Trophy{
this.hidden = true;
}
}
-export class MultipleOrganizationsTrophy extends Trophy{
- constructor(score: number){
+export class MultipleOrganizationsTrophy extends Trophy {
+ constructor(score: number) {
const rankConditions = [
new RankCondition(
RANK.SECRET,
@@ -199,8 +200,8 @@ export class MultipleOrganizationsTrophy extends Trophy{
}
}
-export class OGAccountTrophy extends Trophy{
- constructor(score: number){
+export class OGAccountTrophy extends Trophy {
+ constructor(score: number) {
const rankConditions = [
new RankCondition(
RANK.SECRET,
@@ -211,7 +212,7 @@ export class OGAccountTrophy extends Trophy{
super(score, rankConditions);
this.title = "OGUser";
this.filterTitles = ["OGUser"];
- this.bottomMessage = "Joined 2008"
+ this.bottomMessage = "Joined 2008";
this.hidden = true;
}
}
diff --git a/src/trophy_list.ts b/src/trophy_list.ts
index 7b5b9c51..d8e406f3 100644
--- a/src/trophy_list.ts
+++ b/src/trophy_list.ts
@@ -1,22 +1,22 @@
import {
- Trophy,
- TotalStarTrophy,
+ AllSuperRankTrophy,
+ AncientAccountTrophy,
+ Joined2020Trophy,
+ LongTimeAccountTrophy,
+ MultipleLangTrophy,
+ MultipleOrganizationsTrophy,
+ OGAccountTrophy,
TotalCommitTrophy,
TotalFollowerTrophy,
TotalIssueTrophy,
TotalPullRequestTrophy,
TotalRepositoryTrophy,
TotalReviewsTrophy,
- MultipleLangTrophy,
- LongTimeAccountTrophy,
- AncientAccountTrophy,
- OGAccountTrophy,
- Joined2020Trophy,
- AllSuperRankTrophy,
- MultipleOrganizationsTrophy,
+ TotalStarTrophy,
+ Trophy,
} from "./trophy.ts";
import { UserInfo } from "./user_info.ts";
-import { RANK_ORDER, RANK } from "./utils.ts";
+import { RANK, RANK_ORDER } from "./utils.ts";
export class TrophyList {
private trophies = new Array();
@@ -49,7 +49,9 @@ export class TrophyList {
return this.trophies;
}
private get isAllSRank() {
- return this.trophies.every((trophy) => trophy.rank.slice(0, 1) == RANK.S) ? 1 : 0;
+ return this.trophies.every((trophy) => trophy.rank.slice(0, 1) == RANK.S)
+ ? 1
+ : 0;
}
filterByHideen() {
this.trophies = this.trophies.filter((trophy) =>
@@ -64,9 +66,9 @@ export class TrophyList {
filterByRanks(ranks: Array) {
if (ranks.filter((rank) => rank.includes("-")).length !== 0) {
this.trophies = this.trophies.filter((trophy) =>
- !ranks.map(rank => rank.substring(1)).includes(trophy.rank)
- )
- return
+ !ranks.map((rank) => rank.substring(1)).includes(trophy.rank)
+ );
+ return;
}
this.trophies = this.trophies.filter((trophy) =>
ranks.includes(trophy.rank)
@@ -78,4 +80,3 @@ export class TrophyList {
);
}
}
-
diff --git a/src/user_info.ts b/src/user_info.ts
index c2e47c3b..7bfdac88 100644
--- a/src/user_info.ts
+++ b/src/user_info.ts
@@ -88,15 +88,18 @@ export class UserInfo {
const joined2020 = new Date(userActivity.createdAt).getFullYear() == 2020
? 1
: 0;
- const ogAccount =
- new Date(userActivity.createdAt).getFullYear() <= 2008 ? 1 : 0;
+ const ogAccount = new Date(userActivity.createdAt).getFullYear() <= 2008
+ ? 1
+ : 0;
this.totalCommits = totalCommits;
this.totalFollowers = userActivity.followers.totalCount;
- this.totalIssues = userIssue.openIssues.totalCount + userIssue.closedIssues.totalCount;
+ this.totalIssues = userIssue.openIssues.totalCount +
+ userIssue.closedIssues.totalCount;
this.totalOrganizations = userActivity.organizations.totalCount;
this.totalPullRequests = userPullRequest.pullRequests.totalCount;
- this.totalReviews = userActivity.contributionsCollection.totalPullRequestReviewContributions;
+ this.totalReviews =
+ userActivity.contributionsCollection.totalPullRequestReviewContributions;
this.totalStargazers = totalStargazers;
this.totalRepositories = userRepository.repositories.totalCount;
this.languageCount = languages.size;
diff --git a/vercel.json b/vercel.json
index 5288cb87..64706355 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,14 +1,14 @@
{
- "public": true,
- "functions": {
- "api/**/*.[jt]s": {
- "runtime": "vercel-deno@3.0.4"
- }
- },
- "rewrites": [
- {
- "source": "/(.*)",
- "destination": "/api/$1"
- }
- ]
-}
\ No newline at end of file
+ "public": true,
+ "functions": {
+ "api/**/*.[jt]s": {
+ "runtime": "vercel-deno@3.0.4"
+ }
+ },
+ "rewrites": [
+ {
+ "source": "/(.*)",
+ "destination": "/api/$1"
+ }
+ ]
+}