diff --git a/backend/vercel.json b/backend/vercel.json index 1b206ef7..69a022e0 100644 --- a/backend/vercel.json +++ b/backend/vercel.json @@ -1,5 +1,5 @@ { - "buildCommand": "npm install && mkdir --parents .vercel/output/functions/api.func/ && cp --recursive ./* .vercel/output/functions/api.func/ && cp --recursive .vercel/output/functions/api.func/_dot_vercel_copy/output/ .vercel/", + "buildCommand": "npm install && ../vercel-preparation.sh", "redirects": [ { "source": "/", diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/config.json b/frontend/frontend/src/backend/_dot_vercel_copy/output/config.json deleted file mode 100644 index cd2f236b..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "version": 3 -} diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json deleted file mode 100644 index 605ee7c0..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "runtime": "nodejs22.x", - "handler": "router.js", - "launcherType": "Nodejs" -} diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.func/router.js b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.func/router.js deleted file mode 100644 index e03265d8..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.func/router.js +++ /dev/null @@ -1,71 +0,0 @@ -import { default as api } from "./api-renamed/index.js"; -import { default as gist } from "./api-renamed/gist.js"; -import { default as pin } from "./api-renamed/pin.js"; -import { default as topLangs } from "./api-renamed/top-langs.js"; -import { default as wakatime } from "./api-renamed/wakatime.js"; -import { default as repeatRecent } from "./api-renamed/repeat-recent.js"; -import { default as patInfo } from "./api-renamed/status/pat-info.js"; -import { default as statusUp } from "./api-renamed/status/up.js"; -import { default as authenticate } from "./api-renamed/authenticate.js"; -import { default as deleteUser } from "./api-renamed/delete-user.js"; -import { default as privateAccess } from "./api-renamed/private-access.js"; -import { default as downgrade } from "./api-renamed/downgrade.js"; - -export default async (req, res) => { - // remaining code expects express.js-like request and response objects - res.send = function (data) { - if (typeof data === "object") { - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify(data)); - } else if (typeof data === "string") { - res.end(data); - } else { - res.end(String(data)); - } - }; - const url = new URL(req.url, "https://localhost"); - req.query = Object.fromEntries(url.searchParams.entries()); - - switch (url.pathname) { - case "/api": - api(req, res); - break; - case "/api/gist": - gist(req, res); - break; - case "/api/pin": - pin(req, res); - break; - case "/api/top-langs": - topLangs(req, res); - break; - case "/api/wakatime": - wakatime(req, res); - break; - case "/api/repeat-recent": - repeatRecent(req, res); - break; - case "/api/status/pat-info": - patInfo(req, res); - break; - case "/api/status/up": - statusUp(req, res); - break; - case "/api/authenticate": - authenticate(req, res); - break; - case "/api/delete-user": - deleteUser(req, res); - break; - case "/api/private-access": - privateAccess(req, res); - break; - case "/api/downgrade": - downgrade(req, res); - break; - default: - res.statusCode = 404; - res.end("Not Found"); - break; - } -}; diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.prerender-config.json b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.prerender-config.json deleted file mode 100644 index d7747ff4..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api.prerender-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "expiration": 39600, - "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", - "passQuery": true -} \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/authenticate.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/authenticate.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/authenticate.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/delete-user.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/delete-user.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/delete-user.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/downgrade.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/downgrade.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/downgrade.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/gist.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/gist.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/gist.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json deleted file mode 100644 index d7747ff4..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/gist.prerender-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "expiration": 39600, - "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", - "passQuery": true -} \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/pin.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/pin.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/pin.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json deleted file mode 100644 index d7747ff4..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/pin.prerender-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "expiration": 39600, - "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", - "passQuery": true -} \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/private-access.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/private-access.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/private-access.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/repeat-recent.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/repeat-recent.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/repeat-recent.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/status/pat-info.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/status/pat-info.func deleted file mode 120000 index 0f4906ce..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/status/pat-info.func +++ /dev/null @@ -1 +0,0 @@ -../../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/status/up.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/status/up.func deleted file mode 120000 index 0f4906ce..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/status/up.func +++ /dev/null @@ -1 +0,0 @@ -../../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/top-langs.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/top-langs.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/top-langs.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json deleted file mode 100644 index d7747ff4..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/top-langs.prerender-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "expiration": 39600, - "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", - "passQuery": true -} \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/wakatime.func b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/wakatime.func deleted file mode 120000 index 2e79e533..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/wakatime.func +++ /dev/null @@ -1 +0,0 @@ -../api.func \ No newline at end of file diff --git a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json b/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json deleted file mode 100644 index d7747ff4..00000000 --- a/frontend/frontend/src/backend/_dot_vercel_copy/output/functions/api/wakatime.prerender-config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "expiration": 39600, - "bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n", - "passQuery": true -} \ No newline at end of file diff --git a/frontend/frontend/src/backend/api-renamed/authenticate.js b/frontend/frontend/src/backend/api-renamed/authenticate.js deleted file mode 100644 index b5852982..00000000 --- a/frontend/frontend/src/backend/api-renamed/authenticate.js +++ /dev/null @@ -1,17 +0,0 @@ -import { logger } from "../src/common/utils.js"; -import { authenticate } from "../src/users.js"; - -/** - * @param {any} req The request. - * @param {any} res The response. - */ -export default async (req, res) => { - const { code, private_access, user_key } = req.query; - try { - let userId = await authenticate(code, private_access === "true", user_key); - res.send(userId); - } catch (err) { - logger.error(err); - res.send("Something went wrong: " + err.message); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/delete-user.js b/frontend/frontend/src/backend/api-renamed/delete-user.js deleted file mode 100644 index 0a561668..00000000 --- a/frontend/frontend/src/backend/api-renamed/delete-user.js +++ /dev/null @@ -1,17 +0,0 @@ -import { logger } from "../src/common/utils.js"; -import { deleteUser } from "../src/common/database.js"; - -/** - * @param {any} req The request. - * @param {any} res The response. - */ -export default async (req, res) => { - const { user_key } = req.query; - try { - await deleteUser(user_key); - } catch (err) { - logger.error(err); - res.send("Something went wrong: " + err.message); - } - res.send("ok"); -}; diff --git a/frontend/frontend/src/backend/api-renamed/downgrade.js b/frontend/frontend/src/backend/api-renamed/downgrade.js deleted file mode 100644 index 34d7714e..00000000 --- a/frontend/frontend/src/backend/api-renamed/downgrade.js +++ /dev/null @@ -1,74 +0,0 @@ -import { hasPrivateAccess, getUserToken, deleteUser } from "../src/common/database.js"; -import axios from "axios"; -import { logger } from "../src/index.js"; - -export default async (req, res) => { - // We could optimize this method by doing all 3 database operations in one statement, using "DELETE ... RETURNING ..." - - const { user_key } = req.query; - if (!user_key) { - res.statusCode = 400; - res.send("missing user_key"); - return; - } - - if ( - !process.env.OAUTH_CLIENT_ID || - !process.env.OAUTH_CLIENT_SECRET || - !process.env.OAUTH_REDIRECT_URI - ) { - throw new Error( - "OAuth Error: One or more required environment variables (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI) are not set.", - ); - } - - // verify that user has private access - const privateAccess = await hasPrivateAccess(user_key); - if (!privateAccess) { - res.statusCode = 400; - res.send("user does not have private access"); - return; - } - - // get access token for user - const token = await getUserToken(user_key); - if (!token) { - res.statusCode = 404; - res.send("user not found"); - return; - } - - // delete existing app authorization via GitHub API - try { - await axios.delete( - `https://api.github.com/applications/${process.env.OAUTH_CLIENT_ID}/grant`, - { - auth: { - username: process.env.OAUTH_CLIENT_ID, - password: process.env.OAUTH_CLIENT_SECRET, - }, - data: { access_token: token }, - headers: { - Accept: "application/vnd.github+json", - }, - }, - ); - } catch (err) { - logger.error(err); - res.statusCode = 500; - res.send("Failed to delete GitHub authorization with private access"); - return; - } - - await deleteUser(user_key); - - // redirect to GitHub OAuth for public access - const params = new URLSearchParams({ - client_id: process.env.OAUTH_CLIENT_ID, - redirect_uri: process.env.OAUTH_REDIRECT_URI, - }).toString(); - - res.statusCode = 302; - res.setHeader("Location", `https://github.com/login/oauth/authorize?${params}`); - res.end(); -}; diff --git a/frontend/frontend/src/backend/api-renamed/gist.js b/frontend/frontend/src/backend/api-renamed/gist.js deleted file mode 100644 index 8f2de341..00000000 --- a/frontend/frontend/src/backend/api-renamed/gist.js +++ /dev/null @@ -1,109 +0,0 @@ -import { - clampValue, - CONSTANTS, - renderError, - parseBoolean, -} from "../src/common/utils.js"; -import { gistWhitelist } from "../src/common/whitelist.js"; -import { isLocaleAvailable } from "../src/translations.js"; -import { renderGistCard } from "../src/cards/gist.js"; -import { fetchGist } from "../src/fetchers/gist.js"; -import { storeRequest } from "../src/common/database.js"; - -export default async (req, res) => { - const { - id, - title_color, - icon_color, - text_color, - bg_color, - theme, - cache_seconds, - locale, - border_radius, - border_color, - show_owner, - hide_border, - } = req.query; - - res.setHeader("Content-Type", "image/svg+xml"); - - if (gistWhitelist && !gistWhitelist.includes(id)) { - return res.send( - renderError( - "This gist ID is not whitelisted", - "Please deploy your own instance", - { - title_color, - text_color, - bg_color, - border_color, - theme, - show_repo_link: false, - }, - ), - ); - } - - if (locale && !isLocaleAvailable(locale)) { - return res.send( - renderError("Something went wrong", "Language not found", { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } - - try { - await storeRequest(req); - const gistData = await fetchGist(id); - - let cacheSeconds = clampValue( - parseInt(cache_seconds || CONSTANTS.TEN_HOURS, 10), - CONSTANTS.FOUR_HOURS, - CONSTANTS.SIX_DAY, - ); - cacheSeconds = process.env.CACHE_SECONDS - ? parseInt(process.env.CACHE_SECONDS, 10) || cacheSeconds - : cacheSeconds; - - res.setHeader( - "Cache-Control", - `max-age=${cacheSeconds}, s-maxage=${cacheSeconds}`, - ); - - return res.send( - renderGistCard(gistData, { - title_color, - icon_color, - text_color, - bg_color, - theme, - border_radius, - border_color, - locale: locale ? locale.toLowerCase() : null, - show_owner: parseBoolean(show_owner), - hide_border: parseBoolean(hide_border), - }), - ); - } catch (err) { - res.setHeader( - "Cache-Control", - `max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${ - CONSTANTS.ERROR_CACHE_SECONDS - }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, - ); // Use lower cache period for errors. - return res.send( - renderError(err.message, err.secondaryMessage, { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/index.js b/frontend/frontend/src/backend/api-renamed/index.js deleted file mode 100644 index a1a7b90b..00000000 --- a/frontend/frontend/src/backend/api-renamed/index.js +++ /dev/null @@ -1,207 +0,0 @@ -import { renderStatsCard } from "../src/cards/stats.js"; -import { blacklist } from "../src/common/blacklist.js"; -import { whitelist } from "../src/common/whitelist.js"; -import { - clampValue, - CONSTANTS, - parseArray, - parseBoolean, - renderError, -} from "../src/common/utils.js"; -import { fetchStats } from "../src/fetchers/stats.js"; -import { isLocaleAvailable } from "../src/translations.js"; -import { storeRequest } from "../src/common/database.js"; - -export default async (req, res) => { - const { - username, - repo, - owner, - hide, - hide_title, - hide_border, - card_width, - hide_rank, - show_icons, - include_all_commits, - line_height, - title_color, - ring_color, - icon_color, - text_color, - text_bold, - bg_color, - theme, - cache_seconds, - exclude_repo, - custom_title, - locale, - disable_animations, - border_radius, - number_format, - role, - border_color, - rank_icon, - show, - } = req.query; - res.setHeader("Content-Type", "image/svg+xml"); - - if (whitelist && !whitelist.includes(username)) { - return res.send( - renderError( - "This username is not whitelisted", - "Please deploy your own instance", - { - title_color, - text_color, - bg_color, - border_color, - theme, - show_repo_link: false, - }, - ), - ); - } - - if (whitelist === undefined && blacklist.includes(username)) { - return res.send( - renderError( - "This username is blacklisted", - "Please deploy your own instance", - { - title_color, - text_color, - bg_color, - border_color, - theme, - show_repo_link: false, - }, - ), - ); - } - - if (locale && !isLocaleAvailable(locale)) { - return res.send( - renderError("Something went wrong", "Language not found", { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } - - const safePattern = /^[-\w\/.,]+$/; - if ( - (username && !safePattern.test(username)) || - (repo && !safePattern.test(repo)) || - (owner && !safePattern.test(owner)) - ) { - return res.send( - renderError( - "Something went wrong", - "Username, repository or owner contains unsafe characters", - { - title_color, - text_color, - bg_color, - border_color, - theme, - }, - ), - ); - } - - try { - await storeRequest(req); - const showStats = parseArray(show); - const repoOwner = parseArray(owner); - let repository = parseArray(repo); - repository = repository.map((repo) => - repo.includes("/") ? repo : `${username}/${repo}`, - ); - - const stats = await fetchStats( - username, - parseBoolean(include_all_commits), - parseArray(exclude_repo), - showStats.includes("prs_merged") || - showStats.includes("prs_merged_percentage"), - showStats.includes("discussions_started"), - showStats.includes("discussions_answered"), - repository, - repoOwner, - showStats.includes("prs_authored"), - showStats.includes("prs_commented"), - showStats.includes("prs_reviewed"), - showStats.includes("issues_authored"), - showStats.includes("issues_commented"), - parseArray(role), - ); - - let cacheSeconds = clampValue( - parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10), - CONSTANTS.FOUR_HOURS, - CONSTANTS.TWO_DAY, - ); - cacheSeconds = process.env.CACHE_SECONDS - ? parseInt(process.env.CACHE_SECONDS, 10) || cacheSeconds - : cacheSeconds; - - res.setHeader( - "Cache-Control", - `max-age=${cacheSeconds}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, - ); - - return res.send( - renderStatsCard( - stats, - { - hide: parseArray(hide), - show_icons: parseBoolean(show_icons), - hide_title: parseBoolean(hide_title), - hide_border: parseBoolean(hide_border), - card_width: parseInt(card_width, 10), - hide_rank: parseBoolean(hide_rank), - include_all_commits: parseBoolean(include_all_commits), - line_height, - title_color, - ring_color, - icon_color, - text_color, - text_bold: parseBoolean(text_bold), - bg_color, - theme, - custom_title, - border_radius, - border_color, - number_format, - locale: locale ? locale.toLowerCase() : null, - disable_animations: parseBoolean(disable_animations), - rank_icon, - show: showStats, - }, - username, - repository, - repoOwner, - ), - ); - } catch (err) { - res.setHeader( - "Cache-Control", - `max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${ - CONSTANTS.ERROR_CACHE_SECONDS - }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, - ); // Use lower cache period for errors. - return res.send( - renderError(err.message, err.secondaryMessage, { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/pin.js b/frontend/frontend/src/backend/api-renamed/pin.js deleted file mode 100644 index 8dc7e8d7..00000000 --- a/frontend/frontend/src/backend/api-renamed/pin.js +++ /dev/null @@ -1,173 +0,0 @@ -import { renderRepoCard } from "../src/cards/repo.js"; -import { blacklist } from "../src/common/blacklist.js"; -import { whitelist } from "../src/common/whitelist.js"; -import { - clampValue, - CONSTANTS, - parseArray, - parseBoolean, - renderError, -} from "../src/common/utils.js"; -import { fetchRepo } from "../src/fetchers/repo.js"; -import { isLocaleAvailable } from "../src/translations.js"; -import { storeRequest } from "../src/common/database.js"; - -export default async (req, res) => { - const { - username, - repo, - hide_border, - title_color, - icon_color, - text_color, - bg_color, - card_width, - theme, - show_owner, - show, - show_icons, - number_format, - text_bold, - line_height, - cache_seconds, - locale, - border_radius, - border_color, - description_lines_count, - } = req.query; - - res.setHeader("Content-Type", "image/svg+xml"); - - if (whitelist && !whitelist.includes(username)) { - return res.send( - renderError( - "This username is not whitelisted", - "Please deploy your own instance", - { - title_color, - text_color, - bg_color, - border_color, - theme, - show_repo_link: false, - }, - ), - ); - } - - if (whitelist === undefined && blacklist.includes(username)) { - return res.send( - renderError( - "This username is blacklisted", - "Please deploy your own instance", - { - title_color, - text_color, - bg_color, - border_color, - theme, - show_repo_link: false, - }, - ), - ); - } - - if (locale && !isLocaleAvailable(locale)) { - return res.send( - renderError("Something went wrong", "Language not found", { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } - - const safePattern = /^[-\w\/.,]+$/; - if ( - (username && !safePattern.test(username)) || - (repo && !safePattern.test(repo)) - ) { - return res.send( - renderError( - "Something went wrong", - "Username or repository contains unsafe characters", - { - title_color, - text_color, - bg_color, - border_color, - theme, - }, - ), - ); - } - - try { - await storeRequest(req); - const showStats = parseArray(show); - const repoData = await fetchRepo( - username, - repo, - showStats.includes("prs_authored"), - showStats.includes("prs_commented"), - showStats.includes("prs_reviewed"), - showStats.includes("issues_authored"), - showStats.includes("issues_commented"), - ); - - let cacheSeconds = clampValue( - parseInt(cache_seconds || CONSTANTS.PIN_CARD_CACHE_SECONDS, 10), - CONSTANTS.FOUR_HOURS, - CONSTANTS.TEN_DAY, - ); - cacheSeconds = process.env.CACHE_SECONDS - ? parseInt(process.env.CACHE_SECONDS, 10) || cacheSeconds - : cacheSeconds; - - res.setHeader( - "Cache-Control", - `max-age=${cacheSeconds}, s-maxage=${cacheSeconds}`, - ); - - return res.send( - renderRepoCard(repoData, { - hide_border: parseBoolean(hide_border), - title_color, - icon_color, - text_color, - bg_color, - theme, - border_radius, - border_color, - card_width_input: parseInt(card_width, 10), - show_owner: parseBoolean(show_owner), - show: showStats, - show_icons: parseBoolean(show_icons), - number_format, - text_bold: parseBoolean(text_bold), - line_height, - username, - locale: locale ? locale.toLowerCase() : null, - description_lines_count, - }), - ); - } catch (err) { - res.setHeader( - "Cache-Control", - `max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${ - CONSTANTS.ERROR_CACHE_SECONDS - }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, - ); // Use lower cache period for errors. - return res.send( - renderError(err.message, err.secondaryMessage, { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/private-access.js b/frontend/frontend/src/backend/api-renamed/private-access.js deleted file mode 100644 index c7957164..00000000 --- a/frontend/frontend/src/backend/api-renamed/private-access.js +++ /dev/null @@ -1,17 +0,0 @@ -import { logger } from "../src/common/utils.js"; -import { hasPrivateAccess } from "../src/common/database.js"; - -/** - * @param {any} req The request. - * @param {any} res The response. - */ -export default async (req, res) => { - const { user_key } = req.query; - try { - const result = await hasPrivateAccess(user_key); - res.send(result); - } catch (err) { - logger.error(err); - res.send("Something went wrong: " + err.message); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/repeat-recent.js b/frontend/frontend/src/backend/api-renamed/repeat-recent.js deleted file mode 100644 index a54ce464..00000000 --- a/frontend/frontend/src/backend/api-renamed/repeat-recent.js +++ /dev/null @@ -1,18 +0,0 @@ -import { repeatRecentRequests } from "../src/repeatRequests.js"; - -export default async (req, res) => { - if (req.method !== "POST") { - res.statusCode = 405; - res.send({ error: "Method Not Allowed" }); - return; - } - try { - await repeatRecentRequests(); - res.statusCode = 200; - res.send({ message: "Recent requests repeated successfully." }); - } catch (error) { - console.error("Error repeating recent requests:", error); - res.statusCode = 500; - res.send({ error: error.message || "Internal Server Error" }); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/status/pat-info.js b/frontend/frontend/src/backend/api-renamed/status/pat-info.js deleted file mode 100644 index 8b15d76b..00000000 --- a/frontend/frontend/src/backend/api-renamed/status/pat-info.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @file Contains a simple cloud function that can be used to check which PATs are no - * longer working. It returns a list of valid PATs, expired PATs and PATs with errors. - * - * @description This function is currently rate limited to 1 request per 3 minutes. - */ - -import { logger, request, dateDiff } from "../../src/common/utils.js"; -export const RATE_LIMIT_SECONDS = 60 * 3; // 1 request per 3 minutes - -/** - * @typedef {import('axios').AxiosRequestHeaders} AxiosRequestHeaders Axios request headers. - * @typedef {import('axios').AxiosResponse} AxiosResponse Axios response. - */ - -/** - * Simple uptime check fetcher for the PATs. - * - * @param {AxiosRequestHeaders} variables Fetcher variables. - * @param {string} token GitHub token. - * @returns {Promise} The response. - */ -const uptimeFetcher = (variables, token) => { - return request( - { - query: ` - query { - rateLimit { - remaining - resetAt - }, - }`, - variables, - }, - { - Authorization: `bearer ${token}`, - }, - ); -}; - -const getAllPATs = () => { - return Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key)); -}; - -/** - * @typedef {(variables: AxiosRequestHeaders, token: string) => Promise} Fetcher The fetcher function. - * @typedef {{validPATs: string[], expiredPATs: string[], exhaustedPATs: string[], suspendedPATs: string[], errorPATs: string[], details: any}} PATInfo The PAT info. - */ - -/** - * Check whether any of the PATs is expired. - * - * @param {Fetcher} fetcher The fetcher function. - * @param {AxiosRequestHeaders} variables Fetcher variables. - * @returns {Promise} The response. - */ -const getPATInfo = async (fetcher, variables) => { - const details = {}; - const PATs = getAllPATs(); - - for (const pat of PATs) { - try { - const response = await fetcher(variables, process.env[pat]); - const errors = response.data.errors; - const hasErrors = Boolean(errors); - const errorType = errors?.[0]?.type; - const isRateLimited = - (hasErrors && errorType === "RATE_LIMITED") || - response.data.data?.rateLimit?.remaining === 0; - - // Store PATs with errors. - if (hasErrors && errorType !== "RATE_LIMITED") { - details[pat] = { - status: "error", - error: { - type: errors[0].type, - message: errors[0].message, - }, - }; - continue; - } else if (isRateLimited) { - const date1 = new Date(); - const date2 = new Date(response.data?.data?.rateLimit?.resetAt); - details[pat] = { - status: "exhausted", - remaining: 0, - resetIn: dateDiff(date2, date1) + " minutes", - }; - } else { - details[pat] = { - status: "valid", - remaining: response.data.data.rateLimit.remaining, - }; - } - } catch (err) { - // Store the PAT if it is expired. - const errorMessage = err.response?.data?.message?.toLowerCase(); - if (errorMessage === "bad credentials") { - details[pat] = { - status: "expired", - }; - } else if (errorMessage === "sorry. your account was suspended.") { - details[pat] = { - status: "suspended", - }; - } else { - throw err; - } - } - } - - const filterPATsByStatus = (status) => { - return Object.keys(details).filter((pat) => details[pat].status === status); - }; - - const sortedDetails = Object.keys(details) - .sort() - .reduce((obj, key) => { - obj[key] = details[key]; - return obj; - }, {}); - - return { - validPATs: filterPATsByStatus("valid"), - expiredPATs: filterPATsByStatus("expired"), - exhaustedPATs: filterPATsByStatus("exhausted"), - suspendedPATs: filterPATsByStatus("suspended"), - errorPATs: filterPATsByStatus("error"), - details: sortedDetails, - }; -}; - -/** - * Cloud function that returns information about the used PATs. - * - * @param {any} _ The request. - * @param {any} res The response. - * @returns {Promise} The response. - */ -export default async (_, res) => { - res.setHeader("Content-Type", "application/json"); - try { - // Add header to prevent abuse. - const PATsInfo = await getPATInfo(uptimeFetcher, {}); - if (PATsInfo) { - res.setHeader( - "Cache-Control", - `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`, - ); - } - res.send(JSON.stringify(PATsInfo, null, 2)); - } catch (err) { - // Throw error if something went wrong. - logger.error(err); - res.setHeader("Cache-Control", "no-store"); - res.send("Something went wrong: " + err.message); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/status/up.js b/frontend/frontend/src/backend/api-renamed/status/up.js deleted file mode 100644 index 168930b7..00000000 --- a/frontend/frontend/src/backend/api-renamed/status/up.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @file Contains a simple cloud function that can be used to check if the PATs are still - * functional. - * - * @description This function is currently rate limited to 1 request per 3 minutes. - */ - -import retryer from "../../src/common/retryer.js"; -import { logger, request } from "../../src/common/utils.js"; - -export const RATE_LIMIT_SECONDS = 60 * 3; // 1 request per 3 minutes - -/** - * @typedef {import('axios').AxiosRequestHeaders} AxiosRequestHeaders Axios request headers. - * @typedef {import('axios').AxiosResponse} AxiosResponse Axios response. - */ - -/** - * Simple uptime check fetcher for the PATs. - * - * @param {AxiosRequestHeaders} variables Fetcher variables. - * @param {string} token GitHub token. - * @returns {Promise} The response. - */ -const uptimeFetcher = (variables, token) => { - return request( - { - query: ` - query { - rateLimit { - remaining - } - } - `, - variables, - }, - { - Authorization: `bearer ${token}`, - }, - ); -}; - -/** - * @typedef {{ - * schemaVersion: number; - * label: string; - * message: "up" | "down"; - * color: "brightgreen" | "red"; - * isError: boolean - * }} ShieldsResponse Shields.io response object. - */ - -/** - * Creates Json response that can be used for shields.io dynamic card generation. - * - * @param {boolean} up Whether the PATs are up or not. - * @returns {ShieldsResponse} Dynamic shields.io JSON response object. - * - * @see https://shields.io/endpoint. - */ -const shieldsUptimeBadge = (up) => { - const schemaVersion = 1; - const isError = true; - const label = "Public Instance"; - const message = up ? "up" : "down"; - const color = up ? "brightgreen" : "red"; - return { - schemaVersion, - label, - message, - color, - isError, - }; -}; - -/** - * Cloud function that returns whether the PATs are still functional. - * - * @param {any} req The request. - * @param {any} res The response. - * @returns {Promise} Nothing. - */ -export default async (req, res) => { - let { type } = req.query; - type = type ? type.toLowerCase() : "boolean"; - - res.setHeader("Content-Type", "application/json"); - - try { - let PATsValid = true; - try { - await retryer(uptimeFetcher, {}); - } catch (err) { - // Resolve eslint no-unused-vars - err; - - PATsValid = false; - } - - if (PATsValid) { - res.setHeader( - "Cache-Control", - `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`, - ); - } else { - res.setHeader("Cache-Control", "no-store"); - } - - switch (type) { - case "shields": - res.send(shieldsUptimeBadge(PATsValid)); - break; - case "json": - res.send({ up: PATsValid }); - break; - default: - res.send(PATsValid); - break; - } - } catch (err) { - // Return fail boolean if something went wrong. - logger.error(err); - res.setHeader("Cache-Control", "no-store"); - res.send("Something went wrong: " + err.message); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/top-langs.js b/frontend/frontend/src/backend/api-renamed/top-langs.js deleted file mode 100644 index 46672cf4..00000000 --- a/frontend/frontend/src/backend/api-renamed/top-langs.js +++ /dev/null @@ -1,151 +0,0 @@ -import { renderTopLanguages } from "../src/cards/top-languages.js"; -import { blacklist } from "../src/common/blacklist.js"; -import { whitelist } from "../src/common/whitelist.js"; -import { - clampValue, - CONSTANTS, - parseArray, - parseBoolean, - renderError, -} from "../src/common/utils.js"; -import { fetchTopLanguages } from "../src/fetchers/top-languages.js"; -import { isLocaleAvailable } from "../src/translations.js"; -import { storeRequest } from "../src/common/database.js"; - -export default async (req, res) => { - const { - username, - hide, - hide_title, - hide_border, - card_width, - title_color, - text_color, - bg_color, - theme, - cache_seconds, - layout, - langs_count, - exclude_repo, - size_weight, - count_weight, - custom_title, - locale, - border_radius, - border_color, - role, - disable_animations, - hide_progress, - } = req.query; - res.setHeader("Content-Type", "image/svg+xml"); - - if (whitelist && !whitelist.includes(username)) { - return res.send( - renderError( - "This username is not whitelisted", - "Please deploy your own instance", - { - title_color, - text_color, - bg_color, - border_color, - theme, - show_repo_link: false, - }, - ), - ); - } - - if (whitelist === undefined && blacklist.includes(username)) { - return res.send( - renderError( - "This username is blacklisted", - "Please deploy your own instance", - { - title_color, - text_color, - bg_color, - border_color, - theme, - show_repo_link: false, - }, - ), - ); - } - - if (locale && !isLocaleAvailable(locale)) { - return res.send(renderError("Something went wrong", "Locale not found")); - } - - if ( - layout !== undefined && - (typeof layout !== "string" || - !["compact", "normal", "donut", "donut-vertical", "pie"].includes(layout)) - ) { - return res.send( - renderError("Something went wrong", "Incorrect layout input"), - ); - } - - try { - await storeRequest(req); - const topLangs = await fetchTopLanguages( - username, - parseArray(exclude_repo), - size_weight, - count_weight, - parseArray(role), - ); - - let cacheSeconds = clampValue( - parseInt(cache_seconds || CONSTANTS.TOP_LANGS_CACHE_SECONDS, 10), - CONSTANTS.FOUR_HOURS, - CONSTANTS.TEN_DAY, - ); - cacheSeconds = process.env.CACHE_SECONDS - ? parseInt(process.env.CACHE_SECONDS, 10) || cacheSeconds - : cacheSeconds; - - res.setHeader( - "Cache-Control", - `max-age=${cacheSeconds / 2}, s-maxage=${cacheSeconds}`, - ); - - return res.send( - renderTopLanguages(topLangs, { - custom_title, - hide_title: parseBoolean(hide_title), - hide_border: parseBoolean(hide_border), - card_width: parseInt(card_width, 10), - hide: parseArray(hide), - title_color, - text_color, - bg_color, - theme, - layout, - langs_count, - border_radius, - border_color, - locale: locale ? locale.toLowerCase() : null, - disable_animations: parseBoolean(disable_animations), - hide_progress: parseBoolean(hide_progress), - }), - ); - } catch (err) { - res.setHeader( - "Cache-Control", - `max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${ - CONSTANTS.ERROR_CACHE_SECONDS - }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, - ); // Use lower cache period for errors. - return res.send( - renderError(err.message, err.secondaryMessage, { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } -}; diff --git a/frontend/frontend/src/backend/api-renamed/wakatime.js b/frontend/frontend/src/backend/api-renamed/wakatime.js deleted file mode 100644 index 32c501a4..00000000 --- a/frontend/frontend/src/backend/api-renamed/wakatime.js +++ /dev/null @@ -1,131 +0,0 @@ -import { renderWakatimeCard } from "../src/cards/wakatime.js"; -import { - clampValue, - CONSTANTS, - parseArray, - parseBoolean, - renderError, -} from "../src/common/utils.js"; -import { whitelist } from "../src/common/whitelist.js"; -import { fetchWakatimeStats } from "../src/fetchers/wakatime.js"; -import { isLocaleAvailable } from "../src/translations.js"; -import { storeRequest } from "../src/common/database.js"; - -export default async (req, res) => { - const { - username, - title_color, - icon_color, - hide_border, - card_width, - line_height, - text_color, - bg_color, - theme, - cache_seconds, - hide_title, - hide_progress, - custom_title, - locale, - layout, - langs_count, - hide, - api_domain, - border_radius, - border_color, - display_format, - disable_animations, - } = req.query; - - res.setHeader("Content-Type", "image/svg+xml"); - - if (whitelist && !whitelist.includes(username)) { - return res.send( - renderError( - "This username is not whitelisted", - "Please deploy your own instance", - { - title_color, - text_color, - bg_color, - border_color, - theme, - show_repo_link: false, - }, - ), - ); - } - - if (locale && !isLocaleAvailable(locale)) { - return res.send( - renderError("Something went wrong", "Language not found", { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } - - try { - await storeRequest(req); - const stats = await fetchWakatimeStats({ username, api_domain }); - - let cacheSeconds = clampValue( - parseInt(cache_seconds || CONSTANTS.CARD_CACHE_SECONDS, 10), - CONSTANTS.FOUR_HOURS, - CONSTANTS.TWO_DAY, - ); - cacheSeconds = process.env.CACHE_SECONDS - ? parseInt(process.env.CACHE_SECONDS, 10) || cacheSeconds - : cacheSeconds; - - res.setHeader( - "Cache-Control", - `max-age=${ - cacheSeconds / 2 - }, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, - ); - - return res.send( - renderWakatimeCard(stats, { - custom_title, - hide_title: parseBoolean(hide_title), - hide_border: parseBoolean(hide_border), - card_width: parseInt(card_width, 10), - hide: parseArray(hide), - line_height, - title_color, - icon_color, - text_color, - bg_color, - theme, - hide_progress, - border_radius, - border_color, - locale: locale ? locale.toLowerCase() : null, - layout, - langs_count, - display_format, - disable_animations: parseBoolean(disable_animations), - }), - ); - } catch (err) { - res.setHeader( - "Cache-Control", - `max-age=${CONSTANTS.ERROR_CACHE_SECONDS / 2}, s-maxage=${ - CONSTANTS.ERROR_CACHE_SECONDS - }, stale-while-revalidate=${CONSTANTS.ONE_DAY}`, - ); // Use lower cache period for errors. - return res.send( - renderError(err.message, err.secondaryMessage, { - title_color, - text_color, - bg_color, - border_color, - theme, - }), - ); - } -}; diff --git a/frontend/frontend/src/backend/src/calculateRank.js b/frontend/frontend/src/backend/src/calculateRank.js deleted file mode 100644 index 4724d038..00000000 --- a/frontend/frontend/src/backend/src/calculateRank.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Calculates the exponential cdf. - * - * @param {number} x The value. - * @returns {number} The exponential cdf. - */ -function exponential_cdf(x) { - return 1 - 2 ** -x; -} - -/** - * Calculates the log normal cdf. - * - * @param {number} x The value. - * @returns {number} The log normal cdf. - */ -function log_normal_cdf(x) { - // approximation - return x / (1 + x); -} - -/** - * Calculates the users rank. - * - * @param {object} params Parameters on which the user's rank depends. - * @param {boolean} params.all_commits Whether `include_all_commits` was used. - * @param {number} params.commits Number of commits. - * @param {number} params.prs The number of pull requests. - * @param {number} params.issues The number of issues. - * @param {number} params.reviews The number of reviews. - * @param {number} params.repos Total number of repos. - * @param {number} params.stars The number of stars. - * @param {number} params.followers The number of followers. - * @returns {{level: string, percentile: number}}} The users rank. - */ -function calculateRank({ - all_commits, - commits, - prs, - issues, - reviews, - // eslint-disable-next-line no-unused-vars - repos, // unused - stars, - followers, -}) { - const COMMITS_MEDIAN = all_commits ? 1000 : 250, - COMMITS_WEIGHT = 2; - const PRS_MEDIAN = 50, - PRS_WEIGHT = 3; - const ISSUES_MEDIAN = 25, - ISSUES_WEIGHT = 1; - const REVIEWS_MEDIAN = 2, - REVIEWS_WEIGHT = 1; - const STARS_MEDIAN = 50, - STARS_WEIGHT = 4; - const FOLLOWERS_MEDIAN = 10, - FOLLOWERS_WEIGHT = 1; - - const TOTAL_WEIGHT = - COMMITS_WEIGHT + - PRS_WEIGHT + - ISSUES_WEIGHT + - REVIEWS_WEIGHT + - STARS_WEIGHT + - FOLLOWERS_WEIGHT; - - const THRESHOLDS = [1, 12.5, 25, 37.5, 50, 62.5, 75, 87.5, 100]; - const LEVELS = ["S", "A+", "A", "A-", "B+", "B", "B-", "C+", "C"]; - - const rank = - 1 - - (COMMITS_WEIGHT * exponential_cdf(commits / COMMITS_MEDIAN) + - PRS_WEIGHT * exponential_cdf(prs / PRS_MEDIAN) + - ISSUES_WEIGHT * exponential_cdf(issues / ISSUES_MEDIAN) + - REVIEWS_WEIGHT * exponential_cdf(reviews / REVIEWS_MEDIAN) + - STARS_WEIGHT * log_normal_cdf(stars / STARS_MEDIAN) + - FOLLOWERS_WEIGHT * log_normal_cdf(followers / FOLLOWERS_MEDIAN)) / - TOTAL_WEIGHT; - - const level = LEVELS[THRESHOLDS.findIndex((t) => rank * 100 <= t)]; - - return { level, percentile: rank * 100 }; -} - -export { calculateRank }; -export default calculateRank; diff --git a/frontend/frontend/src/backend/src/cards/gist.js b/frontend/frontend/src/backend/src/cards/gist.js deleted file mode 100644 index 9e889e74..00000000 --- a/frontend/frontend/src/backend/src/cards/gist.js +++ /dev/null @@ -1,152 +0,0 @@ -// @ts-check - -import { - getCardColors, - parseEmojis, - wrapTextMultiline, - encodeHTML, - kFormatter, - measureText, - flexLayout, - iconWithLabel, - createLanguageNode, -} from "../common/utils.js"; -import Card from "../common/Card.js"; -import { icons } from "../common/icons.js"; - -/** Import language colors. - * - * @description Here we use the workaround found in - * https://stackoverflow.com/questions/66726365/how-should-i-import-json-in-node - * since vercel is using v16.14.0 which does not yet support json imports without the - * --experimental-json-modules flag. - */ -import { createRequire } from "module"; -const require = createRequire(import.meta.url); -const languageColors = require("../common/languageColors.json"); // now works - -const ICON_SIZE = 16; -const CARD_DEFAULT_WIDTH = 400; -const HEADER_MAX_LENGTH = 35; - -/** - * @typedef {import('./types').GistCardOptions} GistCardOptions Gist card options. - * @typedef {import('../fetchers/types').GistData} GistData Gist data. - */ - -/** - * Render gist card. - * - * @param {GistData} gistData Gist data. - * @param {Partial} options Gist card options. - * @returns {string} Gist card. - */ -const renderGistCard = (gistData, options = {}) => { - const { name, nameWithOwner, description, language, starsCount, forksCount } = - gistData; - const { - title_color, - icon_color, - text_color, - bg_color, - theme, - border_radius, - border_color, - show_owner = false, - hide_border = false, - } = options; - - // returns theme based colors with proper overrides and defaults - const { titleColor, textColor, iconColor, bgColor, borderColor } = - getCardColors({ - title_color, - icon_color, - text_color, - bg_color, - border_color, - theme, - }); - - const lineWidth = 59; - const linesLimit = 10; - const desc = parseEmojis(description || "No description provided"); - const multiLineDescription = wrapTextMultiline(desc, lineWidth, linesLimit); - const descriptionLines = multiLineDescription.length; - const descriptionSvg = multiLineDescription - .map((line) => `${encodeHTML(line)}`) - .join(""); - - const lineHeight = descriptionLines > 3 ? 12 : 10; - const height = - (descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight; - - const totalStars = kFormatter(starsCount); - const totalForks = kFormatter(forksCount); - const svgStars = iconWithLabel( - icons.star, - totalStars, - "starsCount", - ICON_SIZE, - ); - const svgForks = iconWithLabel( - icons.fork, - totalForks, - "forksCount", - ICON_SIZE, - ); - - const languageName = language || "Unspecified"; - const languageColor = languageColors[languageName] || "#858585"; - - const svgLanguage = createLanguageNode(languageName, languageColor); - - const starAndForkCount = flexLayout({ - items: [svgLanguage, svgStars, svgForks], - sizes: [ - measureText(languageName, 12), - ICON_SIZE + measureText(`${totalStars}`, 12), - ICON_SIZE + measureText(`${totalForks}`, 12), - ], - gap: 25, - }).join(""); - - const header = show_owner ? nameWithOwner : name; - - const card = new Card({ - defaultTitle: - header.length > HEADER_MAX_LENGTH - ? `${header.slice(0, HEADER_MAX_LENGTH)}...` - : header, - titlePrefixIcon: icons.gist, - width: CARD_DEFAULT_WIDTH, - height, - border_radius, - colors: { - titleColor, - textColor, - iconColor, - bgColor, - borderColor, - }, - }); - - card.setCSS(` - .description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} } - .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} } - .icon { fill: ${iconColor} } - `); - card.setHideBorder(hide_border); - - return card.render(` - - ${descriptionSvg} - - - - ${starAndForkCount} - - `); -}; - -export { renderGistCard, HEADER_MAX_LENGTH }; -export default renderGistCard; diff --git a/frontend/frontend/src/backend/src/cards/index.js b/frontend/frontend/src/backend/src/cards/index.js deleted file mode 100644 index 5ca3a97a..00000000 --- a/frontend/frontend/src/backend/src/cards/index.js +++ /dev/null @@ -1,4 +0,0 @@ -export { renderRepoCard } from "./repo.js"; -export { renderStatsCard } from "./stats.js"; -export { renderTopLanguages } from "./top-languages.js"; -export { renderWakatimeCard } from "./wakatime.js"; diff --git a/frontend/frontend/src/backend/src/cards/repo.js b/frontend/frontend/src/backend/src/cards/repo.js deleted file mode 100644 index 52010548..00000000 --- a/frontend/frontend/src/backend/src/cards/repo.js +++ /dev/null @@ -1,326 +0,0 @@ -// @ts-check -import { Card } from "../common/Card.js"; -import { I18n } from "../common/I18n.js"; -import { icons } from "../common/icons.js"; -import { - encodeHTML, - flexLayout, - getCardColors, - kFormatter, - measureText, - parseEmojis, - wrapTextMultiline, - iconWithLabel, - createLanguageNode, - clampValue, - buildSearchFilter, -} from "../common/utils.js"; -import { repoCardLocales } from "../translations.js"; -import { createTextNode } from "./stats.js"; - -const ICON_SIZE = 16; -const DESCRIPTION_LINE_WIDTH = 59; -const DESCRIPTION_MAX_LINES = 3; - -/** - * Retrieves the repository description and wraps it to fit the card width. - * - * @param {string} label The repository description. - * @param {string} textColor The color of the text. - * @returns {string} Wrapped repo description SVG object. - */ -const getBadgeSVG = (label, textColor, xOffset = 0) => ` - - - - ${label} - - -`; - -/** - * @typedef {import("../fetchers/types").RepositoryData} RepositoryData Repository data. - * @typedef {import("./types").RepoCardOptions} RepoCardOptions Repo card options. - */ - -/** - * Renders repository card details. - * - * @param {RepositoryData} repo Repository data. - * @param {Partial} options Card options. - * @returns {string} Repository card SVG object. - */ -const renderRepoCard = (repo, options = {}) => { - const { - name, - nameWithOwner, - description, - primaryLanguage, - isArchived, - isTemplate, - starCount, - forkCount, - totalPRsAuthored, - totalPRsCommented, - totalPRsReviewed, - totalIssuesAuthored, - totalIssuesCommented, - } = repo; - const { - hide_border = false, - title_color, - icon_color, - text_color, - bg_color, - card_width_input, - show_owner = false, - show = [], - show_icons = true, - number_format = "short", - text_bold = false, - line_height = 22, - username, - theme = "default_repocard", - border_radius, - border_color, - locale, - description_lines_count, - } = options; - - const card_width = - card_width_input && !isNaN(card_width_input) - ? card_width_input - : show.length >= 2 - ? 430 - : 400; - - const i18n = new I18n({ - locale, - translations: repoCardLocales, - }); - - let repoFilter = encodeURIComponent(buildSearchFilter([nameWithOwner], [])); - const STATS = {}; - if (show.includes("prs_authored")) { - STATS.prs_authored = { - icon: icons.prs, - label: i18n.t("repocard.prs-authored"), - value: totalPRsAuthored, - id: "prs_authored", - link: `https://github.com/search?q=${repoFilter}author%3A${username}&type=pullrequests`, - }; - } - if (show.includes("prs_commented")) { - STATS.prs_commented = { - icon: icons.comments, - label: i18n.t("repocard.prs-commented"), - value: totalPRsCommented, - id: "prs_commented", - link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&type=pullrequests`, - }; - } - if (show.includes("prs_reviewed")) { - STATS.prs_reviewed = { - icon: icons.reviews, - label: i18n.t("repocard.prs-reviewed"), - value: totalPRsReviewed, - id: "prs_reviewed", - link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${username}+-author%3A${username}&type=pullrequests`, - }; - } - if (show.includes("issues_authored")) { - STATS.issues_authored = { - icon: icons.issues, - label: i18n.t("repocard.issues-authored"), - value: totalIssuesAuthored, - id: "issues_authored", - link: `https://github.com/search?q=${repoFilter}author%3A${username}&type=issues`, - }; - } - if (show.includes("issues_commented")) { - STATS.issues_commented = { - icon: icons.discussions_started, - label: i18n.t("repocard.issues-commented"), - value: totalIssuesCommented, - id: "issues_commented", - link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&type=issues`, - }; - } - - const statItems = Object.keys(STATS).map((key, index) => - // create the text nodes, and pass index so that we can calculate the line spacing - createTextNode({ - icon: STATS[key].icon, - label: STATS[key].label, - value: STATS[key].value, - id: STATS[key].id, - unitSymbol: STATS[key].unitSymbol, - index, - showIcons: show_icons, - shiftValuePos: 14.01, - bold: text_bold, - number_format, - link: STATS[key].link, - labelXOffset: 23, - }), - ); - - const extraLHeight = parseInt(String(line_height), 10); - const lineHeight = 10; - const header = show_owner ? nameWithOwner : name; - const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified"; - const langColor = (primaryLanguage && primaryLanguage.color) || "#333"; - const descriptionMaxLines = description_lines_count - ? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES) - : DESCRIPTION_MAX_LINES; - - const desc = parseEmojis(description || "No description provided"); - const multiLineDescription = wrapTextMultiline( - desc, - Math.round((card_width - 400) / 5.93 + DESCRIPTION_LINE_WIDTH), - descriptionMaxLines, - ); - const descriptionLinesCount = description_lines_count - ? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES) - : multiLineDescription.length; - - const descriptionSvg = multiLineDescription - .map((line) => `${encodeHTML(line)}`) - .join(""); - - const extraHeight = Object.keys(STATS).length - ? -7 + (Math.ceil(statItems.length / 2) + 1) * extraLHeight - : 0; - const height = - (descriptionLinesCount > 1 ? 120 : 110) + - descriptionLinesCount * lineHeight + - extraHeight; - - // returns theme based colors with proper overrides and defaults - const colors = getCardColors({ - title_color, - icon_color, - text_color, - bg_color, - border_color, - theme, - }); - - const svgLanguage = primaryLanguage - ? createLanguageNode(langName, langColor) - : ""; - - const totalStars = kFormatter(starCount); - const totalForks = kFormatter(forkCount); - const svgStars = iconWithLabel( - icons.star, - totalStars, - "stargazers", - ICON_SIZE, - ); - const svgForks = iconWithLabel( - icons.fork, - totalForks, - "forkcount", - ICON_SIZE, - ); - - const starAndForkCount = flexLayout({ - items: [svgLanguage, svgStars, svgForks], - sizes: [ - measureText(langName, 12), - ICON_SIZE + measureText(`${totalStars}`, 12), - ICON_SIZE + measureText(`${totalForks}`, 12), - ], - gap: 25, - }).join(""); - - let extraRows = []; - for (let i = 0; i < statItems.length; i += 2) { - extraRows.push( - flexLayout({ - items: statItems.slice(i, i + 2), - gap: 210, - direction: "row", - }).join(""), - ); - } - const extraItems = ` - - ${flexLayout({ - items: extraRows, - gap: extraLHeight, - direction: "column", - }).join("")} - - `; - - const card = new Card({ - defaultTitle: header.length > 35 ? `${header.slice(0, 35)}...` : header, - titlePrefixIcon: icons.contribs, - width: card_width, - height, - border_radius, - colors, - }); - - card.disableAnimations(); - card.setHideBorder(hide_border); - card.setHideTitle(false); - card.setCSS(` - .description { font: 400 13px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} } - .gray { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} } - .badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; } - .badge rect { opacity: 0.2 } - - .stat { font: 400 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} } - .stagger { - opacity: 0; - animation: fadeInAnimation 0.3s ease-in-out forwards; - } - .not_bold { font-weight: 400 } - .bold { font-weight: 700 } - .icon { - fill: ${colors.iconColor}; - display: block; - } - `); - - return card.render(` - ${ - isTemplate - ? // @ts-ignore - getBadgeSVG( - i18n.t("repocard.template"), - colors.textColor, - card_width - 400, - ) - : isArchived - ? // @ts-ignore - getBadgeSVG( - i18n.t("repocard.archived"), - colors.textColor, - card_width - 400, - ) - : "" - } - - - ${descriptionSvg} - - - - ${starAndForkCount} - - ${extraItems} - `); -}; - -export { renderRepoCard }; -export default renderRepoCard; diff --git a/frontend/frontend/src/backend/src/cards/stats.js b/frontend/frontend/src/backend/src/cards/stats.js deleted file mode 100644 index d92b2199..00000000 --- a/frontend/frontend/src/backend/src/cards/stats.js +++ /dev/null @@ -1,616 +0,0 @@ -// @ts-check -import { Card } from "../common/Card.js"; -import { I18n } from "../common/I18n.js"; -import { icons, rankIcon } from "../common/icons.js"; -import { - CustomError, - clampValue, - flexLayout, - getCardColors, - kFormatter, - measureText, - buildSearchFilter, -} from "../common/utils.js"; -import { statCardLocales } from "../translations.js"; - -const CARD_MIN_WIDTH = 287; -const CARD_DEFAULT_WIDTH = 287; -const RANK_CARD_MIN_WIDTH = 420; -const RANK_CARD_DEFAULT_WIDTH = 450; -const RANK_ONLY_CARD_MIN_WIDTH = 290; -const RANK_ONLY_CARD_DEFAULT_WIDTH = 290; - -/** - * Create a stats card text item. - * - * @param {object} createTextNodeParams Object that contains the createTextNode parameters. - * @param {string} createTextNodeParams.icon The icon to display. - * @param {string} createTextNodeParams.label The label to display. - * @param {number} createTextNodeParams.value The value to display. - * @param {string} createTextNodeParams.id The id of the stat. - * @param {string=} createTextNodeParams.unitSymbol The unit symbol of the stat. - * @param {number} createTextNodeParams.index The index of the stat. - * @param {boolean} createTextNodeParams.showIcons Whether to show icons. - * @param {number} createTextNodeParams.shiftValuePos Number of pixels the value has to be shifted to the right. - * @param {boolean} createTextNodeParams.bold Whether to bold the label. - * @param {string} createTextNodeParams.number_format The format of numbers on card. - * @param {string} createTextNodeParams.link Url to link to. - * @param {number} createTextNodeParams.labelXOffset horizontal offset for label. - * @returns {string} The stats card text item SVG object. - */ -const createTextNode = ({ - icon, - label, - value, - id, - unitSymbol, - index, - showIcons, - shiftValuePos, - bold, - number_format, - link, - labelXOffset = 25, -}) => { - const kValue = - number_format.toLowerCase() === "long" ? value : kFormatter(value); - const staggerDelay = (index + 3) * 150; - - const labelOffset = showIcons ? `x="${labelXOffset}"` : ""; - const iconSvg = showIcons - ? ` - - ${icon} - - ` - : ""; - return ( - ` - ` + - (link ? `` : "") + - ` - ${iconSvg} - ${label}: - ${kValue}${unitSymbol ? ` ${unitSymbol}` : ""}` + - (link ? "" : "") + - ` - - ` - ); -}; - -/** - * Calculates progress along the boundary of the circle, i.e. its circumference. - * - * @param {number} value The rank value to calculate progress for. - * @returns {number} Progress value. - */ -const calculateCircleProgress = (value) => { - const radius = 40; - const c = Math.PI * (radius * 2); - - if (value < 0) { - value = 0; - } - if (value > 100) { - value = 100; - } - - return ((100 - value) / 100) * c; -}; - -/** - * Retrieves the animation to display progress along the circumference of circle - * from the beginning to the given value in a clockwise direction. - * - * @param {{progress: number}} progress The progress value to animate to. - * @returns {string} Progress animation css. - */ -const getProgressAnimation = ({ progress }) => { - return ` - @keyframes rankAnimation { - from { - stroke-dashoffset: ${calculateCircleProgress(0)}; - } - to { - stroke-dashoffset: ${calculateCircleProgress(progress)}; - } - } - `; -}; - -/** - * Retrieves CSS styles for a card. - * - * @param {Object} colors The colors to use for the card. - * @param {string} colors.titleColor The title color. - * @param {string} colors.textColor The text color. - * @param {string} colors.iconColor The icon color. - * @param {string} colors.ringColor The ring color. - * @param {boolean} colors.show_icons Whether to show icons. - * @param {number} colors.progress The progress value to animate to. - * @returns {string} Card CSS styles. - */ -const getStyles = ({ - // eslint-disable-next-line no-unused-vars - titleColor, - textColor, - iconColor, - ringColor, - show_icons, - progress, -}) => { - return ` - .stat { - font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor}; - } - @supports(-moz-appearance: auto) { - /* Selector detects Firefox */ - .stat { font-size:12px; } - } - .stagger { - opacity: 0; - animation: fadeInAnimation 0.3s ease-in-out forwards; - } - .rank-text { - font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor}; - animation: scaleInAnimation 0.3s ease-in-out forwards; - } - .rank-percentile-header { - font-size: 14px; - } - .rank-percentile-text { - font-size: 16px; - } - - .not_bold { font-weight: 400 } - .bold { font-weight: 700 } - .icon { - fill: ${iconColor}; - display: ${show_icons ? "block" : "none"}; - } - - .rank-circle-rim { - stroke: ${ringColor}; - fill: none; - stroke-width: 6; - opacity: 0.2; - } - .rank-circle { - stroke: ${ringColor}; - stroke-dasharray: 250; - fill: none; - stroke-width: 6; - stroke-linecap: round; - opacity: 0.8; - transform-origin: -10px 8px; - transform: rotate(-90deg); - animation: rankAnimation 1s forwards ease-in-out; - } - ${process.env.NODE_ENV === "test" ? "" : getProgressAnimation({ progress })} - `; -}; - -/** - * @typedef {import('../fetchers/types').StatsData} StatsData - * @typedef {import('./types').StatCardOptions} StatCardOptions - */ - -/** - * Renders the stats card. - * - * @param {StatsData} stats The stats data. - * @param {Partial} options The card options. - * @returns {string} The stats card SVG object. - */ -const renderStatsCard = ( - stats, - options = {}, - username, - repo = [], - owner = [], -) => { - const { - name, - totalStars, - totalCommits, - totalIssues, - totalPRs, - totalPRsMerged, - mergedPRsPercentage, - totalReviews, - totalDiscussionsStarted, - totalDiscussionsAnswered, - contributedTo, - totalPRsAuthored, - totalPRsCommented, - totalPRsReviewed, - totalIssuesAuthored, - totalIssuesCommented, - rank, - } = stats; - const { - hide = [], - show_icons = false, - hide_title = false, - hide_border = false, - card_width, - hide_rank = false, - include_all_commits = false, - line_height = 25, - title_color, - ring_color, - icon_color, - text_color, - text_bold = true, - bg_color, - theme = "default", - custom_title, - border_radius, - border_color, - number_format = "short", - locale, - disable_animations = false, - rank_icon = "default", - show = [], - } = options; - - const lheight = parseInt(String(line_height), 10); - - // returns theme based colors with proper overrides and defaults - const { titleColor, iconColor, textColor, bgColor, borderColor, ringColor } = - getCardColors({ - title_color, - text_color, - icon_color, - bg_color, - border_color, - ring_color, - theme, - }); - - const apostrophe = /s$/i.test(name.trim()) ? "" : "s"; - const i18n = new I18n({ - locale, - translations: statCardLocales({ name, apostrophe }), - }); - - // Meta data for creating text nodes with createTextNode function - const STATS = {}; - - STATS.stars = { - icon: icons.star, - label: i18n.t("statcard.totalstars"), - value: totalStars, - id: "stars", - }; - STATS.commits = { - icon: icons.commits, - label: `${i18n.t("statcard.commits")}${ - include_all_commits ? "" : ` (${new Date().getFullYear()})` - }`, - value: totalCommits, - id: "commits", - }; - STATS.prs = { - icon: icons.prs, - label: i18n.t("statcard.prs"), - value: totalPRs, - id: "prs", - }; - - if (show.includes("prs_merged")) { - STATS.prs_merged = { - icon: icons.prs_merged, - label: i18n.t("statcard.prs-merged"), - value: totalPRsMerged, - id: "prs_merged", - }; - } - - if (show.includes("prs_merged_percentage")) { - STATS.prs_merged_percentage = { - icon: icons.prs_merged_percentage, - label: i18n.t("statcard.prs-merged-percentage"), - value: mergedPRsPercentage.toFixed(2), - id: "prs_merged_percentage", - unitSymbol: "%", - }; - } - - if (show.includes("reviews")) { - STATS.reviews = { - icon: icons.reviews, - label: i18n.t("statcard.reviews"), - value: totalReviews, - id: "reviews", - }; - } - - STATS.issues = { - icon: icons.issues, - label: i18n.t("statcard.issues"), - value: totalIssues, - id: "issues", - }; - - if (show.includes("discussions_started")) { - STATS.discussions_started = { - icon: icons.discussions_started, - label: i18n.t("statcard.discussions-started"), - value: totalDiscussionsStarted, - id: "discussions_started", - }; - } - if (show.includes("discussions_answered")) { - STATS.discussions_answered = { - icon: icons.discussions_answered, - label: i18n.t("statcard.discussions-answered"), - value: totalDiscussionsAnswered, - id: "discussions_answered", - }; - } - - let repoFilter = encodeURIComponent(buildSearchFilter(repo, owner)); - if (show.includes("prs_authored")) { - STATS.prs_authored = { - icon: icons.prs, - label: i18n.t("statcard.prs-authored"), - value: totalPRsAuthored, - id: "prs_authored", - link: `https://github.com/search?q=${repoFilter}author%3A${username}&type=pullrequests`, - }; - } - if (show.includes("prs_commented")) { - STATS.prs_commented = { - icon: icons.comments, - label: i18n.t("statcard.prs-commented"), - value: totalPRsCommented, - id: "prs_commented", - link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&type=pullrequests`, - }; - } - if (show.includes("prs_reviewed")) { - STATS.prs_reviewed = { - icon: icons.reviews, - label: i18n.t("statcard.prs-reviewed"), - value: totalPRsReviewed, - id: "prs_reviewed", - link: `https://github.com/search?q=${repoFilter}reviewed-by%3A${username}+-author%3A${username}&type=pullrequests`, - }; - } - if (show.includes("issues_authored")) { - STATS.issues_authored = { - icon: icons.issues, - label: i18n.t("statcard.issues-authored"), - value: totalIssuesAuthored, - id: "issues_authored", - link: `https://github.com/search?q=${repoFilter}author%3A${username}&type=issues`, - }; - } - if (show.includes("issues_commented")) { - STATS.issues_commented = { - icon: icons.discussions_started, - label: i18n.t("statcard.issues-commented"), - value: totalIssuesCommented, - id: "issues_commented", - link: `https://github.com/search?q=${repoFilter}commenter%3A${username}+-author%3A${username}&type=issues`, - }; - } - - STATS.contribs = { - icon: icons.contribs, - label: i18n.t("statcard.contribs"), - value: contributedTo, - id: "contribs", - }; - - const longLocales = [ - "cn", - "es", - "fr", - "pt-br", - "ru", - "uk-ua", - "id", - "ml", - "my", - "pl", - "de", - "nl", - "zh-tw", - "uz", - ]; - const isLongLocale = locale ? longLocales.includes(locale) : false; - - // check if all used labels are short - const longLabels = - Object.keys(STATS) - .filter((key) => !hide.includes(key)) - .filter((key) => STATS[key].label.length > 18).length > 0; - - // filter out hidden stats defined by user & create the text nodes - const statItems = Object.keys(STATS) - .filter((key) => !hide.includes(key)) - .map((key, index) => - // create the text nodes, and pass index so that we can calculate the line spacing - createTextNode({ - icon: STATS[key].icon, - label: STATS[key].label, - value: STATS[key].value, - id: STATS[key].id, - unitSymbol: STATS[key].unitSymbol, - index, - showIcons: show_icons, - shiftValuePos: 29.01 + (longLabels ? 50 : 0) + (isLongLocale ? 50 : 0), - bold: text_bold, - number_format, - link: STATS[key].link, - }), - ); - - if (statItems.length === 0 && hide_rank) { - throw new CustomError( - "Could not render stats card.", - "Either stats or rank are required.", - ); - } - - // Calculate the card height depending on how many items there are - // but if rank circle is visible clamp the minimum height to `150` - let height = Math.max( - 45 + (statItems.length + 1) * lheight, - hide_rank ? 0 : statItems.length ? 150 : 180, - ); - - // the lower the user's percentile the better - const progress = 100 - rank.percentile; - const cssStyles = getStyles({ - titleColor, - ringColor, - textColor, - iconColor, - show_icons, - progress, - }); - - const calculateTextWidth = () => { - return measureText( - custom_title - ? custom_title - : statItems.length - ? i18n.t("statcard.title") - : i18n.t("statcard.ranktitle"), - ); - }; - - /* - When hide_rank=true, the minimum card width is 270 px + the title length and padding. - When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true). - Numbers are picked by looking at existing dimensions on production. - */ - const iconWidth = show_icons && statItems.length ? 16 + /* padding */ 1 : 0; - const minCardWidth = - (hide_rank - ? clampValue( - 50 /* padding */ + calculateTextWidth() * 2, - CARD_MIN_WIDTH, - Infinity, - ) - : statItems.length - ? RANK_CARD_MIN_WIDTH - : RANK_ONLY_CARD_MIN_WIDTH) + iconWidth; - const defaultCardWidth = - (hide_rank - ? CARD_DEFAULT_WIDTH - : statItems.length - ? RANK_CARD_DEFAULT_WIDTH - : RANK_ONLY_CARD_DEFAULT_WIDTH) + iconWidth; - let width = card_width - ? isNaN(card_width) - ? Math.max(defaultCardWidth, minCardWidth) - : card_width - : Math.max(defaultCardWidth, minCardWidth); - - const card = new Card({ - customTitle: custom_title, - defaultTitle: statItems.length - ? i18n.t("statcard.title") - : i18n.t("statcard.ranktitle"), - width, - height, - border_radius, - colors: { - titleColor, - textColor, - iconColor, - bgColor, - borderColor, - }, - }); - - card.setHideBorder(hide_border); - card.setHideTitle(hide_title); - card.setCSS(cssStyles); - - if (disable_animations) { - card.disableAnimations(); - } - - /** - * Calculates the right rank circle translation values such that the rank circle - * keeps respecting the following padding: - * - * width > RANK_CARD_DEFAULT_WIDTH: The default right padding of 70 px will be used. - * width < RANK_CARD_DEFAULT_WIDTH: The left and right padding will be enlarged - * equally from a certain minimum at RANK_CARD_MIN_WIDTH. - * - * @returns {number} - Rank circle translation value. - */ - const calculateRankXTranslation = () => { - if (statItems.length) { - const minXTranslation = RANK_CARD_MIN_WIDTH + iconWidth - 70; - if (width > RANK_CARD_DEFAULT_WIDTH) { - const xMaxExpansion = minXTranslation + (450 - minCardWidth) / 2; - return xMaxExpansion + width - RANK_CARD_DEFAULT_WIDTH; - } else { - return minXTranslation + (width - minCardWidth) / 2; - } - } else { - return width / 2 + 20 - 10; - } - }; - - // Conditionally rendered elements - const rankCircle = hide_rank - ? "" - : ` - - - - ${rankIcon(rank_icon, rank?.level, rank?.percentile)} - - `; - - // Accessibility Labels - const labels = Object.keys(STATS) - .filter((key) => !hide.includes(key)) - .map((key) => { - if (key === "commits") { - return `${i18n.t("statcard.commits")} ${ - include_all_commits ? "" : `in ${new Date().getFullYear()}` - } : ${STATS[key].value}`; - } - return `${STATS[key].label}: ${STATS[key].value}`; - }) - .join(", "); - - card.setAccessibilityLabel({ - title: `${card.title}, Rank: ${rank.level}`, - desc: labels, - }); - - return card.render(` - ${rankCircle} - - ${flexLayout({ - items: statItems, - gap: lheight, - direction: "column", - }).join("")} - - `); -}; - -export { renderStatsCard, createTextNode }; -export default renderStatsCard; diff --git a/frontend/frontend/src/backend/src/cards/top-languages.js b/frontend/frontend/src/backend/src/cards/top-languages.js deleted file mode 100644 index 9385f4a7..00000000 --- a/frontend/frontend/src/backend/src/cards/top-languages.js +++ /dev/null @@ -1,890 +0,0 @@ -// @ts-check -import { Card } from "../common/Card.js"; -import { createProgressNode } from "../common/createProgressNode.js"; -import { I18n } from "../common/I18n.js"; -import { - chunkArray, - clampValue, - flexLayout, - getCardColors, - lowercaseTrim, - measureText, -} from "../common/utils.js"; -import { langCardLocales } from "../translations.js"; - -const DEFAULT_CARD_WIDTH = 300; -const MIN_CARD_WIDTH = 280; -const DEFAULT_LANG_COLOR = "#858585"; -const CARD_PADDING = 25; -const COMPACT_LAYOUT_BASE_HEIGHT = 90; -const MAXIMUM_LANGS_COUNT = 20; - -const NORMAL_LAYOUT_DEFAULT_LANGS_COUNT = 5; -const COMPACT_LAYOUT_DEFAULT_LANGS_COUNT = 6; -const DONUT_LAYOUT_DEFAULT_LANGS_COUNT = 5; -const PIE_LAYOUT_DEFAULT_LANGS_COUNT = 6; -const DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT = 6; - -/** - * @typedef {import("../fetchers/types").Lang} Lang - */ - -/** - * Retrieves the programming language whose name is the longest. - * - * @param {Lang[]} arr Array of programming languages. - * @returns {{ name: string, size: number, color: string }} Longest programming language object. - */ -const getLongestLang = (arr) => - arr.reduce( - (savedLang, lang) => - lang.name.length > savedLang.name.length ? lang : savedLang, - { name: "", size: 0, color: "" }, - ); - -/** - * Convert degrees to radians. - * - * @param {number} angleInDegrees Angle in degrees. - * @returns {number} Angle in radians. - */ -const degreesToRadians = (angleInDegrees) => angleInDegrees * (Math.PI / 180.0); - -/** - * Convert radians to degrees. - * - * @param {number} angleInRadians Angle in radians. - * @returns {number} Angle in degrees. - */ -const radiansToDegrees = (angleInRadians) => angleInRadians / (Math.PI / 180.0); - -/** - * Convert polar coordinates to cartesian coordinates. - * - * @param {number} centerX Center x coordinate. - * @param {number} centerY Center y coordinate. - * @param {number} radius Radius of the circle. - * @param {number} angleInDegrees Angle in degrees. - * @returns {{x: number, y: number}} Cartesian coordinates. - */ -const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => { - const rads = degreesToRadians(angleInDegrees); - return { - x: centerX + radius * Math.cos(rads), - y: centerY + radius * Math.sin(rads), - }; -}; - -/** - * Convert cartesian coordinates to polar coordinates. - * - * @param {number} centerX Center x coordinate. - * @param {number} centerY Center y coordinate. - * @param {number} x Point x coordinate. - * @param {number} y Point y coordinate. - * @returns {{radius: number, angleInDegrees: number}} Polar coordinates. - */ -const cartesianToPolar = (centerX, centerY, x, y) => { - const radius = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)); - let angleInDegrees = radiansToDegrees(Math.atan2(y - centerY, x - centerX)); - if (angleInDegrees < 0) { - angleInDegrees += 360; - } - return { radius, angleInDegrees }; -}; - -/** - * Calculates length of circle. - * - * @param {number} radius Radius of the circle. - * @returns {number} The length of the circle. - */ -const getCircleLength = (radius) => { - return 2 * Math.PI * radius; -}; - -/** - * Calculates height for the compact layout. - * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. - */ -const calculateCompactLayoutHeight = (totalLangs) => { - return COMPACT_LAYOUT_BASE_HEIGHT + Math.round(totalLangs / 2) * 25; -}; - -/** - * Calculates height for the normal layout. - * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. - */ -const calculateNormalLayoutHeight = (totalLangs) => { - return 45 + (totalLangs + 1) * 40; -}; - -/** - * Calculates height for the donut layout. - * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. - */ -const calculateDonutLayoutHeight = (totalLangs) => { - return 215 + Math.max(totalLangs - 5, 0) * 32; -}; - -/** - * Calculates height for the donut vertical layout. - * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. - */ -const calculateDonutVerticalLayoutHeight = (totalLangs) => { - return 300 + Math.round(totalLangs / 2) * 25; -}; - -/** - * Calculates height for the pie layout. - * - * @param {number} totalLangs Total number of languages. - * @returns {number} Card height. - */ -const calculatePieLayoutHeight = (totalLangs) => { - return 300 + Math.round(totalLangs / 2) * 25; -}; - -/** - * Calculates the center translation needed to keep the donut chart centred. - * @param {number} totalLangs Total number of languages. - * @returns {number} Donut center translation. - */ -const donutCenterTranslation = (totalLangs) => { - return -45 + Math.max(totalLangs - 5, 0) * 16; -}; - -/** - * Trim top languages to lang_count while also hiding certain languages. - * - * @param {Record} topLangs Top languages. - * @param {number} langs_count Number of languages to show. - * @param {string[]=} hide Languages to hide. - * @returns {{ langs: Lang[], totalLanguageSize: number }} Trimmed top languages and total size. - */ -const trimTopLanguages = (topLangs, langs_count, hide) => { - let langs = Object.values(topLangs); - let langsToHide = {}; - let langsCount = clampValue(langs_count, 1, MAXIMUM_LANGS_COUNT); - - // populate langsToHide map for quick lookup - // while filtering out - if (hide) { - hide.forEach((langName) => { - langsToHide[lowercaseTrim(langName)] = true; - }); - } - - // filter out languages to be hidden - langs = langs - .sort((a, b) => b.size - a.size) - .filter((lang) => { - return !langsToHide[lowercaseTrim(lang.name)]; - }) - .slice(0, langsCount); - - const totalLanguageSize = langs.reduce((acc, curr) => acc + curr.size, 0); - - return { langs, totalLanguageSize }; -}; - -/** - * Create progress bar text item for a programming language. - * - * @param {object} props Function properties. - * @param {number} props.width The card width - * @param {string} props.color Color of the programming language. - * @param {string} props.name Name of the programming language. - * @param {number} props.progress Usage of the programming language in percentage. - * @param {number} props.index Index of the programming language. - * @returns {string} Programming language SVG node. - */ -const createProgressTextNode = ({ width, color, name, progress, index }) => { - const staggerDelay = (index + 3) * 150; - const paddingRight = 95; - const progressTextX = width - paddingRight + 10; - const progressWidth = width - paddingRight; - - return ` - - ${name} - ${progress}% - ${createProgressNode({ - x: 0, - y: 25, - color, - width: progressWidth, - progress, - progressBarBackgroundColor: "#ddd", - delay: staggerDelay + 300, - })} - - `; -}; - -/** - * Creates compact text item for a programming language. - * - * @param {object} props Function properties. - * @param {Lang} props.lang Programming language object. - * @param {number} props.totalSize Total size of all languages. - * @param {boolean=} props.hideProgress Whether to hide percentage. - * @param {number} props.index Index of the programming language. - * @returns {string} Compact layout programming language SVG node. - */ -const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => { - const percentage = ((lang.size / totalSize) * 100).toFixed(2); - const staggerDelay = (index + 3) * 150; - const color = lang.color || "#858585"; - - return ` - - - - ${lang.name} ${hideProgress ? "" : percentage + "%"} - - - `; -}; - -/** - * Create compact languages text items for all programming languages. - * - * @param {object} props Function properties. - * @param {Lang[]} props.langs Array of programming languages. - * @param {number} props.totalSize Total size of all languages. - * @param {boolean=} props.hideProgress Whether to hide percentage. - * @returns {string} Programming languages SVG node. - */ -const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => { - const longestLang = getLongestLang(langs); - const chunked = chunkArray(langs, langs.length / 2); - const layouts = chunked.map((array) => { - // @ts-ignore - const items = array.map((lang, index) => - createCompactLangNode({ - lang, - totalSize, - hideProgress, - index, - }), - ); - return flexLayout({ - items, - gap: 25, - direction: "column", - }).join(""); - }); - - const percent = ((longestLang.size / totalSize) * 100).toFixed(2); - const minGap = 150; - const maxGap = 20 + measureText(`${longestLang.name} ${percent}%`, 11); - return flexLayout({ - items: layouts, - gap: maxGap < minGap ? minGap : maxGap, - }).join(""); -}; - -/** - * Create donut languages text items for all programming languages. - * - * @param {object} props Function properties. - * @param {Lang[]} props.langs Array of programming languages. - * @param {number} props.totalSize Total size of all languages. - * @returns {string} Donut layout programming language SVG node. - */ -const createDonutLanguagesNode = ({ langs, totalSize }) => { - return flexLayout({ - items: langs.map((lang, index) => { - return createCompactLangNode({ - lang, - totalSize, - hideProgress: false, - index, - }); - }), - gap: 32, - direction: "column", - }).join(""); -}; - -/** - * Renders the default language card layout. - * - * @param {Lang[]} langs Array of programming languages. - * @param {number} width Card width. - * @param {number} totalLanguageSize Total size of all languages. - * @returns {string} Normal layout card SVG object. - */ -const renderNormalLayout = (langs, width, totalLanguageSize) => { - return flexLayout({ - items: langs.map((lang, index) => { - return createProgressTextNode({ - width, - name: lang.name, - color: lang.color || DEFAULT_LANG_COLOR, - progress: parseFloat( - ((lang.size / totalLanguageSize) * 100).toFixed(2), - ), - index, - }); - }), - gap: 40, - direction: "column", - }).join(""); -}; - -/** - * Renders the compact language card layout. - * - * @param {Lang[]} langs Array of programming languages. - * @param {number} width Card width. - * @param {number} totalLanguageSize Total size of all languages. - * @param {boolean=} hideProgress Whether to hide progress bar. - * @returns {string} Compact layout card SVG object. - */ -const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => { - const paddingRight = 50; - const offsetWidth = width - paddingRight; - // progressOffset holds the previous language's width and used to offset the next language - // so that we can stack them one after another, like this: [--][----][---] - let progressOffset = 0; - const compactProgressBar = langs - .map((lang) => { - const percentage = parseFloat( - ((lang.size / totalLanguageSize) * offsetWidth).toFixed(2), - ); - - const progress = percentage < 10 ? percentage + 10 : percentage; - - const output = ` - - `; - progressOffset += percentage; - return output; - }) - .join(""); - - return ` - ${ - hideProgress - ? "" - : ` - - - - ${compactProgressBar} - ` - } - - ${createLanguageTextNode({ - langs, - totalSize: totalLanguageSize, - hideProgress, - })} - - `; -}; - -/** - * Renders donut vertical layout to display user's most frequently used programming languages. - * - * @param {Lang[]} langs Array of programming languages. - * @param {number} totalLanguageSize Total size of all languages. - * @returns {string} Compact layout card SVG object. - */ -const renderDonutVerticalLayout = (langs, totalLanguageSize) => { - // Donut vertical chart radius and total length - const radius = 80; - const totalCircleLength = getCircleLength(radius); - - // SVG circles - let circles = []; - - // Start indent for donut vertical chart parts - let indent = 0; - - // Start delay coefficient for donut vertical chart parts - let startDelayCoefficient = 1; - - // Generate each donut vertical chart part - for (const lang of langs) { - const percentage = (lang.size / totalLanguageSize) * 100; - const circleLength = totalCircleLength * (percentage / 100); - const delay = startDelayCoefficient * 100; - - circles.push(` - - - - `); - - // Update the indent for the next part - indent += circleLength; - // Update the start delay coefficient for the next part - startDelayCoefficient += 1; - } - - return ` - - - - ${circles.join("")} - - - - - ${createLanguageTextNode({ - langs, - totalSize: totalLanguageSize, - hideProgress: false, - })} - - - - `; -}; - -/** - * Renders pie layout to display user's most frequently used programming languages. - * - * @param {Lang[]} langs Array of programming languages. - * @param {number} totalLanguageSize Total size of all languages. - * @returns {string} Compact layout card SVG object. - */ -const renderPieLayout = (langs, totalLanguageSize) => { - // Pie chart radius and center coordinates - const radius = 90; - const centerX = 150; - const centerY = 100; - - // Start angle for the pie chart parts - let startAngle = 0; - - // Start delay coefficient for the pie chart parts - let startDelayCoefficient = 1; - - // SVG paths - const paths = []; - - // Generate each pie chart part - for (const lang of langs) { - if (langs.length === 1) { - paths.push(` - - `); - break; - } - - const langSizePart = lang.size / totalLanguageSize; - const percentage = langSizePart * 100; - // Calculate the angle for the current part - const angle = langSizePart * 360; - - // Calculate the end angle - const endAngle = startAngle + angle; - - // Calculate the coordinates of the start and end points of the arc - const startPoint = polarToCartesian(centerX, centerY, radius, startAngle); - const endPoint = polarToCartesian(centerX, centerY, radius, endAngle); - - // Determine the large arc flag based on the angle - const largeArcFlag = angle > 180 ? 1 : 0; - - // Calculate delay - const delay = startDelayCoefficient * 100; - - // SVG arc markup - paths.push(` - - - - `); - - // Update the start angle for the next part - startAngle = endAngle; - // Update the start delay coefficient for the next part - startDelayCoefficient += 1; - } - - return ` - - - - ${paths.join("")} - - - - - ${createLanguageTextNode({ - langs, - totalSize: totalLanguageSize, - hideProgress: false, - })} - - - - `; -}; - -/** - * Creates the SVG paths for the language donut chart. - * - * @param {number} cx Donut center x-position. - * @param {number} cy Donut center y-position. - * @param {number} radius Donut arc Radius. - * @param {number[]} percentages Array with donut section percentages. - * @returns {{d: string, percent: number}[]} Array of svg path elements - */ -const createDonutPaths = (cx, cy, radius, percentages) => { - const paths = []; - let startAngle = 0; - let endAngle = 0; - - const totalPercent = percentages.reduce((acc, curr) => acc + curr, 0); - for (let i = 0; i < percentages.length; i++) { - const tmpPath = {}; - - let percent = parseFloat( - ((percentages[i] / totalPercent) * 100).toFixed(2), - ); - - endAngle = 3.6 * percent + startAngle; - const startPoint = polarToCartesian(cx, cy, radius, endAngle - 90); // rotate donut 90 degrees counter-clockwise. - const endPoint = polarToCartesian(cx, cy, radius, startAngle - 90); // rotate donut 90 degrees counter-clockwise. - const largeArc = endAngle - startAngle <= 180 ? 0 : 1; - - tmpPath.percent = percent; - tmpPath.d = `M ${startPoint.x} ${startPoint.y} A ${radius} ${radius} 0 ${largeArc} 0 ${endPoint.x} ${endPoint.y}`; - - paths.push(tmpPath); - startAngle = endAngle; - } - - return paths; -}; - -/** - * Renders the donut language card layout. - * - * @param {Lang[]} langs Array of programming languages. - * @param {number} width Card width. - * @param {number} totalLanguageSize Total size of all languages. - * @returns {string} Donut layout card SVG object. - */ -const renderDonutLayout = (langs, width, totalLanguageSize) => { - const centerX = width / 3; - const centerY = width / 3; - const radius = centerX - 60; - const strokeWidth = 12; - - const colors = langs.map((lang) => lang.color); - const langsPercents = langs.map((lang) => - parseFloat(((lang.size / totalLanguageSize) * 100).toFixed(2)), - ); - - const langPaths = createDonutPaths(centerX, centerY, radius, langsPercents); - - const donutPaths = - langs.length === 1 - ? `` - : langPaths - .map((section, index) => { - const staggerDelay = (index + 3) * 100; - const delay = staggerDelay + 300; - - const output = ` - - - - - `; - - return output; - }) - .join(""); - - const donut = `${donutPaths}`; - - return ` - - - ${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize })} - - - - ${donut} - - - `; -}; - -/** - * @typedef {import("./types").TopLangOptions} TopLangOptions - * @typedef {TopLangOptions["layout"]} Layout - */ - -/** - * Creates the no languages data SVG node. - * - * @param {object} props Object with function properties. - * @param {string} props.color No languages data text color. - * @param {string} props.text No languages data translated text. - * @param {Layout | undefined} props.layout Card layout. - * @returns {string} No languages data SVG node string. - */ -const noLanguagesDataNode = ({ color, text, layout }) => { - return ` - ${text} - `; -}; - -/** - * Get default languages count for provided card layout. - * - * @param {object} props Function properties. - * @param {Layout=} props.layout Input layout string. - * @param {boolean=} props.hide_progress Input hide_progress parameter value. - * @returns {number} Default languages count for input layout. - */ -const getDefaultLanguagesCountByLayout = ({ layout, hide_progress }) => { - if (layout === "compact" || hide_progress === true) { - return COMPACT_LAYOUT_DEFAULT_LANGS_COUNT; - } else if (layout === "donut") { - return DONUT_LAYOUT_DEFAULT_LANGS_COUNT; - } else if (layout === "donut-vertical") { - return DONUT_VERTICAL_LAYOUT_DEFAULT_LANGS_COUNT; - } else if (layout === "pie") { - return PIE_LAYOUT_DEFAULT_LANGS_COUNT; - } else { - return NORMAL_LAYOUT_DEFAULT_LANGS_COUNT; - } -}; - -/** - * @typedef {import('../fetchers/types').TopLangData} TopLangData - */ - -/** - * Renders card that display user's most frequently used programming languages. - * - * @param {TopLangData} topLangs User's most frequently used programming languages. - * @param {Partial} options Card options. - * @returns {string} Language card SVG object. - */ -const renderTopLanguages = (topLangs, options = {}) => { - const { - hide_title = false, - hide_border = false, - card_width, - title_color, - text_color, - bg_color, - hide, - hide_progress, - theme, - layout, - custom_title, - locale, - langs_count = getDefaultLanguagesCountByLayout({ layout, hide_progress }), - border_radius, - border_color, - disable_animations, - } = options; - - const i18n = new I18n({ - locale, - translations: langCardLocales, - }); - - const { langs, totalLanguageSize } = trimTopLanguages( - topLangs, - langs_count, - hide, - ); - - let width = card_width - ? isNaN(card_width) - ? DEFAULT_CARD_WIDTH - : card_width < MIN_CARD_WIDTH - ? MIN_CARD_WIDTH - : card_width - : DEFAULT_CARD_WIDTH; - let height = calculateNormalLayoutHeight(langs.length); - - // returns theme based colors with proper overrides and defaults - const colors = getCardColors({ - title_color, - text_color, - bg_color, - border_color, - theme, - }); - - let finalLayout = ""; - if (langs.length === 0) { - height = COMPACT_LAYOUT_BASE_HEIGHT; - finalLayout = noLanguagesDataNode({ - color: colors.textColor, - text: i18n.t("langcard.nodata"), - layout, - }); - } else if (layout === "pie") { - height = calculatePieLayoutHeight(langs.length); - finalLayout = renderPieLayout(langs, totalLanguageSize); - } else if (layout === "donut-vertical") { - height = calculateDonutVerticalLayoutHeight(langs.length); - finalLayout = renderDonutVerticalLayout(langs, totalLanguageSize); - } else if (layout === "compact" || hide_progress == true) { - height = - calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0); - - finalLayout = renderCompactLayout( - langs, - width, - totalLanguageSize, - hide_progress, - ); - } else if (layout === "donut") { - height = calculateDonutLayoutHeight(langs.length); - width = width + 50; // padding - finalLayout = renderDonutLayout(langs, width, totalLanguageSize); - } else { - finalLayout = renderNormalLayout(langs, width, totalLanguageSize); - } - - const card = new Card({ - customTitle: custom_title, - defaultTitle: i18n.t("langcard.title"), - width, - height, - border_radius, - colors, - }); - - if (disable_animations) { - card.disableAnimations(); - } - - card.setHideBorder(hide_border); - card.setHideTitle(hide_title); - card.setCSS( - ` - @keyframes slideInAnimation { - from { - width: 0; - } - to { - width: calc(100%-100px); - } - } - @keyframes growWidthAnimation { - from { - width: 0; - } - to { - width: 100%; - } - } - .stat { - font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${colors.textColor}; - } - @supports(-moz-appearance: auto) { - /* Selector detects Firefox */ - .stat { font-size:12px; } - } - .bold { font-weight: 700 } - .lang-name { - font: 400 11px "Segoe UI", Ubuntu, Sans-Serif; - fill: ${colors.textColor}; - } - .stagger { - opacity: 0; - animation: fadeInAnimation 0.3s ease-in-out forwards; - } - #rect-mask rect{ - animation: slideInAnimation 1s ease-in-out forwards; - } - .lang-progress{ - animation: growWidthAnimation 0.6s ease-in-out forwards; - } - `, - ); - - if (layout === "pie" || layout === "donut-vertical") { - return card.render(finalLayout); - } - - return card.render(` - - ${finalLayout} - - `); -}; - -export { - getLongestLang, - degreesToRadians, - radiansToDegrees, - polarToCartesian, - cartesianToPolar, - getCircleLength, - calculateCompactLayoutHeight, - calculateNormalLayoutHeight, - calculateDonutLayoutHeight, - calculateDonutVerticalLayoutHeight, - calculatePieLayoutHeight, - donutCenterTranslation, - trimTopLanguages, - renderTopLanguages, - MIN_CARD_WIDTH, - getDefaultLanguagesCountByLayout, -}; diff --git a/frontend/frontend/src/backend/src/cards/types.d.ts b/frontend/frontend/src/backend/src/cards/types.d.ts deleted file mode 100644 index 4549d3c8..00000000 --- a/frontend/frontend/src/backend/src/cards/types.d.ts +++ /dev/null @@ -1,70 +0,0 @@ -type ThemeNames = keyof typeof import("../../themes/index.js"); -type RankIcon = "default" | "github" | "percentile"; - -export type CommonOptions = { - title_color: string; - icon_color: string; - text_color: string; - bg_color: string; - theme: ThemeNames; - border_radius: number; - border_color: string; - locale: string; - hide_border: boolean; -}; - -export type StatCardOptions = CommonOptions & { - hide: string[]; - show_icons: boolean; - hide_title: boolean; - card_width: number; - hide_rank: boolean; - include_all_commits: boolean; - line_height: number | string; - custom_title: string; - disable_animations: boolean; - number_format: string; - ring_color: string; - text_bold: boolean; - rank_icon: RankIcon; - show: string[]; -}; - -export type RepoCardOptions = CommonOptions & { - show_owner: boolean; - description_lines_count: number; - card_width_input; - show: string[]; - show_icons: boolean; - number_format: string; - text_bold: boolean; - line_height: number | string; - username; -}; - -export type TopLangOptions = CommonOptions & { - hide_title: boolean; - card_width: number; - hide: string[]; - layout: "compact" | "normal" | "donut" | "donut-vertical" | "pie"; - custom_title: string; - langs_count: number; - disable_animations: boolean; - hide_progress: boolean; -}; - -export type WakaTimeOptions = CommonOptions & { - hide_title: boolean; - hide: string[]; - line_height: string; - hide_progress: boolean; - custom_title: string; - layout: "compact" | "normal"; - langs_count: number; - display_format: "time" | "percent"; - disable_animations: boolean; -}; - -export type GistCardOptions = CommonOptions & { - show_owner: boolean; -}; diff --git a/frontend/frontend/src/backend/src/cards/wakatime.js b/frontend/frontend/src/backend/src/cards/wakatime.js deleted file mode 100644 index 9f1404e0..00000000 --- a/frontend/frontend/src/backend/src/cards/wakatime.js +++ /dev/null @@ -1,458 +0,0 @@ -// @ts-check -import { Card } from "../common/Card.js"; -import { createProgressNode } from "../common/createProgressNode.js"; -import { I18n } from "../common/I18n.js"; -import { - clampValue, - flexLayout, - getCardColors, - lowercaseTrim, -} from "../common/utils.js"; -import { wakatimeCardLocales } from "../translations.js"; - -/** Import language colors. - * - * @description Here we use the workaround found in - * https://stackoverflow.com/questions/66726365/how-should-i-import-json-in-node - * since vercel is using v16.14.0 which does not yet support json imports without the - * --experimental-json-modules flag. - */ -import { createRequire } from "module"; -const require = createRequire(import.meta.url); -const languageColors = require("../common/languageColors.json"); // now works - -/** - * Creates the no coding activity SVG node. - * - * @param {object} props The function properties. - * @param {string} props.color No coding activity text color. - * @param {string} props.text No coding activity translated text. - * @returns {string} No coding activity SVG node string. - */ -const noCodingActivityNode = ({ color, text }) => { - return ` - ${text} - `; -}; - -/** - * @typedef {import('../fetchers/types').WakaTimeLang} WakaTimeLang - */ - -/** - * Format language value. - * - * @param {Object} args The function arguments. - * @param {WakaTimeLang} args.lang The language object. - * @param {"time" | "percent"} args.display_format The display format of the language node. - * @returns {string} The formatted language value. - */ -const formatLanguageValue = ({ display_format, lang }) => { - return display_format === "percent" - ? `${lang.percent.toFixed(2).toString()} %` - : lang.text; -}; - -/** - * Create compact WakaTime layout. - * - * @param {Object} args The function arguments. - * @param {WakaTimeLang} args.lang The languages array. - * @param {number} args.x The x position of the language node. - * @param {number} args.y The y position of the language node. - * @param {"time" | "percent"} args.display_format The display format of the language node. - * @returns {string} The compact layout language SVG node. - */ -const createCompactLangNode = ({ lang, x, y, display_format }) => { - const color = languageColors[lang.name] || "#858585"; - const value = formatLanguageValue({ display_format, lang }); - - return ` - - - - ${lang.name} - ${value} - - - `; -}; - -/** - * Create WakaTime language text node item. - * - * @param {Object} args The function arguments. - * @param {WakaTimeLang[]} args.langs The language objects. - * @param {number} args.y The y position of the language node. - * @param {"time" | "percent"} args.display_format The display format of the language node. - * @param {number} args.card_width Width in px of the card. - * @returns {string[]} The language text node items. - */ -const createLanguageTextNode = ({ langs, y, display_format, card_width }) => { - return langs.map((lang, index) => { - if (index % 2 === 0) { - return createCompactLangNode({ - lang, - x: 25, - y: 12.5 * index + y, - display_format, - }); - } - return createCompactLangNode({ - lang, - x: 230 + (card_width - 495) / 2, - y: 12.5 + 12.5 * index, - display_format, - }); - }); -}; - -/** - * Create WakaTime text item. - * - * @param {Object} args The function arguments. - * @param {string} args.id The id of the text node item. - * @param {string} args.label The label of the text node item. - * @param {string} args.value The value of the text node item. - * @param {number} args.index The index of the text node item. - * @param {number} args.percent Percentage of the text node item. - * @param {boolean=} args.hideProgress Whether to hide the progress bar. - * @param {string} args.progressBarColor The color of the progress bar. - * @param {string} args.progressBarBackgroundColor The color of the progress bar background. - * @param {number} args.progressBarWidth The width of the progress bar. - * @returns {string} The text SVG node. - */ -const createTextNode = ({ - id, - label, - value, - index, - percent, - hideProgress, - progressBarColor, - progressBarBackgroundColor, - progressBarWidth, -}) => { - const staggerDelay = (index + 3) * 150; - - const cardProgress = hideProgress - ? null - : createProgressNode({ - x: 110, - y: 4, - progress: percent, - color: progressBarColor, - width: progressBarWidth, - // @ts-ignore - name: label, - progressBarBackgroundColor, - delay: staggerDelay + 300, - }); - - return ` - - ${label}: - ${value} - ${cardProgress} - - `; -}; - -/** - * Recalculating percentages so that, compact layout's progress bar does not break when - * hiding languages. - * - * @param {WakaTimeLang[]} languages The languages array. - * @returns {void} The recalculated languages array. - */ -const recalculatePercentages = (languages) => { - const totalSum = languages.reduce( - (totalSum, language) => totalSum + language.percent, - 0, - ); - const weight = +(100 / totalSum).toFixed(2); - languages.forEach((language) => { - language.percent = +(language.percent * weight).toFixed(2); - }); -}; - -/** - * Retrieves CSS styles for a card. - * - * @param {Object} colors The colors to use for the card. - * @param {string} colors.titleColor The title color. - * @param {string} colors.textColor The text color. - * @returns {string} Card CSS styles. - */ -const getStyles = ({ - // eslint-disable-next-line no-unused-vars - titleColor, - textColor, -}) => { - return ` - .stat { - font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor}; - } - @supports(-moz-appearance: auto) { - /* Selector detects Firefox */ - .stat { font-size:12px; } - } - .stagger { - opacity: 0; - animation: fadeInAnimation 0.3s ease-in-out forwards; - } - .not_bold { font-weight: 400 } - .bold { font-weight: 700 } - `; -}; - -/** - * @typedef {import('../fetchers/types').WakaTimeData} WakaTimeData - * @typedef {import('./types').WakaTimeOptions} WakaTimeOptions - */ - -/** - * Renders WakaTime card. - * - * @param {Partial} stats WakaTime stats. - * @param {Partial} options Card options. - * @returns {string} WakaTime card SVG. - */ -const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { - let { languages = [] } = stats; - let { - hide_title = false, - hide_border = false, - card_width, - hide, - line_height = 25, - title_color, - icon_color, - text_color, - bg_color, - theme = "default", - hide_progress, - custom_title, - locale, - layout, - langs_count = languages.length, - border_radius, - border_color, - display_format = "time", - disable_animations, - } = options; - - if (isNaN(card_width)) { - card_width = 495; - } - - const shouldHideLangs = Array.isArray(hide) && hide.length > 0; - if (shouldHideLangs) { - const languagesToHide = new Set(hide.map((lang) => lowercaseTrim(lang))); - languages = languages.filter( - (lang) => !languagesToHide.has(lowercaseTrim(lang.name)), - ); - } - - // Since the percentages are sorted in descending order, we can just - // slice from the beginning without sorting. - languages = languages.slice(0, langs_count); - recalculatePercentages(languages); - - const i18n = new I18n({ - locale, - translations: wakatimeCardLocales, - }); - - const lheight = parseInt(String(line_height), 10); - - const langsCount = clampValue(langs_count, 1, langs_count); - - // returns theme based colors with proper overrides and defaults - const { titleColor, textColor, iconColor, bgColor, borderColor } = - getCardColors({ - title_color, - icon_color, - text_color, - bg_color, - border_color, - theme, - }); - - const filteredLanguages = languages - .filter((language) => language.hours || language.minutes) - .slice(0, langsCount); - - // Calculate the card height depending on how many items there are - // but if rank circle is visible clamp the minimum height to `150` - let height = Math.max(45 + (filteredLanguages.length + 1) * lheight, 150); - - const cssStyles = getStyles({ - titleColor, - textColor, - }); - - let finalLayout = ""; - - // RENDER COMPACT LAYOUT - if (layout === "compact") { - let width = card_width - 5; - height = 90 + Math.round(filteredLanguages.length / 2) * 25; - - // progressOffset holds the previous language's width and used to offset the next language - // so that we can stack them one after another, like this: [--][----][---] - let progressOffset = 0; - const compactProgressBar = filteredLanguages - .map((language) => { - // const progress = (width * lang.percent) / 100; - const progress = ((width - 25) * language.percent) / 100; - - const languageColor = languageColors[language.name] || "#858585"; - - const output = ` - - `; - progressOffset += progress; - return output; - }) - .join(""); - - finalLayout = ` - - - - ${compactProgressBar} - ${ - filteredLanguages.length - ? createLanguageTextNode({ - y: 25, - langs: filteredLanguages, - display_format, - card_width, - }).join("") - : noCodingActivityNode({ - // @ts-ignore - color: textColor, - text: stats.is_coding_activity_visible - ? stats.is_other_usage_visible - ? i18n.t("wakatimecard.nocodingactivity") - : i18n.t("wakatimecard.nocodedetails") - : i18n.t("wakatimecard.notpublic"), - }) - } - `; - } else { - finalLayout = flexLayout({ - items: filteredLanguages.length - ? filteredLanguages.map((language, index) => { - return createTextNode({ - id: language.name, - label: language.name, - value: formatLanguageValue({ display_format, lang: language }), - index, - percent: language.percent, - // @ts-ignore - progressBarColor: titleColor, - // @ts-ignore - progressBarBackgroundColor: textColor, - hideProgress: hide_progress, - progressBarWidth: card_width - 275, - }); - }) - : [ - noCodingActivityNode({ - // @ts-ignore - color: textColor, - text: stats.is_coding_activity_visible - ? stats.is_other_usage_visible - ? i18n.t("wakatimecard.nocodingactivity") - : i18n.t("wakatimecard.nocodedetails") - : i18n.t("wakatimecard.notpublic"), - }), - ], - gap: lheight, - direction: "column", - }).join(""); - } - - // Get title range text - let titleText = i18n.t("wakatimecard.title"); - switch (stats.range) { - case "last_7_days": - titleText += ` (${i18n.t("wakatimecard.last7days")})`; - break; - case "last_year": - titleText += ` (${i18n.t("wakatimecard.lastyear")})`; - break; - } - - const card = new Card({ - customTitle: custom_title, - defaultTitle: titleText, - width: card_width, - height, - border_radius, - colors: { - titleColor, - textColor, - iconColor, - bgColor, - borderColor, - }, - }); - - if (disable_animations) { - card.disableAnimations(); - } - - card.setHideBorder(hide_border); - card.setHideTitle(hide_title); - card.setCSS( - ` - ${cssStyles} - @keyframes slideInAnimation { - from { - width: 0; - } - to { - width: calc(100%-100px); - } - } - @keyframes growWidthAnimation { - from { - width: 0; - } - to { - width: 100%; - } - } - .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} } - #rect-mask rect{ - animation: slideInAnimation 1s ease-in-out forwards; - } - .lang-progress{ - animation: growWidthAnimation 0.6s ease-in-out forwards; - } - `, - ); - - return card.render(` - - ${finalLayout} - - `); -}; - -export { renderWakatimeCard }; -export default renderWakatimeCard; diff --git a/frontend/frontend/src/backend/src/common/Card.js b/frontend/frontend/src/backend/src/common/Card.js deleted file mode 100644 index d32da562..00000000 --- a/frontend/frontend/src/backend/src/common/Card.js +++ /dev/null @@ -1,273 +0,0 @@ -import { encodeHTML, flexLayout } from "./utils.js"; - -class Card { - /** - * Creates a new card instance. - * - * @param {object} args Card arguments. - * @param {number?=} args.width Card width. - * @param {number?=} args.height Card height. - * @param {number?=} args.border_radius Card border radius. - * @param {string?=} args.customTitle Card custom title. - * @param {string?=} args.defaultTitle Card default title. - * @param {string?=} args.titlePrefixIcon Card title prefix icon. - * @param {object?=} args.colors Card colors arguments. - * @param {string} args.colors.titleColor Card title color. - * @param {string} args.colors.textColor Card text color. - * @param {string} args.colors.iconColor Card icon color. - * @param {string|Array} args.colors.bgColor Card background color. - * @param {string} args.colors.borderColor Card border color. - * @returns {Card} Card instance. - */ - constructor({ - width = 100, - height = 100, - border_radius = 4.5, - colors = {}, - customTitle, - defaultTitle = "", - titlePrefixIcon, - }) { - this.width = width; - this.height = height; - - this.hideBorder = false; - this.hideTitle = false; - - this.border_radius = border_radius; - - // returns theme based colors with proper overrides and defaults - this.colors = colors; - this.title = - customTitle === undefined - ? encodeHTML(defaultTitle) - : encodeHTML(customTitle); - - this.css = ""; - - this.paddingX = 25; - this.paddingY = 35; - this.titlePrefixIcon = titlePrefixIcon; - this.animations = true; - this.a11yTitle = ""; - this.a11yDesc = ""; - } - - /** - * @returns {void} - */ - disableAnimations() { - this.animations = false; - } - - /** - * @param {Object} props The props object. - * @param {string} props.title Accessibility title. - * @param {string} props.desc Accessibility description. - * @returns {void} - */ - setAccessibilityLabel({ title, desc }) { - this.a11yTitle = title; - this.a11yDesc = desc; - } - - /** - * @param {string} value The CSS to add to the card. - * @returns {void} - */ - setCSS(value) { - this.css = value; - } - - /** - * @param {boolean} value Whether to hide the border or not. - * @returns {void} - */ - setHideBorder(value) { - this.hideBorder = value; - } - - /** - * @param {boolean} value Whether to hide the title or not. - * @returns {void} - */ - setHideTitle(value) { - this.hideTitle = value; - if (value) { - this.height -= 30; - } - } - - /** - * @param {string} text The title to set. - * @returns {void} - */ - setTitle(text) { - this.title = text; - } - - /** - * @returns {string} The rendered card title. - */ - renderTitle() { - const titleText = ` - ${this.title} - `; - - const prefixIcon = ` - - ${this.titlePrefixIcon} - - `; - return ` - - ${flexLayout({ - items: [this.titlePrefixIcon && prefixIcon, titleText], - gap: 25, - }).join("")} - - `; - } - - /** - * @returns {string} The rendered card gradient. - */ - renderGradient() { - if (typeof this.colors.bgColor !== "object") { - return ""; - } - - const gradients = this.colors.bgColor.slice(1); - return typeof this.colors.bgColor === "object" - ? ` - - - ${gradients.map((grad, index) => { - let offset = (index * 100) / (gradients.length - 1); - return ``; - })} - - - ` - : ""; - } - - /** - * Retrieves css animations for a card. - * - * @returns {string} Animation css. - */ - getAnimations = () => { - return ` - /* Animations */ - @keyframes scaleInAnimation { - from { - transform: translate(-5px, 5px) scale(0); - } - to { - transform: translate(-5px, 5px) scale(1); - } - } - @keyframes fadeInAnimation { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - `; - }; - - /** - * @param {string} body The inner body of the card. - * @returns {string} The rendered card. - */ - render(body) { - return ` - - ${this.a11yTitle} - ${this.a11yDesc} - - - ${this.renderGradient()} - - - - ${this.hideTitle ? "" : this.renderTitle()} - - - ${body} - - - `; - } -} - -export { Card }; -export default Card; diff --git a/frontend/frontend/src/backend/src/common/I18n.js b/frontend/frontend/src/backend/src/common/I18n.js deleted file mode 100644 index bd5f29fc..00000000 --- a/frontend/frontend/src/backend/src/common/I18n.js +++ /dev/null @@ -1,41 +0,0 @@ -const FALLBACK_LOCALE = "en"; - -/** - * I18n translation class. - */ -class I18n { - /** - * Constructor. - * - * @param {Object} options Options. - * @param {string=} options.locale Locale. - * @param {Object} options.translations Translations. - */ - constructor({ locale, translations }) { - this.locale = locale || FALLBACK_LOCALE; - this.translations = translations; - } - - /** - * Get translation. - * - * @param {string} str String to translate. - * @returns {string} Translated string. - */ - t(str) { - if (!this.translations[str]) { - throw new Error(`${str} Translation string not found`); - } - - if (!this.translations[str][this.locale]) { - throw new Error( - `'${str}' translation not found for locale '${this.locale}'`, - ); - } - - return this.translations[str][this.locale]; - } -} - -export { I18n }; -export default I18n; diff --git a/frontend/frontend/src/backend/src/common/blacklist.js b/frontend/frontend/src/backend/src/common/blacklist.js deleted file mode 100644 index c363a071..00000000 --- a/frontend/frontend/src/backend/src/common/blacklist.js +++ /dev/null @@ -1,10 +0,0 @@ -const blacklist = [ - "renovate-bot", - "technote-space", - "sw-yx", - "YourUsername", - "[YourUsername]", -]; - -export { blacklist }; -export default blacklist; diff --git a/frontend/frontend/src/backend/src/common/createProgressNode.js b/frontend/frontend/src/backend/src/common/createProgressNode.js deleted file mode 100644 index 2d7303a5..00000000 --- a/frontend/frontend/src/backend/src/common/createProgressNode.js +++ /dev/null @@ -1,46 +0,0 @@ -// @ts-check - -import { clampValue } from "./utils.js"; - -/** - * Create a node to indicate progress in percentage along a horizontal line. - * - * @param {Object} createProgressNodeParams Object that contains the createProgressNode parameters. - * @param {number} createProgressNodeParams.x X-axis position. - * @param {number} createProgressNodeParams.y Y-axis position. - * @param {number} createProgressNodeParams.width Width of progress bar. - * @param {string} createProgressNodeParams.color Progress color. - * @param {number} createProgressNodeParams.progress Progress value. - * @param {string} createProgressNodeParams.progressBarBackgroundColor Progress bar bg color. - * @param {number} createProgressNodeParams.delay Delay before animation starts. - * @returns {string} Progress node. - */ -const createProgressNode = ({ - x, - y, - width, - color, - progress, - progressBarBackgroundColor, - delay, -}) => { - const progressPercentage = clampValue(progress, 2, 100); - - return ` - - - - - - - `; -}; - -export { createProgressNode }; -export default createProgressNode; diff --git a/frontend/frontend/src/backend/src/common/database.js b/frontend/frontend/src/backend/src/common/database.js deleted file mode 100644 index 20572c4e..00000000 --- a/frontend/frontend/src/backend/src/common/database.js +++ /dev/null @@ -1,256 +0,0 @@ -import pkg from "pg"; -const { Pool } = pkg; - -export const pool = process.env.POSTGRES_URL - ? new Pool({ - connectionString: process.env.POSTGRES_URL, - }) - : null; - -/** - * Creates all required tables if they do not exist. - */ -async function createAllTables() { - if (!pool) { - return; - } - - await pool.query(` - CREATE TABLE IF NOT EXISTS requests ( - request TEXT PRIMARY KEY, - requested_at TIMESTAMP NOT NULL DEFAULT now(), - user_requested_at TIMESTAMP NOT NULL DEFAULT now() - ); - CREATE TABLE IF NOT EXISTS authenticated_users ( - user_id TEXT PRIMARY KEY, - access_token TEXT NOT NULL, - user_key TEXT, - private_access BOOLEAN NOT NULL DEFAULT false - ); - `); -} - -/** - * Stores or updates a request in the database. - */ -export async function storeRequest(req) { - if (!pool) { - return; - } - - const isBypass = req.headers && req.headers["x-bypass-store"]; - const insertQuery = isBypass - ? ` - INSERT INTO requests (request, requested_at) - VALUES ($1, NOW()) - ON CONFLICT (request) - DO UPDATE SET requested_at = EXCLUDED.requested_at - ` - : ` - INSERT INTO requests (request, requested_at, user_requested_at) - VALUES ($1, NOW(), NOW()) - ON CONFLICT (request) - DO UPDATE SET requested_at = EXCLUDED.requested_at, user_requested_at = EXCLUDED.user_requested_at - `; - - try { - await pool.query(insertQuery, [req.url]); - } catch (err) { - // Check for undefined_table error (SQLSTATE 42P01) - if (err.code === "42P01") { - await createAllTables(); - // Retry the insert after creating the table - await pool.query(insertQuery, [req.url]); - } else { - throw err; // Re-throw if it's some other error - } - } -} - -/** - * Deletes all requests older than 8 days from the database. - */ -export async function deleteOldRequests() { - if (!pool) { - return; - } - - const deleteQuery = ` - DELETE FROM requests - WHERE user_requested_at < NOW() - INTERVAL '8 days' - `; - try { - let result = await pool.query(deleteQuery); - console.log(`Deleted ${result.rowCount} old requests.`); - } catch (err) { - if (err.code === "42P01") { - console.log("Error deleting requests, table doesn't exist"); - } else { - throw err; - } - } -} - -/** - * Fetches all requests which are between 11 hours and 8 days old. - * - * @returns {Promise} Array of all requests between 11 hours and 8 days old. - */ -export async function getRecentRequests() { - if (!pool) { - return []; - } - - const query = ` - SELECT request - FROM requests - WHERE requested_at >= NOW() - INTERVAL '8 days' - AND requested_at < NOW() - INTERVAL '11 hours' - ORDER BY requested_at ASC - `; - let rows; - try { - ({ rows } = await pool.query(query)); - } catch (err) { - if (err.code === "42P01") { - console.log("Error fetching requests, table doesn't exist"); - } else { - throw err; - } - } - return rows.map((row) => row.request); -} - -/** - * Inserts or updates a user in the database. - * - * @param {string} userId GitHub userId (login name) - * @param {string} accessToken GitHub access token - * @param {string|null} userKey Optional user key - * @param {boolean} privateAccess Whether private access was requested - */ -export async function storeUser(userId, accessToken, userKey, privateAccess) { - if (!pool) { - return; - } - - const insertQuery = ` - INSERT INTO authenticated_users (user_id, access_token, user_key, private_access) - VALUES ($1, $2, $3, $4) - ON CONFLICT (user_id) - DO UPDATE SET - access_token = EXCLUDED.access_token, - user_key = EXCLUDED.user_key, - private_access = EXCLUDED.private_access - `; - - try { - await pool.query(insertQuery, [ - userId, - accessToken, - userKey, - privateAccess, - ]); - } catch (err) { - if (err.code === "42P01") { - await createAllTables(); - await pool.query(insertQuery, [ - userId, - accessToken, - userKey, - privateAccess, - ]); - } else { - throw err; - } - } -} - -/** - * Delete a user from the database. - * - * @param userKey user key of the user which is to be deleted. - */ -export async function deleteUser(userKey) { - if (!pool) { - return; - } - - const deleteQuery = ` - DELETE FROM authenticated_users - WHERE user_key = $1 - `; - try { - await pool.query(deleteQuery, [userKey]); - } catch (err) { - if (err.code === "42P01") { - console.log("Error deleting user, table doesn't exist"); - } else { - throw err; - } - } -} - -/** - * Checks if private_access is true for the given user_key. - * - * @param {string} userKey user key of the user to be checked - * @returns {Promise} true if private_access is true, false otherwise - */ -export async function hasPrivateAccess(userKey) { - if (!pool) { - return null; - } - - const query = ` - SELECT private_access - FROM authenticated_users - WHERE user_key = $1 - LIMIT 1 - `; - try { - const { rows } = await pool.query(query, [userKey]); - if (rows.length === 0) { - return null; - } - return rows[0].private_access; - } catch (err) { - if (err.code === "42P01") { - return null; - } else { - throw err; - } - } -} - -/** - * Fetches access_token for a given user_key. - * - * @param {string} userKey user key of the user to fetch token for - * @returns Returns user key if found, null otherwise - */ -export async function getUserToken(userKey) { - if (!pool) { - return null; - } - - const query = ` - SELECT access_token - FROM authenticated_users - WHERE user_key = $1 - LIMIT 1 - `; - try { - const { rows } = await pool.query(query, [userKey]); - if (rows.length === 0) { - return null; - } - return rows[0].access_token; - } catch (err) { - if (err.code === "42P01") { - return null; - } else { - throw err; - } - } -} diff --git a/frontend/frontend/src/backend/src/common/icons.js b/frontend/frontend/src/backend/src/common/icons.js deleted file mode 100644 index b08f3fbb..00000000 --- a/frontend/frontend/src/backend/src/common/icons.js +++ /dev/null @@ -1,54 +0,0 @@ -const icons = { - star: ``, - commits: ``, - prs: ``, - prs_merged: ``, - prs_merged_percentage: ``, - issues: ``, - icon: ``, - contribs: ``, - fork: ``, - reviews: ``, - discussions_started: ``, - discussions_answered: ``, - comments: ``, - gist: ``, -}; - -/** - * Get rank icon - * - * @param {string} rankIcon - The rank icon type. - * @param {string} rankLevel - The rank level. - * @param {number} percentile - The rank percentile. - * @returns {string} - The SVG code of the rank icon - */ -const rankIcon = (rankIcon, rankLevel, percentile) => { - switch (rankIcon) { - case "github": - return ` - - `; - case "percentile": - return ` - - Top - - - ${percentile.toFixed(1)}% - - `; - case "default": - default: - return ` - - ${rankLevel} - - `; - } -}; - -export { icons, rankIcon }; -export default icons; diff --git a/frontend/frontend/src/backend/src/common/index.js b/frontend/frontend/src/backend/src/common/index.js deleted file mode 100644 index 2e7e9cb2..00000000 --- a/frontend/frontend/src/backend/src/common/index.js +++ /dev/null @@ -1,30 +0,0 @@ -export { blacklist } from "./blacklist.js"; -export { Card } from "./Card.js"; -export { createProgressNode } from "./createProgressNode.js"; -export { I18n } from "./I18n.js"; -export { icons } from "./icons.js"; -export { retryer } from "./retryer.js"; -export { - ERROR_CARD_LENGTH, - renderError, - encodeHTML, - kFormatter, - isValidHexColor, - parseBoolean, - parseArray, - clampValue, - isValidGradient, - fallbackColor, - request, - flexLayout, - getCardColors, - wrapTextMultiline, - logger, - CONSTANTS, - CustomError, - MissingParamError, - measureText, - lowercaseTrim, - chunkArray, - parseEmojis, -} from "./utils.js"; diff --git a/frontend/frontend/src/backend/src/common/languageColors.json b/frontend/frontend/src/backend/src/common/languageColors.json deleted file mode 100644 index 9d25c890..00000000 --- a/frontend/frontend/src/backend/src/common/languageColors.json +++ /dev/null @@ -1,643 +0,0 @@ -{ - "1C Enterprise": "#814CCC", - "2-Dimensional Array": "#38761D", - "4D": "#004289", - "ABAP": "#E8274B", - "ABAP CDS": "#555e25", - "AGS Script": "#B9D9FF", - "AIDL": "#34EB6B", - "AL": "#3AA2B5", - "AMPL": "#E6EFBB", - "ANTLR": "#9DC3FF", - "API Blueprint": "#2ACCA8", - "APL": "#5A8164", - "ASP.NET": "#9400ff", - "ATS": "#1ac620", - "ActionScript": "#882B0F", - "Ada": "#02f88c", - "Adblock Filter List": "#800000", - "Adobe Font Metrics": "#fa0f00", - "Agda": "#315665", - "Aiken": "#640ff8", - "Alloy": "#64C800", - "Alpine Abuild": "#0D597F", - "Altium Designer": "#A89663", - "AngelScript": "#C7D7DC", - "Answer Set Programming": "#A9CC29", - "Ant Build System": "#A9157E", - "Antlers": "#ff269e", - "ApacheConf": "#d12127", - "Apex": "#1797c0", - "Apollo Guidance Computer": "#0B3D91", - "AppleScript": "#101F1F", - "Arc": "#aa2afe", - "AsciiDoc": "#73a0c5", - "AspectJ": "#a957b0", - "Assembly": "#6E4C13", - "Astro": "#ff5a03", - "Asymptote": "#ff0000", - "Augeas": "#9CC134", - "AutoHotkey": "#6594b9", - "AutoIt": "#1C3552", - "Avro IDL": "#0040FF", - "Awk": "#c30e9b", - "B4X": "#00e4ff", - "BASIC": "#ff0000", - "BQN": "#2b7067", - "Ballerina": "#FF5000", - "Batchfile": "#C1F12E", - "Beef": "#a52f4e", - "Berry": "#15A13C", - "BibTeX": "#778899", - "Bicep": "#519aba", - "Bikeshed": "#5562ac", - "Bison": "#6A463F", - "BitBake": "#00bce4", - "Blade": "#f7523f", - "BlitzBasic": "#00FFAE", - "BlitzMax": "#cd6400", - "Bluespec": "#12223c", - "Bluespec BH": "#12223c", - "Boo": "#d4bec1", - "Boogie": "#c80fa0", - "Brainfuck": "#2F2530", - "BrighterScript": "#66AABB", - "Brightscript": "#662D91", - "Browserslist": "#ffd539", - "BuildStream": "#006bff", - "C": "#555555", - "C#": "#178600", - "C++": "#f34b7d", - "C3": "#2563eb", - "CAP CDS": "#0092d1", - "CLIPS": "#00A300", - "CMake": "#DA3434", - "COLLADA": "#F1A42B", - "CSON": "#244776", - "CSS": "#663399", - "CSV": "#237346", - "CUE": "#5886E1", - "CWeb": "#00007a", - "Cabal Config": "#483465", - "Caddyfile": "#22b638", - "Cadence": "#00ef8b", - "Cairo": "#ff4a48", - "Cairo Zero": "#ff4a48", - "CameLIGO": "#3be133", - "Cap'n Proto": "#c42727", - "Carbon": "#222222", - "Ceylon": "#dfa535", - "Chapel": "#8dc63f", - "ChucK": "#3f8000", - "Circom": "#707575", - "Cirru": "#ccccff", - "Clarion": "#db901e", - "Clarity": "#5546ff", - "Classic ASP": "#6a40fd", - "Clean": "#3F85AF", - "Click": "#E4E6F3", - "Clojure": "#db5855", - "Closure Templates": "#0d948f", - "Cloud Firestore Security Rules": "#FFA000", - "Clue": "#0009b5", - "CodeQL": "#140f46", - "CoffeeScript": "#244776", - "ColdFusion": "#ed2cd6", - "ColdFusion CFC": "#ed2cd6", - "Common Lisp": "#3fb68b", - "Common Workflow Language": "#B5314C", - "Component Pascal": "#B0CE4E", - "Cooklang": "#E15A29", - "Crystal": "#000100", - "Csound": "#1a1a1a", - "Csound Document": "#1a1a1a", - "Csound Score": "#1a1a1a", - "Cuda": "#3A4E3A", - "Curry": "#531242", - "Cylc": "#00b3fd", - "Cypher": "#34c0eb", - "Cython": "#fedf5b", - "D": "#ba595e", - "D2": "#526ee8", - "DM": "#447265", - "Dafny": "#FFEC25", - "Darcs Patch": "#8eff23", - "Dart": "#00B4AB", - "Daslang": "#d3d3d3", - "DataWeave": "#003a52", - "Debian Package Control File": "#D70751", - "DenizenScript": "#FBEE96", - "Dhall": "#dfafff", - "DirectX 3D File": "#aace60", - "Dockerfile": "#384d54", - "Dogescript": "#cca760", - "Dotenv": "#e5d559", - "Dune": "#89421e", - "Dylan": "#6c616e", - "E": "#ccce35", - "ECL": "#8a1267", - "ECLiPSe": "#001d9d", - "EJS": "#a91e50", - "EQ": "#a78649", - "Earthly": "#2af0ff", - "Easybuild": "#069406", - "Ecere Projects": "#913960", - "Ecmarkup": "#eb8131", - "Edge": "#0dffe0", - "EdgeQL": "#31A7FF", - "EditorConfig": "#fff1f2", - "Eiffel": "#4d6977", - "Elixir": "#6e4a7e", - "Elm": "#60B5CC", - "Elvish": "#55BB55", - "Elvish Transcript": "#55BB55", - "Emacs Lisp": "#c065db", - "EmberScript": "#FFF4F3", - "Erlang": "#B83998", - "Euphoria": "#FF790B", - "F#": "#b845fc", - "F*": "#572e30", - "FIGlet Font": "#FFDDBB", - "FIRRTL": "#2f632f", - "FLUX": "#88ccff", - "Factor": "#636746", - "Fancy": "#7b9db4", - "Fantom": "#14253c", - "Faust": "#c37240", - "Fennel": "#fff3d7", - "Filebench WML": "#F6B900", - "Fluent": "#ffcc33", - "Forth": "#341708", - "Fortran": "#4d41b1", - "Fortran Free Form": "#4d41b1", - "FreeBASIC": "#141AC9", - "FreeMarker": "#0050b2", - "Frege": "#00cafe", - "Futhark": "#5f021f", - "G-code": "#D08CF2", - "GAML": "#FFC766", - "GAMS": "#f49a22", - "GAP": "#0000cc", - "GCC Machine Description": "#FFCFAB", - "GDScript": "#355570", - "GDShader": "#478CBF", - "GEDCOM": "#003058", - "GLSL": "#5686a5", - "GSC": "#FF6800", - "Game Maker Language": "#71b417", - "Gemfile.lock": "#701516", - "Gemini": "#ff6900", - "Genero 4gl": "#63408e", - "Genero per": "#d8df39", - "Genie": "#fb855d", - "Genshi": "#951531", - "Gentoo Ebuild": "#9400ff", - "Gentoo Eclass": "#9400ff", - "Gerber Image": "#d20b00", - "Gherkin": "#5B2063", - "Git Attributes": "#F44D27", - "Git Config": "#F44D27", - "Git Revision List": "#F44D27", - "Gleam": "#ffaff3", - "Glimmer JS": "#F5835F", - "Glimmer TS": "#3178c6", - "Glyph": "#c1ac7f", - "Gnuplot": "#f0a9f0", - "Go": "#00ADD8", - "Go Checksums": "#00ADD8", - "Go Module": "#00ADD8", - "Go Workspace": "#00ADD8", - "Godot Resource": "#355570", - "Golo": "#88562A", - "Gosu": "#82937f", - "Grace": "#615f8b", - "Gradle": "#02303a", - "Gradle Kotlin DSL": "#02303a", - "Grammatical Framework": "#ff0000", - "GraphQL": "#e10098", - "Graphviz (DOT)": "#2596be", - "Groovy": "#4298b8", - "Groovy Server Pages": "#4298b8", - "HAProxy": "#106da9", - "HCL": "#844FBA", - "HIP": "#4F3A4F", - "HLSL": "#aace60", - "HOCON": "#9ff8ee", - "HTML": "#e34c26", - "HTML+ECR": "#2e1052", - "HTML+EEX": "#6e4a7e", - "HTML+ERB": "#701516", - "HTML+PHP": "#4f5d95", - "HTML+Razor": "#512be4", - "HTTP": "#005C9C", - "HXML": "#f68712", - "Hack": "#878787", - "Haml": "#ece2a9", - "Handlebars": "#f7931e", - "Harbour": "#0e60e3", - "Hare": "#9d7424", - "Haskell": "#5e5086", - "Haxe": "#df7900", - "HiveQL": "#dce200", - "HolyC": "#ffefaf", - "Hosts File": "#308888", - "Hy": "#7790B2", - "IDL": "#a3522f", - "IGOR Pro": "#0000cc", - "INI": "#d1dbe0", - "ISPC": "#2D68B1", - "Idris": "#b30000", - "Ignore List": "#000000", - "ImageJ Macro": "#99AAFF", - "Imba": "#16cec6", - "Inno Setup": "#264b99", - "Io": "#a9188d", - "Ioke": "#078193", - "Isabelle": "#FEFE00", - "Isabelle ROOT": "#FEFE00", - "J": "#9EEDFF", - "JAR Manifest": "#b07219", - "JCL": "#d90e09", - "JFlex": "#DBCA00", - "JSON": "#292929", - "JSON with Comments": "#292929", - "JSON5": "#267CB9", - "JSONLD": "#0c479c", - "JSONiq": "#40d47e", - "Jai": "#ab8b4b", - "Janet": "#0886a5", - "Jasmin": "#d03600", - "Java": "#b07219", - "Java Properties": "#2A6277", - "Java Server Pages": "#2A6277", - "Java Template Engine": "#2A6277", - "JavaScript": "#f1e05a", - "JavaScript+ERB": "#f1e05a", - "Jest Snapshot": "#15c213", - "JetBrains MPS": "#21D789", - "Jinja": "#a52a22", - "Jison": "#56b3cb", - "Jison Lex": "#56b3cb", - "Jolie": "#843179", - "Jsonnet": "#0064bd", - "Julia": "#a270ba", - "Julia REPL": "#a270ba", - "Jupyter Notebook": "#DA5B0B", - "Just": "#384d54", - "KDL": "#ffb3b3", - "KRL": "#28430A", - "Kaitai Struct": "#773b37", - "KakouneScript": "#6f8042", - "KerboScript": "#41adf0", - "KiCad Layout": "#2f4aab", - "KiCad Legacy Layout": "#2f4aab", - "KiCad Schematic": "#2f4aab", - "Koka": "#215166", - "Kotlin": "#A97BFF", - "LFE": "#4C3023", - "LLVM": "#185619", - "LOLCODE": "#cc9900", - "LSL": "#3d9970", - "LabVIEW": "#fede06", - "Lark": "#2980B9", - "Lasso": "#999999", - "Latte": "#f2a542", - "Leo": "#C4FFC2", - "Less": "#1d365d", - "Lex": "#DBCA00", - "LigoLANG": "#0e74ff", - "LilyPond": "#9ccc7c", - "Liquid": "#67b8de", - "Literate Agda": "#315665", - "Literate CoffeeScript": "#244776", - "Literate Haskell": "#5e5086", - "LiveCode Script": "#0c5ba5", - "LiveScript": "#499886", - "Logtalk": "#295b9a", - "LookML": "#652B81", - "Lua": "#000080", - "Luau": "#00A2FF", - "M3U": "#179C7D", - "MATLAB": "#e16737", - "MAXScript": "#00a6a6", - "MDX": "#fcb32c", - "MLIR": "#5EC8DB", - "MQL4": "#62A8D6", - "MQL5": "#4A76B8", - "MTML": "#b7e1f4", - "Macaulay2": "#d8ffff", - "Makefile": "#427819", - "Mako": "#7e858d", - "Markdown": "#083fa1", - "Marko": "#42bff2", - "Mask": "#f97732", - "Mathematica": "#dd1100", - "Max": "#c4a79c", - "Mercury": "#ff2b2b", - "Mermaid": "#ff3670", - "Meson": "#007800", - "Metal": "#8f14e9", - "MiniYAML": "#ff1111", - "MiniZinc": "#06a9e6", - "Mint": "#02b046", - "Mirah": "#c7a938", - "Modelica": "#de1d31", - "Modula-2": "#10253f", - "Modula-3": "#223388", - "Mojo": "#ff4c1f", - "Monkey C": "#8D6747", - "MoonBit": "#b92381", - "MoonScript": "#ff4585", - "Motoko": "#fbb03b", - "Motorola 68K Assembly": "#005daa", - "Move": "#4a137a", - "Mustache": "#724b3b", - "NCL": "#28431f", - "NMODL": "#00356B", - "NPM Config": "#cb3837", - "NWScript": "#111522", - "Nasal": "#1d2c4e", - "Nearley": "#990000", - "Nemerle": "#3d3c6e", - "NetLinx": "#0aa0ff", - "NetLinx+ERB": "#747faa", - "NetLogo": "#ff6375", - "NewLisp": "#87AED7", - "Nextflow": "#3ac486", - "Nginx": "#009639", - "Nickel": "#E0C3FC", - "Nim": "#ffc200", - "Nit": "#009917", - "Nix": "#7e7eff", - "Noir": "#2f1f49", - "Nu": "#c9df40", - "NumPy": "#9C8AF9", - "Nunjucks": "#3d8137", - "Nushell": "#4E9906", - "OASv2-json": "#85ea2d", - "OASv2-yaml": "#85ea2d", - "OASv3-json": "#85ea2d", - "OASv3-yaml": "#85ea2d", - "OCaml": "#ef7a08", - "OMNeT++ MSG": "#a0e0a0", - "OMNeT++ NED": "#08607c", - "ObjectScript": "#424893", - "Objective-C": "#438eff", - "Objective-C++": "#6866fb", - "Objective-J": "#ff0c5a", - "Odin": "#60AFFE", - "Omgrofl": "#cabbff", - "Opal": "#f7ede0", - "Open Policy Agent": "#7d9199", - "OpenAPI Specification v2": "#85ea2d", - "OpenAPI Specification v3": "#85ea2d", - "OpenCL": "#ed2e2d", - "OpenEdge ABL": "#5ce600", - "OpenQASM": "#AA70FF", - "OpenSCAD": "#e5cd45", - "Option List": "#476732", - "Org": "#77aa99", - "OverpassQL": "#cce2aa", - "Oxygene": "#cdd0e3", - "Oz": "#fab738", - "P4": "#7055b5", - "PDDL": "#0d00ff", - "PEG.js": "#234d6b", - "PHP": "#4F5D95", - "PLSQL": "#dad8d8", - "PLpgSQL": "#336790", - "POV-Ray SDL": "#6bac65", - "Pact": "#F7A8B8", - "Pan": "#cc0000", - "Papyrus": "#6600cc", - "Parrot": "#f3ca0a", - "Pascal": "#E3F171", - "Pawn": "#dbb284", - "Pep8": "#C76F5B", - "Perl": "#0298c3", - "PicoLisp": "#6067af", - "PigLatin": "#fcd7de", - "Pike": "#005390", - "Pip Requirements": "#FFD343", - "Pkl": "#6b9543", - "PlantUML": "#fbbd16", - "PogoScript": "#d80074", - "Polar": "#ae81ff", - "Portugol": "#f8bd00", - "PostCSS": "#dc3a0c", - "PostScript": "#da291c", - "PowerBuilder": "#8f0f8d", - "PowerShell": "#012456", - "Praat": "#c8506d", - "Prisma": "#0c344b", - "Processing": "#0096D8", - "Procfile": "#3B2F63", - "Prolog": "#74283c", - "Promela": "#de0000", - "Propeller Spin": "#7fa2a7", - "Pug": "#a86454", - "Puppet": "#302B6D", - "PureBasic": "#5a6986", - "PureScript": "#1D222D", - "Pyret": "#ee1e10", - "Python": "#3572A5", - "Python console": "#3572A5", - "Python traceback": "#3572A5", - "Q#": "#fed659", - "QML": "#44a51c", - "Qt Script": "#00b841", - "Quake": "#882233", - "QuakeC": "#975777", - "QuickBASIC": "#008080", - "R": "#198CE7", - "RAML": "#77d9fb", - "RBS": "#701516", - "RDoc": "#701516", - "REXX": "#d90e09", - "RMarkdown": "#198ce7", - "RON": "#a62c00", - "ROS Interface": "#22314e", - "RPGLE": "#2BDE21", - "RUNOFF": "#665a4e", - "Racket": "#3c5caa", - "Ragel": "#9d5200", - "Raku": "#0000fb", - "Rascal": "#fffaa0", - "ReScript": "#ed5051", - "Reason": "#ff5847", - "ReasonLIGO": "#ff5847", - "Rebol": "#358a5b", - "Record Jar": "#0673ba", - "Red": "#f50000", - "Regular Expression": "#009a00", - "Ren'Py": "#ff7f7f", - "Rez": "#FFDAB3", - "Ring": "#2D54CB", - "Riot": "#A71E49", - "RobotFramework": "#00c0b5", - "Roc": "#7c38f5", - "Rocq Prover": "#d0b68c", - "Roff": "#ecdebe", - "Roff Manpage": "#ecdebe", - "Rouge": "#cc0088", - "RouterOS Script": "#DE3941", - "Ruby": "#701516", - "Rust": "#dea584", - "SAS": "#B34936", - "SCSS": "#c6538c", - "SPARQL": "#0C4597", - "SQF": "#3F3F3F", - "SQL": "#e38c00", - "SQLPL": "#e38c00", - "SRecode Template": "#348a34", - "STL": "#373b5e", - "SVG": "#ff9900", - "Sail": "#259dd5", - "SaltStack": "#646464", - "Sass": "#a53b70", - "Scala": "#c22d40", - "Scaml": "#bd181a", - "Scenic": "#fdc700", - "Scheme": "#1e4aec", - "Scilab": "#ca0f21", - "Self": "#0579aa", - "ShaderLab": "#222c37", - "Shell": "#89e051", - "ShellCheck Config": "#cecfcb", - "Shen": "#120F14", - "Simple File Verification": "#C9BFED", - "Singularity": "#64E6AD", - "Slang": "#1fbec9", - "Slash": "#007eff", - "Slice": "#003fa2", - "Slim": "#2b2b2b", - "Slint": "#2379F4", - "SmPL": "#c94949", - "Smalltalk": "#596706", - "Smarty": "#f0c040", - "Smithy": "#c44536", - "Snakemake": "#419179", - "Solidity": "#AA6746", - "SourcePawn": "#f69e1d", - "Squirrel": "#800000", - "Stan": "#b2011d", - "Standard ML": "#dc566d", - "Starlark": "#76d275", - "Stata": "#1a5f91", - "StringTemplate": "#3fb34f", - "Stylus": "#ff6347", - "SubRip Text": "#9e0101", - "SugarSS": "#2fcc9f", - "SuperCollider": "#46390b", - "Survex data": "#ffcc99", - "Svelte": "#ff3e00", - "Sway": "#00F58C", - "Sweave": "#198ce7", - "Swift": "#F05138", - "SystemVerilog": "#DAE1C2", - "TI Program": "#A0AA87", - "TL-Verilog": "#C40023", - "TLA": "#4b0079", - "TOML": "#9c4221", - "TSQL": "#e38c00", - "TSV": "#237346", - "TSX": "#3178c6", - "TXL": "#0178b8", - "Tact": "#48b5ff", - "Talon": "#333333", - "Tcl": "#e4cc98", - "TeX": "#3D6117", - "Terra": "#00004c", - "Terraform Template": "#7b42bb", - "TextGrid": "#c8506d", - "TextMate Properties": "#df66e4", - "Textile": "#ffe7ac", - "Thrift": "#D12127", - "Toit": "#c2c9fb", - "Tor Config": "#59316b", - "Tree-sitter Query": "#8ea64c", - "Turing": "#cf142b", - "Twig": "#c1d026", - "TypeScript": "#3178c6", - "TypeSpec": "#4A3665", - "Typst": "#239dad", - "Unified Parallel C": "#4e3617", - "Unity3D Asset": "#222c37", - "Uno": "#9933cc", - "UnrealScript": "#a54c4d", - "Untyped Plutus Core": "#36adbd", - "UrWeb": "#ccccee", - "V": "#4f87c4", - "VBA": "#867db1", - "VBScript": "#15dcdc", - "VCL": "#148AA8", - "VHDL": "#adb2cb", - "Vala": "#a56de2", - "Valve Data Format": "#f26025", - "Velocity Template Language": "#507cff", - "Vento": "#ff0080", - "Verilog": "#b2b7f8", - "Vim Help File": "#199f4b", - "Vim Script": "#199f4b", - "Vim Snippet": "#199f4b", - "Visual Basic .NET": "#945db7", - "Visual Basic 6.0": "#2c6353", - "Volt": "#1F1F1F", - "Vue": "#41b883", - "Vyper": "#9F4CF2", - "WDL": "#42f1f4", - "WGSL": "#1a5e9a", - "Web Ontology Language": "#5b70bd", - "WebAssembly": "#04133b", - "WebAssembly Interface Type": "#6250e7", - "Whiley": "#d5c397", - "Wikitext": "#fc5757", - "Windows Registry Entries": "#52d5ff", - "Witcher Script": "#ff0000", - "Wollok": "#a23738", - "World of Warcraft Addon Data": "#f7e43f", - "Wren": "#383838", - "X10": "#4B6BEF", - "XC": "#99DA07", - "XML": "#0060ac", - "XML Property List": "#0060ac", - "XQuery": "#5232e7", - "XSLT": "#EB8CEB", - "Xmake": "#22a079", - "Xojo": "#81bd41", - "Xonsh": "#285EEF", - "Xtend": "#24255d", - "YAML": "#cb171e", - "YARA": "#220000", - "YASnippet": "#32AB90", - "Yacc": "#4B6C4B", - "Yul": "#794932", - "ZAP": "#0d665e", - "ZIL": "#dc75e5", - "ZenScript": "#00BCD1", - "Zephir": "#118f9e", - "Zig": "#ec915c", - "Zimpl": "#d67711", - "Zmodel": "#ff7100", - "crontab": "#ead7ac", - "eC": "#913960", - "fish": "#4aae47", - "hoon": "#00b171", - "iCalendar": "#ec564c", - "jq": "#c7254e", - "kvlang": "#1da6e0", - "mIRC Script": "#3d57c3", - "mcfunction": "#E22837", - "mdsvex": "#5f9ea0", - "mupad": "#244963", - "nanorc": "#2d004d", - "nesC": "#94B0C7", - "ooc": "#b0b77e", - "q": "#0040cd", - "reStructuredText": "#141414", - "sed": "#64b970", - "templ": "#66D0DD", - "vCard": "#ee2647", - "wisp": "#7582D1", - "xBase": "#403a40" -} \ No newline at end of file diff --git a/frontend/frontend/src/backend/src/common/retryer.js b/frontend/frontend/src/backend/src/common/retryer.js deleted file mode 100644 index bbef173f..00000000 --- a/frontend/frontend/src/backend/src/common/retryer.js +++ /dev/null @@ -1,76 +0,0 @@ -import { CustomError, logger } from "./utils.js"; - -// Script variables. - -// Count the number of GitHub API tokens available. -const PATs = Object.keys(process.env).filter((key) => - /PAT_\d*$/.exec(key), -).length; -const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs; - -/** - * @typedef {import("axios").AxiosResponse} AxiosResponse Axios response. - * @typedef {(variables: object, token: string) => Promise} FetcherFunction Fetcher function. - */ - -/** - * Try to execute the fetcher function until it succeeds or the max number of retries is reached. - * - * @param {FetcherFunction} fetcher The fetcher function. - * @param {object} variables Object with arguments to pass to the fetcher function. - * @param {number} retries How many times to retry. - * @returns {Promise} The response from the fetcher function. - */ -const retryer = async (fetcher, variables, retries = 0) => { - if (!RETRIES) { - throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS); - } - if (retries > RETRIES) { - throw new CustomError( - "Downtime due to GitHub API rate limiting", - CustomError.MAX_RETRY, - ); - } - try { - // try to fetch with the first token since RETRIES is 0 index i'm adding +1 - let response = await fetcher( - variables, - process.env[`PAT_${retries + 1}`], - retries, - ); - - // prettier-ignore - const isRateExceeded = response.data.errors && response.data.errors[0].type === "RATE_LIMITED"; - - // if rate limit is hit increase the RETRIES and recursively call the retryer - // with username, and current RETRIES - if (isRateExceeded) { - logger.log(`PAT_${retries + 1} Failed due to rate limiting`); - retries++; - // directly return from the function - return retryer(fetcher, variables, retries); - } - - // finally return the response - return response; - } catch (err) { - // prettier-ignore - // also checking for bad credentials if any tokens gets invalidated - const isBadCredential = err.response.data && err.response.data.message === "Bad credentials"; - const isAccountSuspended = - err.response.data && - err.response.data.message === "Sorry. Your account was suspended."; - - if (isBadCredential || isAccountSuspended) { - logger.log(`PAT_${retries + 1} Failed due to bad credentials`); - retries++; - // directly return from the function - return retryer(fetcher, variables, retries); - } else { - return err.response; - } - } -}; - -export { retryer, RETRIES }; -export default retryer; diff --git a/frontend/frontend/src/backend/src/common/utils.js b/frontend/frontend/src/backend/src/common/utils.js deleted file mode 100644 index 046c7fc7..00000000 --- a/frontend/frontend/src/backend/src/common/utils.js +++ /dev/null @@ -1,679 +0,0 @@ -// @ts-check -import axios from "axios"; -import toEmoji from "emoji-name-map"; -import wrap from "word-wrap"; -import { themes } from "../../themes/index.js"; - -const OWNER_AFFILIATIONS = ["OWNER", "COLLABORATOR", "ORGANIZATION_MEMBER"]; - -const TRY_AGAIN_LATER = "Please try again later"; - -const SECONDARY_ERROR_MESSAGES = { - MAX_RETRY: - "You can deploy own instance or wait until public will be no longer limited", - NO_TOKENS: - "Please add an env variable called PAT_1 with your GitHub API token in vercel", - USER_NOT_FOUND: "Make sure the provided username is not an organization", - GRAPHQL_ERROR: TRY_AGAIN_LATER, - GITHUB_REST_API_ERROR: TRY_AGAIN_LATER, - WAKATIME_USER_NOT_FOUND: "Make sure you have a public WakaTime profile", - INVALID_AFFILIATION: `Invalid owner affiliations. Valid values are: ${OWNER_AFFILIATIONS.join( - ", ", - )}`, -}; - -/** - * Custom error class to handle custom GRS errors. - */ -class CustomError extends Error { - /** - * @param {string} message Error message. - * @param {string} type Error type. - */ - constructor(message, type) { - super(message); - this.type = type; - this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type; - } - - static MAX_RETRY = "MAX_RETRY"; - static NO_TOKENS = "NO_TOKENS"; - static USER_NOT_FOUND = "USER_NOT_FOUND"; - static GRAPHQL_ERROR = "GRAPHQL_ERROR"; - static GITHUB_REST_API_ERROR = "GITHUB_REST_API_ERROR"; - static WAKATIME_ERROR = "WAKATIME_ERROR"; - static INVALID_AFFILIATION = "INVALID_AFFILIATION"; -} - -/** - * Auto layout utility, allows us to layout things vertically or horizontally with - * proper gaping. - * - * @param {object} props Function properties. - * @param {string[]} props.items Array of items to layout. - * @param {number} props.gap Gap between items. - * @param {"column" | "row"=} props.direction Direction to layout items. - * @param {number[]=} props.sizes Array of sizes for each item. - * @returns {string[]} Array of items with proper layout. - */ -const flexLayout = ({ items, gap, direction, sizes = [] }) => { - let lastSize = 0; - // filter() for filtering out empty strings - return items.filter(Boolean).map((item, i) => { - const size = sizes[i] || 0; - let transform = `translate(${lastSize}, 0)`; - if (direction === "column") { - transform = `translate(0, ${lastSize})`; - } - lastSize += size + gap; - return `${item}`; - }); -}; - -/** - * Creates a node to display the primary programming language of the repository/gist. - * - * @param {string} langName Language name. - * @param {string} langColor Language color. - * @returns {string} Language display SVG object. - */ -const createLanguageNode = (langName, langColor) => { - return ` - - - ${langName} - - `; -}; - -/** - * Creates an icon with label to display repository/gist stats like forks, stars, etc. - * - * @param {string} icon The icon to display. - * @param {number|string} label The label to display. - * @param {string} testid The testid to assign to the label. - * @param {number} iconSize The size of the icon. - * @returns {string} Icon with label SVG object. - */ -const iconWithLabel = (icon, label, testid, iconSize) => { - if (typeof label === "number" && label <= 0) { - return ""; - } - const iconSvg = ` - - ${icon} - - `; - const text = `${label}`; - return flexLayout({ items: [iconSvg, text], gap: 20 }).join(""); -}; - -/** - * Retrieves num with suffix k(thousands) precise to 1 decimal if greater than 999. - * - * @param {number} num The number to format. - * @returns {string|number} The formatted number. - */ -const kFormatter = (num) => { - return Math.abs(num) > 999 - ? Math.sign(num) * parseFloat((Math.abs(num) / 1000).toFixed(1)) + "k" - : Math.sign(num) * Math.abs(num); -}; - -/** - * Checks if a string is a valid hex color. - * - * @param {string} hexColor String to check. - * @returns {boolean} True if the given string is a valid hex color. - */ -const isValidHexColor = (hexColor) => { - return new RegExp( - /^([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4})$/, - ).test(hexColor); -}; - -/** - * Returns boolean if value is either "true" or "false" else the value as it is. - * - * @param {string | boolean} value The value to parse. - * @returns {boolean | undefined } The parsed value. - */ -const parseBoolean = (value) => { - if (typeof value === "boolean") { - return value; - } - - if (typeof value === "string") { - if (value.toLowerCase() === "true") { - return true; - } else if (value.toLowerCase() === "false") { - return false; - } - } - return undefined; -}; - -/** - * Parse string to array of strings. - * - * @param {string} str The string to parse. - * @returns {string[]} The array of strings. - */ -const parseArray = (str) => { - if (!str) { - return []; - } - return str.split(","); -}; - -/** - * Clamp the given number between the given range. - * - * @param {number} number The number to clamp. - * @param {number} min The minimum value. - * @param {number} max The maximum value. - * @returns {number} The clamped number. - */ -const clampValue = (number, min, max) => { - // @ts-ignore - if (Number.isNaN(parseInt(number, 10))) { - return min; - } - return Math.max(min, Math.min(number, max)); -}; - -/** - * Check if the given string is a valid gradient. - * - * @param {string[]} colors Array of colors. - * @returns {boolean} True if the given string is a valid gradient. - */ -const isValidGradient = (colors) => { - return ( - colors.length > 2 && - colors.slice(1).every((color) => isValidHexColor(color)) - ); -}; - -/** - * Retrieves a gradient if color has more than one valid hex codes else a single color. - * - * @param {string} color The color to parse. - * @param {string | string[]} fallbackColor The fallback color. - * @returns {string | string[]} The gradient or color. - */ -const fallbackColor = (color, fallbackColor) => { - let gradient = null; - - let colors = color ? color.split(",") : []; - if (colors.length > 1 && isValidGradient(colors)) { - gradient = colors; - } - - return ( - (gradient ? gradient : isValidHexColor(color) && `#${color}`) || - fallbackColor - ); -}; - -const buildSearchFilter = (repos = [], owners = []) => { - let repoFilter = - Array.isArray(repos) && repos.length > 0 - ? repos.map((r) => `repo:${r} `).join("") - : ""; - let orgFilter = - Array.isArray(owners) && owners.length > 0 - ? owners.map((o) => `owner:${o} `).join("") - : ""; - return repoFilter + orgFilter; -}; - -/** - * @typedef {import('axios').AxiosRequestConfig['data']} AxiosRequestConfigData Axios request data. - * @typedef {import('axios').AxiosRequestConfig['headers']} AxiosRequestConfigHeaders Axios request headers. - */ - -/** - * Send GraphQL request to GitHub API. - * - * @param {AxiosRequestConfigData} data Request data. - * @param {AxiosRequestConfigHeaders} headers Request headers. - * @returns {Promise} Request response. - */ -const request = (data, headers) => { - return axios({ - url: "https://api.github.com/graphql", - method: "post", - headers, - data, - }); -}; - -/** - * Object containing card colors. - * @typedef {{ - * titleColor: string; - * iconColor: string; - * textColor: string; - * bgColor: string | string[]; - * borderColor: string; - * ringColor: string; - * }} CardColors - */ - -/** - * Returns theme based colors with proper overrides and defaults. - * - * @param {Object} args Function arguments. - * @param {string=} args.title_color Card title color. - * @param {string=} args.text_color Card text color. - * @param {string=} args.icon_color Card icon color. - * @param {string=} args.bg_color Card background color. - * @param {string=} args.border_color Card border color. - * @param {string=} args.ring_color Card ring color. - * @param {string=} args.theme Card theme. - * @param {string=} args.fallbackTheme Fallback theme. - * @returns {CardColors} Card colors. - */ -const getCardColors = ({ - title_color, - text_color, - icon_color, - bg_color, - border_color, - ring_color, - theme, - fallbackTheme = "default", -}) => { - const defaultTheme = themes[fallbackTheme]; - const selectedTheme = themes[theme] || defaultTheme; - const defaultBorderColor = - selectedTheme.border_color || defaultTheme.border_color; - - // get the color provided by the user else the theme color - // finally if both colors are invalid fallback to default theme - const titleColor = fallbackColor( - title_color || selectedTheme.title_color, - "#" + defaultTheme.title_color, - ); - - // get the color provided by the user else the theme color - // finally if both colors are invalid we use the titleColor - const ringColor = fallbackColor( - ring_color || selectedTheme.ring_color, - titleColor, - ); - const iconColor = fallbackColor( - icon_color || selectedTheme.icon_color, - "#" + defaultTheme.icon_color, - ); - const textColor = fallbackColor( - text_color || selectedTheme.text_color, - "#" + defaultTheme.text_color, - ); - const bgColor = fallbackColor( - bg_color || selectedTheme.bg_color, - "#" + defaultTheme.bg_color, - ); - - const borderColor = fallbackColor( - border_color || defaultBorderColor, - "#" + defaultBorderColor, - ); - - if ( - typeof titleColor !== "string" || - typeof textColor !== "string" || - typeof ringColor !== "string" || - typeof iconColor !== "string" || - typeof borderColor !== "string" - ) { - throw new Error( - "Unexpected behavior, all colors except background should be string.", - ); - } - - return { titleColor, iconColor, textColor, bgColor, borderColor, ringColor }; -}; - -// Script parameters. -const ERROR_CARD_LENGTH = 576.5; - -/** - * Encode string as HTML. - * - * @see https://stackoverflow.com/a/48073476/10629172 - * - * @param {string} str String to encode. - * @returns {string} Encoded string. - */ -const encodeHTML = (str) => { - return str - .replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => { - return "&#" + i.charCodeAt(0) + ";"; - }) - .replace(/\u0008/gim, ""); -}; - -const UPSTREAM_API_ERRORS = [ - TRY_AGAIN_LATER, - SECONDARY_ERROR_MESSAGES.MAX_RETRY, -]; - -/** - * Renders error message on the card. - * - * @param {string} message Main error message. - * @param {string} secondaryMessage The secondary error message. - * @param {object} options Function options. - * @param {string=} options.title_color Card title color. - * @param {string=} options.text_color Card text color. - * @param {string=} options.bg_color Card background color. - * @param {string=} options.border_color Card border color. - * @param {string=} options.theme Card theme. - * @param {boolean=} options.show_repo_link Whether to show repo link or not. - * @returns {string} The SVG markup. - */ -const renderError = (message, secondaryMessage = "", options = {}) => { - const { - title_color, - text_color, - bg_color, - border_color, - theme = "default", - show_repo_link = true, - } = options; - - // returns theme based colors with proper overrides and defaults - const { titleColor, textColor, bgColor, borderColor } = getCardColors({ - title_color, - text_color, - icon_color: "", - bg_color, - border_color, - ring_color: "", - theme, - }); - - return ` - - - - Something went wrong!${ - UPSTREAM_API_ERRORS.includes(secondaryMessage) || !show_repo_link - ? "" - : " file an issue at https://tiny.one/readme-stats" - } - - ${encodeHTML(message)} - ${secondaryMessage} - - - `; -}; - -/** - * Split text over multiple lines based on the card width. - * - * @param {string} text Text to split. - * @param {number} width Line width in number of characters. - * @param {number} maxLines Maximum number of lines. - * @returns {string[]} Array of lines. - */ -const wrapTextMultiline = (text, width = 59, maxLines = 3) => { - const fullWidthComma = ","; - const encoded = encodeHTML(text); - const isChinese = encoded.includes(fullWidthComma); - - let wrapped = []; - - if (isChinese) { - wrapped = encoded.split(fullWidthComma); // Chinese full punctuation - } else { - wrapped = wrap(encoded, { - width, - }).split("\n"); // Split wrapped lines to get an array of lines - } - - const lines = wrapped.map((line) => line.trim()).slice(0, maxLines); // Only consider maxLines lines - - // Add "..." to the last line if the text exceeds maxLines - if (wrapped.length > maxLines) { - lines[maxLines - 1] += "..."; - } - - // Remove empty lines if text fits in less than maxLines lines - const multiLineText = lines.filter(Boolean); - return multiLineText; -}; - -const noop = () => {}; -// return console instance based on the environment -const logger = - process.env.NODE_ENV === "test" ? { log: noop, error: noop } : console; - -const MIN = 60; -const HOUR = 60 * MIN; -const DAY = 24 * HOUR; - -const CONSTANTS = { - ONE_MINUTE: MIN, - FIVE_MINUTES: 5 * MIN, - TEN_MINUTES: 10 * MIN, - FIFTEEN_MINUTES: 15 * MIN, - THIRTY_MINUTES: 30 * MIN, - - TWO_HOURS: 2 * HOUR, - FOUR_HOURS: 4 * HOUR, - SIX_HOURS: 6 * HOUR, - EIGHT_HOURS: 8 * HOUR, - TEN_HOURS: 10 * HOUR, - TWELVE_HOURS: 12 * HOUR, - - ONE_DAY: DAY, - TWO_DAY: 2 * DAY, - SIX_DAY: 6 * DAY, - TEN_DAY: 10 * DAY, - - CARD_CACHE_SECONDS: 10 * HOUR, - TOP_LANGS_CACHE_SECONDS: 10 * HOUR, - PIN_CARD_CACHE_SECONDS: 10 * HOUR, - ERROR_CACHE_SECONDS: 10 * MIN, -}; - -/** - * Missing query parameter class. - */ -class MissingParamError extends Error { - /** - * Missing query parameter error constructor. - * - * @param {string[]} missedParams An array of missing parameters names. - * @param {string=} secondaryMessage Optional secondary message to display. - */ - constructor(missedParams, secondaryMessage) { - const msg = `Missing params ${missedParams - .map((p) => `"${p}"`) - .join(", ")} make sure you pass the parameters in URL`; - super(msg); - this.missedParams = missedParams; - this.secondaryMessage = secondaryMessage; - } -} - -/** - * Retrieve text length. - * - * @see https://stackoverflow.com/a/48172630/10629172 - * @param {string} str String to measure. - * @param {number} fontSize Font size. - * @returns {number} Text length. - */ -const measureText = (str, fontSize = 10) => { - // prettier-ignore - const widths = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0.2796875, 0.2765625, - 0.3546875, 0.5546875, 0.5546875, 0.8890625, 0.665625, 0.190625, - 0.3328125, 0.3328125, 0.3890625, 0.5828125, 0.2765625, 0.3328125, - 0.2765625, 0.3015625, 0.5546875, 0.5546875, 0.5546875, 0.5546875, - 0.5546875, 0.5546875, 0.5546875, 0.5546875, 0.5546875, 0.5546875, - 0.2765625, 0.2765625, 0.584375, 0.5828125, 0.584375, 0.5546875, - 1.0140625, 0.665625, 0.665625, 0.721875, 0.721875, 0.665625, - 0.609375, 0.7765625, 0.721875, 0.2765625, 0.5, 0.665625, - 0.5546875, 0.8328125, 0.721875, 0.7765625, 0.665625, 0.7765625, - 0.721875, 0.665625, 0.609375, 0.721875, 0.665625, 0.94375, - 0.665625, 0.665625, 0.609375, 0.2765625, 0.3546875, 0.2765625, - 0.4765625, 0.5546875, 0.3328125, 0.5546875, 0.5546875, 0.5, - 0.5546875, 0.5546875, 0.2765625, 0.5546875, 0.5546875, 0.221875, - 0.240625, 0.5, 0.221875, 0.8328125, 0.5546875, 0.5546875, - 0.5546875, 0.5546875, 0.3328125, 0.5, 0.2765625, 0.5546875, - 0.5, 0.721875, 0.5, 0.5, 0.5, 0.3546875, 0.259375, 0.353125, 0.5890625, - ]; - - const avg = 0.5279276315789471; - return ( - str - .split("") - .map((c) => - c.charCodeAt(0) < widths.length ? widths[c.charCodeAt(0)] : avg, - ) - .reduce((cur, acc) => acc + cur) * fontSize - ); -}; - -/** - * Lowercase and trim string. - * - * @param {string} name String to lowercase and trim. - * @returns {string} Lowercased and trimmed string. - */ -const lowercaseTrim = (name) => name.toLowerCase().trim(); - -/** - * Split array of languages in two columns. - * - * @template T Language object. - * @param {Array} arr Array of languages. - * @param {number} perChunk Number of languages per column. - * @returns {Array} Array of languages split in two columns. - */ -const chunkArray = (arr, perChunk) => { - return arr.reduce((resultArray, item, index) => { - const chunkIndex = Math.floor(index / perChunk); - - if (!resultArray[chunkIndex]) { - // @ts-ignore - resultArray[chunkIndex] = []; // start a new chunk - } - - // @ts-ignore - resultArray[chunkIndex].push(item); - - return resultArray; - }, []); -}; - -/** - * Parse emoji from string. - * - * @param {string} str String to parse emoji from. - * @returns {string} String with emoji parsed. - */ -const parseEmojis = (str) => { - if (!str) { - throw new Error("[parseEmoji]: str argument not provided"); - } - return str.replace(/:\w+:/gm, (emoji) => { - return toEmoji.get(emoji) || ""; - }); -}; -/** - * Parse owner affiliations. - * - * @param {string[]} affiliations input affiliations to be parsed. - * @returns {string[]} Parsed affiliations. - * - * @throws {CustomError} If affiliations contains invalid values. - */ -const parseOwnerAffiliations = (affiliations) => { - // Set default value for ownerAffiliations. - // NOTE: Done here since parseArray() will always return an empty array even nothing - //was specified. - affiliations = - affiliations && affiliations.length > 0 - ? affiliations.map((affiliation) => affiliation.toUpperCase()) - : ["OWNER"]; - - // Check if ownerAffiliations contains valid values. - if ( - affiliations.some( - (affiliation) => !OWNER_AFFILIATIONS.includes(affiliation), - ) - ) { - throw new CustomError( - "Invalid query parameter", - CustomError.INVALID_AFFILIATION, - ); - } - return affiliations; -}; - -/** - * Get diff in minutes between two dates. - * - * @param {Date} d1 First date. - * @param {Date} d2 Second date. - * @returns {number} Number of minutes between the two dates. - */ -const dateDiff = (d1, d2) => { - const date1 = new Date(d1); - const date2 = new Date(d2); - const diff = date1.getTime() - date2.getTime(); - return Math.round(diff / (1000 * 60)); -}; - -export { - ERROR_CARD_LENGTH, - renderError, - createLanguageNode, - iconWithLabel, - encodeHTML, - kFormatter, - isValidHexColor, - parseBoolean, - parseArray, - clampValue, - isValidGradient, - fallbackColor, - buildSearchFilter, - request, - flexLayout, - getCardColors, - wrapTextMultiline, - logger, - CONSTANTS, - OWNER_AFFILIATIONS, - CustomError, - MissingParamError, - measureText, - lowercaseTrim, - chunkArray, - parseEmojis, - parseOwnerAffiliations, - dateDiff, -}; diff --git a/frontend/frontend/src/backend/src/common/whitelist.js b/frontend/frontend/src/backend/src/common/whitelist.js deleted file mode 100644 index b5df7c70..00000000 --- a/frontend/frontend/src/backend/src/common/whitelist.js +++ /dev/null @@ -1,10 +0,0 @@ -const whitelist = process.env.WHITELIST - ? process.env.WHITELIST.split(",") - : undefined; - -const gistWhitelist = process.env.GIST_WHITELIST - ? process.env.GIST_WHITELIST.split(",") - : undefined; - -export { whitelist, gistWhitelist }; -export default whitelist; diff --git a/frontend/frontend/src/backend/src/fetchers/gist.js b/frontend/frontend/src/backend/src/fetchers/gist.js deleted file mode 100644 index cd4006b2..00000000 --- a/frontend/frontend/src/backend/src/fetchers/gist.js +++ /dev/null @@ -1,114 +0,0 @@ -// @ts-check - -import { request, MissingParamError } from "../common/utils.js"; -import { retryer } from "../common/retryer.js"; - -/** - * @typedef {import('axios').AxiosRequestHeaders} AxiosRequestHeaders Axios request headers. - * @typedef {import('axios').AxiosResponse} AxiosResponse Axios response. - */ - -const QUERY = ` -query gistInfo($gistName: String!) { - viewer { - gist(name: $gistName) { - description - owner { - login - } - stargazerCount - forks { - totalCount - } - files { - name - language { - name - } - size - } - } - } -} -`; - -/** - * Gist data fetcher. - * - * @param {AxiosRequestHeaders} variables Fetcher variables. - * @param {string} token GitHub token. - * @returns {Promise} The response. - */ -const fetcher = async (variables, token) => { - return await request( - { query: QUERY, variables }, - { Authorization: `token ${token}` }, - ); -}; - -/** - * @typedef {{ name: string; language: { name: string; }, size: number }} GistFile Gist file. - */ - -/** - * This function calculates the primary language of a gist by files size. - * - * @param {GistFile[]} files Files. - * @returns {string} Primary language. - */ -const calculatePrimaryLanguage = (files) => { - const languages = {}; - for (const file of files) { - if (file.language) { - if (languages[file.language.name]) { - languages[file.language.name] += file.size; - } else { - languages[file.language.name] = file.size; - } - } - } - let primaryLanguage = Object.keys(languages)[0]; - for (const language in languages) { - if (languages[language] > languages[primaryLanguage]) { - primaryLanguage = language; - } - } - return primaryLanguage; -}; - -/** - * @typedef {import('./types').GistData} GistData Gist data. - */ - -/** - * Fetch GitHub gist information by given username and ID. - * - * @param {string} id GitHub gist ID. - * @returns {Promise} Gist data. - */ -const fetchGist = async (id) => { - if (!id) { - throw new MissingParamError(["id"], "/api/gist?id=GIST_ID"); - } - const res = await retryer(fetcher, { gistName: id }); - if (res.data.errors) { - throw new Error(res.data.errors[0].message); - } - if (!res.data.data.viewer.gist) { - throw new Error("Gist not found"); - } - const data = res.data.data.viewer.gist; - return { - name: data.files[Object.keys(data.files)[0]].name, - nameWithOwner: `${data.owner.login}/${ - data.files[Object.keys(data.files)[0]].name - }`, - description: data.description, - language: calculatePrimaryLanguage(data.files), - starsCount: data.stargazerCount, - forksCount: data.forks.totalCount, - }; -}; - -export { fetchGist }; -export default fetchGist; diff --git a/frontend/frontend/src/backend/src/fetchers/repo.js b/frontend/frontend/src/backend/src/fetchers/repo.js deleted file mode 100644 index 72ab79e4..00000000 --- a/frontend/frontend/src/backend/src/fetchers/repo.js +++ /dev/null @@ -1,165 +0,0 @@ -// @ts-check -import { retryer } from "../common/retryer.js"; -import { MissingParamError, request } from "../common/utils.js"; -import { fetchRepoUserStats } from "./stats.js"; - -/** - * @typedef {import('axios').AxiosRequestHeaders} AxiosRequestHeaders Axios request headers. - * @typedef {import('axios').AxiosResponse} AxiosResponse Axios response. - */ - -/** - * Repo data fetcher. - * - * @param {AxiosRequestHeaders} variables Fetcher variables. - * @param {string} token GitHub token. - * @returns {Promise} The response. - */ -const fetcher = (variables, token) => { - return request( - { - query: ` - fragment RepoInfo on Repository { - name - nameWithOwner - isPrivate - isArchived - isTemplate - stargazers { - totalCount - } - description - primaryLanguage { - color - id - name - } - forkCount - } - query getRepo($login: String!, $repo: String!) { - user(login: $login) { - repository(name: $repo) { - ...RepoInfo - } - } - organization(login: $login) { - repository(name: $repo) { - ...RepoInfo - } - } - } - `, - variables, - }, - { - Authorization: `token ${token}`, - }, - ); -}; - -const urlExample = "/api/pin?username=USERNAME&repo=REPO_NAME"; - -/** - * @typedef {import("./types").RepositoryData} RepositoryData Repository data. - */ - -/** - * Fetch repository data. - * - * @param {string} username GitHub username. - * @param {string} reponame GitHub repository name. - * @returns {Promise} Repository data. - */ -const fetchRepo = async ( - username, - reponame, - include_prs_authored = false, - include_prs_commented = false, - include_prs_reviewed = false, - include_issues_authored = false, - include_issues_commented = false, -) => { - let owner = username; - if (reponame && reponame.includes("/")) { - const [parsed_owner, parsed_repo] = reponame.split("/"); - owner = parsed_owner; - reponame = parsed_repo; - } - - if (owner && !username) { - username = owner; - } - if (username && !owner) { - owner = username; - } - if (!username && !reponame) { - throw new MissingParamError(["username", "repo"], urlExample); - } - if (!username) { - throw new MissingParamError(["username"], urlExample); - } - if (!reponame) { - throw new MissingParamError(["repo"], urlExample); - } - - let res = await retryer(fetcher, { login: owner, repo: reponame }); - - const data = res.data.data; - - if (!data.user && !data.organization) { - throw new Error("Not found"); - } - - const isUser = data.organization === null && data.user; - const isOrg = data.user === null && data.organization; - - if (isUser) { - if (!data.user.repository || data.user.repository.isPrivate) { - throw new Error("User Repository Not found"); - } - let repoUserStats = await fetchRepoUserStats( - username, - [owner + "/" + reponame], - [], - include_prs_authored, - include_prs_commented, - include_prs_reviewed, - include_issues_authored, - include_issues_commented, - ); - return { - ...repoUserStats, - ...data.user.repository, - starCount: data.user.repository.stargazers.totalCount, - }; - } - - if (isOrg) { - if ( - !data.organization.repository || - data.organization.repository.isPrivate - ) { - throw new Error("Organization Repository Not found"); - } - let repoUserStats = await fetchRepoUserStats( - username, - [owner + "/" + reponame], - [], - include_prs_authored, - include_prs_commented, - include_prs_reviewed, - include_issues_authored, - include_issues_commented, - ); - return { - ...repoUserStats, - ...data.organization.repository, - starCount: data.organization.repository.stargazers.totalCount, - }; - } - - throw new Error("Unexpected behavior"); -}; - -export { fetchRepo }; -export default fetchRepo; diff --git a/frontend/frontend/src/backend/src/fetchers/stats.js b/frontend/frontend/src/backend/src/fetchers/stats.js deleted file mode 100644 index c55dd044..00000000 --- a/frontend/frontend/src/backend/src/fetchers/stats.js +++ /dev/null @@ -1,442 +0,0 @@ -// @ts-check -import axios from "axios"; -import * as dotenv from "dotenv"; -import githubUsernameRegex from "github-username-regex"; -import { calculateRank } from "../calculateRank.js"; -import { retryer } from "../common/retryer.js"; -import { - buildSearchFilter, - CustomError, - logger, - MissingParamError, - request, - wrapTextMultiline, - parseOwnerAffiliations, -} from "../common/utils.js"; - -dotenv.config(); - -// GraphQL queries. -const GRAPHQL_REPOS_FIELD = ` - repositories(first: 100, after: $after, ownerAffiliations: $ownerAffiliations, orderBy: {direction: DESC, field: STARGAZERS}) { - totalCount - nodes { - name - stargazers { - totalCount - } - } - pageInfo { - hasNextPage - endCursor - } - } -`; - -const GRAPHQL_REPOS_QUERY = ` - query userInfo($login: String!, $after: String, $ownerAffiliations: [RepositoryAffiliation]) { - user(login: $login, ownerAffiliations: $ownerAffiliations) { - ${GRAPHQL_REPOS_FIELD} - } - } -`; - -const GRAPHQL_STATS_QUERY = ` - query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $ownerAffiliations: [RepositoryAffiliation]) { - user(login: $login) { - name - login - contributionsCollection { - totalCommitContributions, - totalPullRequestReviewContributions - } - repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) { - totalCount - } - pullRequests(first: 1) { - totalCount - } - mergedPullRequests: pullRequests(states: MERGED) @include(if: $includeMergedPullRequests) { - totalCount - } - openIssues: issues(states: OPEN) { - totalCount - } - closedIssues: issues(states: CLOSED) { - totalCount - } - followers { - totalCount - } - repositoryDiscussions @include(if: $includeDiscussions) { - totalCount - } - repositoryDiscussionComments(onlyAnswers: true) @include(if: $includeDiscussionsAnswers) { - totalCount - } - ${GRAPHQL_REPOS_FIELD} - } - } -`; - -/** - * @typedef {import('axios').AxiosResponse} AxiosResponse Axios response. - */ - -/** - * Stats fetcher object. - * - * @param {object} variables Fetcher variables. - * @param {string} token GitHub token. - * @returns {Promise} Axios response. - */ -const fetcher = (variables, token) => { - const query = variables.after ? GRAPHQL_REPOS_QUERY : GRAPHQL_STATS_QUERY; - return request( - { - query, - variables, - }, - { - Authorization: `bearer ${token}`, - }, - ); -}; - -/** - * Fetch stats information for a given username. - * - * @param {object} variables Fetcher variables. - * @param {string} variables.username GitHub username. - * @param {boolean} variables.includeMergedPullRequests Include merged pull requests. - * @param {boolean} variables.includeDiscussions Include discussions. - * @param {boolean} variables.includeDiscussionsAnswers Include discussions answers. - * @param {string[]} ownerAffiliations The owner affiliations to filter by. Default: OWNER. - * @returns {Promise} Axios response. - * - * @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true. - */ -const statsFetcher = async ({ - username, - includeMergedPullRequests, - includeDiscussions, - includeDiscussionsAnswers, - ownerAffiliations, -}) => { - let stats; - let hasNextPage = true; - let endCursor = null; - while (hasNextPage) { - const variables = { - login: username, - first: 100, - after: endCursor, - includeMergedPullRequests, - includeDiscussions, - includeDiscussionsAnswers, - ownerAffiliations, - }; - let res = await retryer(fetcher, variables); - if (res.data.errors) { - return res; - } - - // Store stats data. - const repoNodes = res.data.data.user.repositories.nodes; - if (stats) { - stats.data.data.user.repositories.nodes.push(...repoNodes); - } else { - stats = res; - } - - // Disable multi page fetching on public Vercel instance due to rate limits. - const repoNodesWithStars = repoNodes.filter( - (node) => node.stargazers.totalCount !== 0, - ); - hasNextPage = - process.env.FETCH_MULTI_PAGE_STARS === "true" && - repoNodes.length === repoNodesWithStars.length && - res.data.data.user.repositories.pageInfo.hasNextPage; - endCursor = res.data.data.user.repositories.pageInfo.endCursor; - } - - return stats; -}; - -/** - * Fetch all the commits for all the repositories of a given username. - * - * @param {string} username GitHub username. - * @returns {Promise} Total commits. - * - * @description Done like this because the GitHub API does not provide a way to fetch all the commits. See - * #92#issuecomment-661026467 and #211 for more information. - */ -const totalItemsFetcher = async (username, repo, owner, type, filter) => { - if (!githubUsernameRegex.test(username)) { - logger.log("Invalid username provided."); - throw new Error("Invalid username provided."); - } - - // https://developer.github.com/v3/search/#search-commits - const fetchTotalItems = (variables, token) => { - return axios({ - method: "get", - url: - `https://api.github.com/search/` + - type + - `?per_page=1&q=` + - buildSearchFilter(variables.repo, variables.owner).replaceAll( - " ", - "+", - ) + - filter, - headers: { - "Content-Type": "application/json", - Accept: "application/vnd.github.cloak-preview", - Authorization: `token ${token}`, - }, - }); - }; - - let res; - try { - res = await retryer(fetchTotalItems, { - login: username, - repo, - owner, - }); - } catch (err) { - logger.log(err); - throw new Error(err); - } - - const totalCount = res.data.total_count; - if (isNaN(totalCount)) { - logger.error("GitHub error: " + JSON.stringify(res.data)); - throw new CustomError( - "Could not fetch data from GitHub REST API.", - CustomError.GITHUB_REST_API_ERROR, - ); - } - return totalCount; -}; - -const fetchRepoUserStats = async ( - username, - repo, - owner, - include_prs_authored, - include_prs_commented, - include_prs_reviewed, - include_issues_authored, - include_issues_commented, -) => { - let stats = {}; - if (include_prs_authored) { - stats.totalPRsAuthored = await totalItemsFetcher( - username, - repo, - owner, - "issues", - `author:${username}+type:pr`, - ); - } - if (include_prs_commented) { - stats.totalPRsCommented = await totalItemsFetcher( - username, - repo, - owner, - "issues", - `commenter:${username}+-author:${username}+type:pr`, - ); - } - if (include_prs_reviewed) { - stats.totalPRsReviewed = await totalItemsFetcher( - username, - repo, - owner, - "issues", - `reviewed-by:${username}+-author:${username}+type:pr`, - ); - } - if (include_issues_authored) { - stats.totalIssuesAuthored = await totalItemsFetcher( - username, - repo, - owner, - "issues", - `author:${username}+type:issue`, - ); - } - if (include_issues_commented) { - stats.totalIssuesCommented = await totalItemsFetcher( - username, - repo, - owner, - "issues", - `commenter:${username}+-author:${username}+type:issue`, - ); - } - return stats; -}; - -/** - * @typedef {import("./types").StatsData} StatsData Stats data. - */ - -/** - * Fetch stats for a given username. - * - * @param {string} username GitHub username. - * @param {boolean} include_all_commits Include all commits. - * @param {string[]} exclude_repo Repositories to exclude. - * @param {boolean} include_merged_pull_requests Include merged pull requests. - * @param {boolean} include_discussions Include discussions. - * @param {boolean} include_discussions_answers Include discussions answers. - * @param {string[]} ownerAffiliations Owner affiliations. Default: OWNER. - * @returns {Promise} Stats data. - */ -const fetchStats = async ( - username, - include_all_commits = false, - exclude_repo = [], - include_merged_pull_requests = false, - include_discussions = false, - include_discussions_answers = false, - repo = [], - owner = [], - include_prs_authored = false, - include_prs_commented = false, - include_prs_reviewed = false, - include_issues_authored = false, - include_issues_commented = false, - ownerAffiliations = [], -) => { - if (!username) { - throw new MissingParamError(["username"]); - } - - const stats = { - name: "", - totalPRs: 0, - totalPRsMerged: 0, - mergedPRsPercentage: 0, - totalReviews: 0, - totalCommits: 0, - totalIssues: 0, - totalStars: 0, - totalDiscussionsStarted: 0, - totalDiscussionsAnswered: 0, - contributedTo: 0, - totalPRsAuthored: 0, - totalPRsCommented: 0, - totalPRsReviewed: 0, - totalIssuesAuthored: 0, - totalIssuesCommented: 0, - rank: { level: "C", percentile: 100 }, - }; - ownerAffiliations = parseOwnerAffiliations(ownerAffiliations); - - let res = await statsFetcher({ - username, - includeMergedPullRequests: include_merged_pull_requests, - includeDiscussions: include_discussions, - includeDiscussionsAnswers: include_discussions_answers, - ownerAffiliations, - }); - - // Catch GraphQL errors. - if (res.data.errors) { - logger.error(res.data.errors); - if (res.data.errors[0].type === "NOT_FOUND") { - throw new CustomError( - res.data.errors[0].message || "Could not fetch user.", - CustomError.USER_NOT_FOUND, - ); - } - if (res.data.errors[0].message) { - throw new CustomError( - wrapTextMultiline(res.data.errors[0].message, 90, 1)[0], - res.statusText, - ); - } - throw new CustomError( - "Something went wrong while trying to retrieve the stats data using the GraphQL API.", - CustomError.GRAPHQL_ERROR, - ); - } - - const user = res.data.data.user; - - stats.name = user.name || user.login; - - // if include_all_commits, fetch all commits using the REST API. - if (include_all_commits) { - stats.totalCommits = await totalItemsFetcher( - username, - repo, - owner, - "commits", - `author:${username}`, - ); - } else { - stats.totalCommits = user.contributionsCollection.totalCommitContributions; - } - let repoUserStats = await fetchRepoUserStats( - username, - repo, - owner, - include_prs_authored, - include_prs_commented, - include_prs_reviewed, - include_issues_authored, - include_issues_commented, - ); - Object.assign(stats, repoUserStats); - - stats.totalPRs = user.pullRequests.totalCount; - if (include_merged_pull_requests) { - stats.totalPRsMerged = user.mergedPullRequests.totalCount; - stats.mergedPRsPercentage = - (user.mergedPullRequests.totalCount / user.pullRequests.totalCount) * 100; - } - stats.totalReviews = - user.contributionsCollection.totalPullRequestReviewContributions; - stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount; - if (include_discussions) { - stats.totalDiscussionsStarted = user.repositoryDiscussions.totalCount; - } - if (include_discussions_answers) { - stats.totalDiscussionsAnswered = - user.repositoryDiscussionComments.totalCount; - } - stats.contributedTo = user.repositoriesContributedTo.totalCount; - - // Retrieve stars while filtering out repositories to be hidden. - let repoToHide = new Set(exclude_repo); - - stats.totalStars = user.repositories.nodes - .filter((data) => { - return !repoToHide.has(data.name); - }) - .reduce((prev, curr) => { - return prev + curr.stargazers.totalCount; - }, 0); - - stats.rank = calculateRank({ - all_commits: include_all_commits, - commits: stats.totalCommits, - prs: stats.totalPRs, - reviews: stats.totalReviews, - issues: stats.totalIssues, - repos: user.repositories.totalCount, - stars: stats.totalStars, - followers: user.followers.totalCount, - }); - - return stats; -}; - -export { fetchStats, fetchRepoUserStats }; -export default fetchStats; diff --git a/frontend/frontend/src/backend/src/fetchers/top-languages.js b/frontend/frontend/src/backend/src/fetchers/top-languages.js deleted file mode 100644 index 816c63a2..00000000 --- a/frontend/frontend/src/backend/src/fetchers/top-languages.js +++ /dev/null @@ -1,170 +0,0 @@ -// @ts-check -import { retryer } from "../common/retryer.js"; -import { - CustomError, - logger, - MissingParamError, - request, - wrapTextMultiline, - parseOwnerAffiliations, -} from "../common/utils.js"; - -/** - * @typedef {import("axios").AxiosRequestHeaders} AxiosRequestHeaders Axios request headers. - * @typedef {import("axios").AxiosResponse} AxiosResponse Axios response. - */ - -/** - * Top languages fetcher object. - * - * @param {AxiosRequestHeaders} variables Fetcher variables. - * @param {string} token GitHub token. - * @returns {Promise} Languages fetcher response. - */ -const fetcher = (variables, token) => { - return request( - { - query: ` - query userInfo($login: String!, $ownerAffiliations: [RepositoryAffiliation]) { - user(login: $login) { - # do not fetch forks - repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) { - nodes { - name - languages(first: 10, orderBy: {field: SIZE, direction: DESC}) { - edges { - size - node { - color - name - } - } - } - } - } - } - } - `, - variables, - }, - { - Authorization: `token ${token}`, - }, - ); -}; - -/** - * @typedef {import("./types").TopLangData} TopLangData Top languages data. - */ - -/** - * Fetch top languages for a given username. - * - * @param {string} username GitHub username. - * @param {string[]} exclude_repo List of repositories to exclude. Default: []. - * @param {number} size_weight Weightage to be given to size. - * @param {number} count_weight Weightage to be given to count. - * @param {string[]} ownerAffiliations The owner affiliations to filter by. Default: OWNER. - * @returns {Promise} Top languages data. - */ -const fetchTopLanguages = async ( - username, - exclude_repo = [], - size_weight = 1, - count_weight = 0, - ownerAffiliations = [], -) => { - if (!username) { - throw new MissingParamError(["username"]); - } - ownerAffiliations = parseOwnerAffiliations(ownerAffiliations); - - const res = await retryer(fetcher, { login: username, ownerAffiliations }); - - if (res.data.errors) { - logger.error(res.data.errors); - if (res.data.errors[0].type === "NOT_FOUND") { - throw new CustomError( - res.data.errors[0].message || "Could not fetch user.", - CustomError.USER_NOT_FOUND, - ); - } - if (res.data.errors[0].message) { - throw new CustomError( - wrapTextMultiline(res.data.errors[0].message, 90, 1)[0], - res.statusText, - ); - } - throw new CustomError( - "Something went wrong while trying to retrieve the language data using the GraphQL API.", - CustomError.GRAPHQL_ERROR, - ); - } - - let repoNodes = res.data.data.user.repositories.nodes; - let repoToHide = {}; - - // populate repoToHide map for quick lookup - // while filtering out - if (exclude_repo) { - exclude_repo.forEach((repoName) => { - repoToHide[repoName] = true; - }); - } - - // filter out repositories to be hidden - repoNodes = repoNodes - .sort((a, b) => b.size - a.size) - .filter((name) => !repoToHide[name.name]); - - let repoCount = 0; - - repoNodes = repoNodes - .filter((node) => node.languages.edges.length > 0) - // flatten the list of language nodes - .reduce((acc, curr) => curr.languages.edges.concat(acc), []) - .reduce((acc, prev) => { - // get the size of the language (bytes) - let langSize = prev.size; - - // if we already have the language in the accumulator - // & the current language name is same as previous name - // add the size to the language size and increase repoCount. - if (acc[prev.node.name] && prev.node.name === acc[prev.node.name].name) { - langSize = prev.size + acc[prev.node.name].size; - repoCount += 1; - } else { - // reset repoCount to 1 - // language must exist in at least one repo to be detected - repoCount = 1; - } - return { - ...acc, - [prev.node.name]: { - name: prev.node.name, - color: prev.node.color, - size: langSize, - count: repoCount, - }, - }; - }, {}); - - Object.keys(repoNodes).forEach((name) => { - // comparison index calculation - repoNodes[name].size = - Math.pow(repoNodes[name].size, size_weight) * - Math.pow(repoNodes[name].count, count_weight); - }); - - const topLangs = Object.keys(repoNodes) - .sort((a, b) => repoNodes[b].size - repoNodes[a].size) - .reduce((result, key) => { - result[key] = repoNodes[key]; - return result; - }, {}); - - return topLangs; -}; - -export { fetchTopLanguages }; -export default fetchTopLanguages; diff --git a/frontend/frontend/src/backend/src/fetchers/types.d.ts b/frontend/frontend/src/backend/src/fetchers/types.d.ts deleted file mode 100644 index 1588c921..00000000 --- a/frontend/frontend/src/backend/src/fetchers/types.d.ts +++ /dev/null @@ -1,128 +0,0 @@ -export type GistData = { - name: string; - nameWithOwner: string; - description: string | null; - language: string | null; - starsCount: number; - forksCount: number; -}; - -export type RepositoryData = { - name: string; - nameWithOwner: string; - isPrivate: boolean; - isArchived: boolean; - isTemplate: boolean; - stargazers: { totalCount: number }; - description: string; - primaryLanguage: { - color: string; - id: string; - name: string; - }; - forkCount: number; - starCount: number; - totalPRsAuthored: number; - totalPRsCommented: number; - totalPRsReviewed: number; - totalIssuesAuthored: number; - totalIssuesCommented: number; -}; - -export type StatsData = { - name: string; - totalPRs: number; - totalPRsMerged: number; - mergedPRsPercentage: number; - totalReviews: number; - totalCommits: number; - totalIssues: number; - totalStars: number; - totalDiscussionsStarted: number; - totalDiscussionsAnswered: number; - contributedTo: number; - totalPRsAuthored: number; - totalPRsCommented: number; - totalPRsReviewed: number; - totalIssuesAuthored: number; - totalIssuesCommented: number; - rank: { level: string; percentile: number }; -}; - -export type Lang = { - name: string; - color: string; - size: number; -}; - -export type TopLangData = Record; - -export type WakaTimeData = { - categories: { - digital: string; - hours: number; - minutes: number; - name: string; - percent: number; - text: string; - total_seconds: number; - }[]; - daily_average: number; - daily_average_including_other_language: number; - days_including_holidays: number; - days_minus_holidays: number; - editors: { - digital: string; - hours: number; - minutes: number; - name: string; - percent: number; - text: string; - total_seconds: number; - }[]; - holidays: number; - human_readable_daily_average: string; - human_readable_daily_average_including_other_language: string; - human_readable_total: string; - human_readable_total_including_other_language: string; - id: string; - is_already_updating: boolean; - is_coding_activity_visible: boolean; - is_including_today: boolean; - is_other_usage_visible: boolean; - is_stuck: boolean; - is_up_to_date: boolean; - languages: { - digital: string; - hours: number; - minutes: number; - name: string; - percent: number; - text: string; - total_seconds: number; - }[]; - operating_systems: { - digital: string; - hours: number; - minutes: number; - name: string; - percent: number; - text: string; - total_seconds: number; - }[]; - percent_calculated: number; - range: string; - status: string; - timeout: number; - total_seconds: number; - total_seconds_including_other_language: number; - user_id: string; - username: string; - writes_only: boolean; -}; - -export type WakaTimeLang = { - name: string; - text: string; - percent: number; -}; diff --git a/frontend/frontend/src/backend/src/fetchers/wakatime.js b/frontend/frontend/src/backend/src/fetchers/wakatime.js deleted file mode 100644 index f69d6ae4..00000000 --- a/frontend/frontend/src/backend/src/fetchers/wakatime.js +++ /dev/null @@ -1,35 +0,0 @@ -import axios from "axios"; -import { CustomError, MissingParamError } from "../common/utils.js"; - -/** - * WakaTime data fetcher. - * - * @param {{username: string, api_domain: string }} props Fetcher props. - * @returns {Promise} WakaTime data response. - */ -const fetchWakatimeStats = async ({ username, api_domain }) => { - if (!username) { - throw new MissingParamError(["username"]); - } - - try { - const { data } = await axios.get( - `https://${ - api_domain ? api_domain.replace(/\/$/gi, "") : "wakatime.com" - }/api/v1/users/${username}/stats?is_including_today=true`, - ); - - return data.data; - } catch (err) { - if (err.response.status < 200 || err.response.status > 299) { - throw new CustomError( - `Could not resolve to a User with the login of '${username}'`, - "WAKATIME_USER_NOT_FOUND", - ); - } - throw err; - } -}; - -export { fetchWakatimeStats }; -export default fetchWakatimeStats; diff --git a/frontend/frontend/src/backend/src/index.js b/frontend/frontend/src/backend/src/index.js deleted file mode 100644 index ca8d586d..00000000 --- a/frontend/frontend/src/backend/src/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./common/index.js"; -export * from "./cards/index.js"; diff --git a/frontend/frontend/src/backend/src/repeatRequests.js b/frontend/frontend/src/backend/src/repeatRequests.js deleted file mode 100644 index 033f7fe5..00000000 --- a/frontend/frontend/src/backend/src/repeatRequests.js +++ /dev/null @@ -1,65 +0,0 @@ -import axios from "axios"; -import { - pool, - deleteOldRequests, - getRecentRequests, -} from "./common/database.js"; - -/** - * Processes URLs with a thread pool of given size using axios.get. - * - * @param {string[]} urls An array of URLs to process. - * @param {number} poolSize The number of concurrent requests to process. - * @returns {Promise} A promise that resolves when all requests are processed. - */ -async function makeRequests(urls, poolSize) { - let current = 0; - - /** - * Worker function to process `urls`. - */ - async function worker() { - while (true) { - let idx = current++; - if (idx >= urls.length) { - break; - } - const url = "https://" + process.env.VERCEL_BRANCH_URL + urls[idx]; - try { - if (idx % 10 === 0) { - console.log(`Processing request ${idx + 1} out of ${urls.length}`); - } - await axios.get(url, { - timeout: 10000, - headers: { "x-bypass-store": "true" }, - }); - } catch (err) { - console.error(`Error fetching ${url}:`, err.message); - } - } - } - - const workers = []; - for (let i = 0; i < poolSize; i++) { - workers.push(worker()); - } - await Promise.all(workers); -} - -/** - * Repeats requests made in the last 8 days, excluding those made in the last 11 hours. - */ -export async function repeatRecentRequests() { - if (!pool) { - console.error("Postgres pool is not initialized."); - return; - } - - await deleteOldRequests(); - const urls = await getRecentRequests(); - if (urls.length === 0) { - console.log("No recent requests found."); - } else { - await makeRequests(urls, 5); - } -} diff --git a/frontend/frontend/src/backend/src/translations.js b/frontend/frontend/src/backend/src/translations.js deleted file mode 100644 index 719cec71..00000000 --- a/frontend/frontend/src/backend/src/translations.js +++ /dev/null @@ -1,827 +0,0 @@ -// @ts-check - -import { encodeHTML } from "./common/utils.js"; - -/** - * Retrieves stat card labels in the available locales. - * - * @param {object} props Function arguments. - * @param {string} props.name The name of the locale. - * @param {string} props.apostrophe Whether to use apostrophe or not. - * @returns {object} The locales object. - * - * @see https://www.andiamo.co.uk/resources/iso-language-codes/ for language codes. - */ -const statCardLocales = ({ name, apostrophe }) => { - const encodedName = encodeHTML(name); - return { - "statcard.title": { - ar: `${encodedName} إحصائيات جيت هاب`, - az: `${encodedName}'${apostrophe} Hesabının GitHub Statistikası`, - cn: `${encodedName} 的 GitHub 统计数据`, - "zh-tw": `${encodedName} 的 GitHub 統計資料`, - cs: `GitHub statistiky uživatele ${encodedName}`, - de: `${encodedName + apostrophe} GitHub-Statistiken`, - en: `${encodedName}'${apostrophe} GitHub Stats`, - bn: `${encodedName} এর GitHub পরিসংখ্যান`, - es: `Estadísticas de GitHub de ${encodedName}`, - fi: `${encodedName}:n GitHub-tilastot`, - fr: `Statistiques GitHub de ${encodedName}`, - hu: `${encodedName} GitHub statisztika`, - it: `Statistiche GitHub di ${encodedName}`, - ja: `${encodedName}の GitHub 統計`, - kr: `${encodedName}의 GitHub 통계`, - nl: `${encodedName}'${apostrophe} GitHub-statistieken`, - "pt-pt": `Estatísticas do GitHub de ${encodedName}`, - "pt-br": `Estatísticas do GitHub de ${encodedName}`, - np: `${encodedName}'${apostrophe} गिटहब तथ्याङ्क`, - el: `Στατιστικά GitHub του ${encodedName}`, - ro: `Statisticile GitHub ale lui ${encodedName}`, - ru: `Статистика GitHub пользователя ${encodedName}`, - "uk-ua": `Статистика GitHub користувача ${encodedName}`, - id: `Statistik GitHub ${encodedName}`, - ml: `${encodedName}'${apostrophe} ഗിറ്റ്ഹബ് സ്ഥിതിവിവരക്കണക്കുകൾ`, - my: `Statistik GitHub ${encodedName}`, - sk: `GitHub štatistiky používateľa ${encodedName}`, - tr: `${encodedName} Hesabının GitHub İstatistikleri`, - pl: `Statystyki GitHub użytkownika ${encodedName}`, - uz: `${encodedName}ning GitHub'dagi statistikasi`, - vi: `Thống Kê GitHub ${encodedName}`, - se: `GitHubstatistik för ${encodedName}`, - }, - "statcard.ranktitle": { - ar: `${encodedName} إحصائيات جيت هاب`, - az: `${encodedName}'${apostrophe} Hesabının GitHub Statistikası`, - cn: `${encodedName} 的 GitHub 统计数据`, - "zh-tw": `${encodedName} 的 GitHub 統計資料`, - cs: `GitHub statistiky uživatele ${encodedName}`, - de: `${encodedName + apostrophe} GitHub-Statistiken`, - en: `${encodedName}'${apostrophe} GitHub Rank`, - bn: `${encodedName} এর GitHub পরিসংখ্যান`, - es: `Estadísticas de GitHub de ${encodedName}`, - fi: `${encodedName}:n GitHub-sijoitus`, - fr: `Statistiques GitHub de ${encodedName}`, - hu: `${encodedName} GitHub statisztika`, - it: `Statistiche GitHub di ${encodedName}`, - ja: `${encodedName} の GitHub ランク`, - kr: `${encodedName}의 GitHub 통계`, - nl: `${encodedName}'${apostrophe} GitHub-statistieken`, - "pt-pt": `Estatísticas do GitHub de ${encodedName}`, - "pt-br": `Estatísticas do GitHub de ${encodedName}`, - np: `${encodedName}'${apostrophe} गिटहब तथ्याङ्क`, - el: `Στατιστικά GitHub του ${encodedName}`, - ro: `Rankul GitHub al lui ${encodedName}`, - ru: `Рейтинг GitHub пользователя ${encodedName}`, - "uk-ua": `Статистика GitHub користувача ${encodedName}`, - id: `Statistik GitHub ${encodedName}`, - ml: `${encodedName}'${apostrophe} ഗിറ്റ്ഹബ് സ്ഥിതിവിവരക്കണക്കുകൾ`, - my: `Statistik GitHub ${encodedName}`, - sk: `GitHub štatistiky používateľa ${encodedName}`, - tr: `${encodedName} Hesabının GitHub Yıldızları`, - pl: `Statystyki GitHub użytkownika ${encodedName}`, - uz: `${encodedName}ning GitHub'dagi statistikasi`, - vi: `Thống Kê GitHub ${encodedName}`, - se: `GitHubstatistik för ${encodedName}`, - }, - "statcard.totalstars": { - ar: "مجموع النجوم", - az: "Ümumi Ulduz", - cn: "获标星数", - "zh-tw": "得標星星數量(Star)", - cs: "Celkem hvězd", - de: "Insgesamt erhaltene Sterne", - en: "Total Stars Earned", - bn: "সর্বমোট Star", - es: "Estrellas totales", - fi: "Ansaitut tähdet yhteensä", - fr: "Total d'étoiles", - hu: "Csillagok", - it: "Stelle totali", - ja: "スターされた数", - kr: "받은 스타 수", - nl: "Totaal Sterren Ontvangen", - "pt-pt": "Total de estrelas", - "pt-br": "Total de estrelas", - np: "कुल ताराहरू", - el: "Σύνολο Αστεριών", - ro: "Total de stele câștigate", - ru: "Всего звёзд", - "uk-ua": "Всього зірок", - id: "Total Bintang", - ml: "ആകെ നക്ഷത്രങ്ങൾ", - my: "Jumlah Bintang", - sk: "Hviezdy", - tr: "Toplam Yıldız", - pl: "Liczba otrzymanych gwiazdek", - uz: "Yulduzchalar", - vi: "Tổng Số Sao", - se: "Antal intjänade stjärnor", - }, - "statcard.commits": { - ar: "مجموع المساهمات", - az: "Ümumi Commit", - cn: "累计提交总数", - "zh-tw": "累計提交數量(Commit)", - cs: "Celkem commitů", - de: "Anzahl Commits", - en: "Total Commits", - bn: "সর্বমোট Commit", - es: "Commits totales", - fi: "Yhteensä committeja", - fr: "Total des Commits", - hu: "Összes commit", - it: "Commit totali", - ja: "合計コミット数", - kr: "전체 커밋 수", - nl: "Aantal commits", - "pt-pt": "Total de Commits", - "pt-br": "Total de Commits", - np: "कुल Commits", - el: "Σύνολο Commits", - ro: "Total Commit-uri", - ru: "Всего коммитов", - "uk-ua": "Всього комітів", - id: "Total Komitmen", - ml: "ആകെ കമ്മിറ്റുകൾ", - my: "Jumlah Komitmen", - sk: "Všetky commity", - tr: "Toplam Commit", - pl: "Wszystkie commity", - uz: "'Commit'lar", - vi: "Tổng Số Cam Kết", - se: "Totalt antal commits", - }, - "statcard.prs": { - ar: "مجموع طلبات السحب", - az: "Ümumi PR", - cn: "发起的 PR 总数", - "zh-tw": "拉取請求數量(PR)", - cs: "Celkem PRs", - de: "PRs Insgesamt", - en: "Total PRs", - bn: "সর্বমোট PR", - es: "PRs totales", - fi: "Yhteensä PR:t", - fr: "Total des PRs", - hu: "Összes PR", - it: "PR totali", - ja: "合計 PR", - kr: "PR 횟수", - nl: "Aantal PR's", - "pt-pt": "Total de PRs", - "pt-br": "Total de PRs", - np: "कुल PRs", - el: "Σύνολο PRs", - ro: "Total PR-uri", - ru: "Всего запросов изменений", - "uk-ua": "Всього pull request`iв", - id: "Total Permintaan Tarik", - ml: "ആകെ പുൾ അഭ്യർത്ഥനകൾ", - my: "Jumlah PR", - sk: "Všetky PR", - tr: "Toplam PR", - pl: "Wszystkie PR-y", - uz: "'Pull Request'lar", - vi: "Tổng Số PR", - se: "Totalt antal PR", - }, - "statcard.issues": { - ar: "مجموع التحسينات", - az: "Ümumi Problem", - cn: "提出的 issue 总数", - "zh-tw": "提出問題數量(Issue)", - cs: "Celkem problémů", - de: "Anzahl Issues", - en: "Total Issues", - bn: "সর্বমোট Issue", - es: "Issues totales", - fi: "Yhteensä ongelmat", - fr: "Nombre total d'incidents", - hu: "Összes hibajegy", - it: "Segnalazioni totali", - ja: "合計 issue", - kr: "이슈 개수", - nl: "Aantal kwesties", - "pt-pt": "Total de Issues", - "pt-br": "Total de Issues", - np: "कुल मुद्दाहरू", - el: "Σύνολο Ζητημάτων", - ro: "Total Issue-uri", - ru: "Всего вопросов", - "uk-ua": "Всього issue", - id: "Total Masalah Dilaporkan", - ml: "ആകെ ലക്കങ്ങൾ", - my: "Jumlah Isu Dilaporkan", - sk: "Všetky problémy", - tr: "Toplam Hata", - pl: "Wszystkie problemy", - uz: "'Issue'lar", - vi: "Tổng Số Vấn Đề", - se: "Total antal issues", - }, - "statcard.contribs": { - ar: "ساهم في (العام الماضي)", - az: "Töhfə verdi (ötən il)", - cn: "贡献的项目数(去年)", - "zh-tw": "參與項目數量(去年)", - cs: "Přispěl k (minulý rok)", - de: "Beigetragen zu (letztes Jahr)", - en: "Contributed to (last year)", - bn: "অবদান (গত বছর)", - es: "Contribuciones en (el año pasado)", - fi: "Osallistunut (viime vuonna)", - fr: "Contribué à (l'année dernière)", - hu: "Hozzájárulások (tavaly)", - it: "Ha contribuito a (l'anno scorso)", - ja: "貢献したリポジトリ (昨年)", - kr: "(작년) 기여", - nl: "Bijgedragen aan (vorig jaar)", - "pt-pt": "Contribuiu em (ano passado)", - "pt-br": "Contribuiu para (ano passado)", - np: "कुल योगदानहरू (गत वर्ष)", - el: "Συνεισφέρθηκε σε (πέρυσι)", - ro: "Total Contribuiri", - ru: "Внесено вклада (за прошлый год)", - "uk-ua": "Зробив внесок у (за минулий рік)", - id: "Berkontribusi ke (tahun lalu)", - ml: "സമർപ്പിച്ചിരിക്കുന്നത് (കഴിഞ്ഞ വർഷം)", - my: "Menyumbang kepada (tahun lepas)", - sk: "Účasti (minulý rok)", - tr: "Katkı Verildi (geçen yıl)", - pl: "Kontrybucje (w zeszłym roku)", - uz: "Hissa qoʻshgan (o'tgan yili)", - vi: "Đã Đóng Góp (năm ngoái)", - se: "Bidragit till (förra året)", - }, - "statcard.reviews": { - ar: "طلبات السحب التي تم مراجعتها", - az: "Nəzərdən Keçirilən Ümumi PR", - cn: "审查的 PR 总数", - "zh-tw": "審核的 PR 總計", - cs: "Celkový počet PR", - de: "Insgesamt überprüfte PRs", - en: "Total PRs Reviewed", - bn: "সর্বমোট পুনরালোচনা করা PR", - es: "PR totales revisados", - fi: "Yhteensä tarkastettuja PR:itä", - fr: "Nombre total de PR examinés", - hu: "Összes ellenőrzött PR", - it: "PR totali esaminati", - ja: "レビューされた PR の総数", - kr: "검토된 총 PR", - nl: "Totaal beoordeelde PR's", - "pt-pt": "Total de PRs revistos", - "pt-br": "Total de PRs revisados", - np: "कुल पीआर समीक्षित", - el: "Σύνολο Αναθεωρημένων PR", - ro: "Total PR-uri Revizuite", - ru: "Всего запросов проверено", - "uk-ua": "Всього pull request`iв перевірено", - id: "Total PR yang Direview", - ml: "ആകെ പുൾ അഭിപ്രായങ്ങൾ", - my: "Jumlah PR Dikaji Semula", - sk: "Celkový počet PR", - tr: "İncelenen toplam PR", - pl: "Łącznie sprawdzonych PR", - uz: "Koʻrib chiqilgan PR-lar soni", - vi: "Tổng Số PR Đã Xem Xét", - se: "Totalt antal granskade PR", - }, - "statcard.discussions-started": { - ar: "مجموع المناقشات التي بدأها", - az: "Başladılan Ümumi Müzakirə", - cn: "发起的讨论总数", - "zh-tw": "發起的討論總數", - cs: "Celkem zahájených diskusí", - de: "Gesamt gestartete Diskussionen", - en: "Total Discussions Started", - bn: "সর্বমোট আলোচনা শুরু", - es: "Discusiones totales iniciadas", - fi: "Aloitetut keskustelut yhteensä", - fr: "Nombre total de discussions lancées", - hu: "Összes megkezdett megbeszélés", - it: "Discussioni totali avviate", - ja: "開始されたディスカッションの総数", - kr: "시작된 토론 총 수", - nl: "Totaal gestarte discussies", - "pt-pt": "Total de Discussões Iniciadas", - "pt-br": "Total de Discussões Iniciadas", - np: "कुल चर्चा सुरु", - el: "Σύνολο Συζητήσεων που Ξεκίνησαν", - ro: "Total Discuții Începute", - ru: "Всего начатых обсуждений", - "uk-ua": "Всього розпочатих дискусій", - id: "Total Diskusi Dimulai", - ml: "ആരംഭിച്ച ആലോചനകൾ", - my: "Jumlah Perbincangan Bermula", - sk: "Celkový počet začatých diskusií", - tr: "Başlatılan Toplam Tartışma", - pl: "Łącznie rozpoczętych dyskusji", - uz: "Boshlangan muzokaralar soni", - vi: "Tổng Số Thảo Luận Bắt Đầu", - se: "Totalt antal diskussioner startade", - }, - "statcard.discussions-answered": { - ar: "مجموع المناقشات المُجابة", - az: "Cavablandırılan Ümumi Müzakirə", - cn: "回复的讨论总数", - "zh-tw": "回覆討論總計", - cs: "Celkem zodpovězených diskusí", - de: "Gesamt beantwortete Diskussionen", - en: "Total Discussions Answered", - bn: "সর্বমোট আলোচনা উত্তর", - es: "Discusiones totales respondidas", - fi: "Vastatut keskustelut yhteensä", - fr: "Nombre total de discussions répondues", - hu: "Összes megválaszolt megbeszélés", - it: "Discussioni totali risposte", - ja: "回答されたディスカッションの総数", - kr: "답변된 토론 총 수", - nl: "Totaal beantwoorde discussies", - "pt-pt": "Total de Discussões Respondidas", - "pt-br": "Total de Discussões Respondidas", - np: "कुल चर्चा उत्तर", - el: "Σύνολο Συζητήσεων που Απαντήθηκαν", - ro: "Total Răspunsuri La Discuții", - ru: "Всего отвеченных обсуждений", - "uk-ua": "Всього відповідей на дискусії", - id: "Total Diskusi Dibalas", - ml: "ഉത്തരം നൽകിയ ആലോചനകൾ", - my: "Jumlah Perbincangan Dijawab", - sk: "Celkový počet zodpovedaných diskusií", - tr: "Toplam Cevaplanan Tartışma", - pl: "Łącznie odpowiedzianych dyskusji", - uz: "Javob berilgan muzokaralar soni", - vi: "Tổng Số Thảo Luận Đã Trả Lời", - se: "Totalt antal diskussioner besvarade", - }, - "statcard.prs-authored": { - en: "PRs Created", - }, - "statcard.prs-commented": { - en: "PRs Commented", - }, - "statcard.prs-reviewed": { - en: "PRs Reviewed", - }, - "statcard.issues-authored": { - en: "Issues Created", - }, - "statcard.issues-commented": { - en: "Issues Commented", - }, - "statcard.prs-merged": { - ar: "مجموع طلبات السحب المُدمجة", - az: "Birləşdirilmiş Ümumi PR", - cn: "合并的 PR 总数", - "zh-tw": "合併的 PR 總計", - cs: "Celkem sloučených PR", - de: "Insgesamt zusammengeführte PRs", - en: "Total PRs Merged", - bn: "সর্বমোট PR একত্রীকৃত", - es: "PR totales fusionados", - fi: "Yhteensä yhdistetyt PR:t", - fr: "Nombre total de PR fusionnés", - hu: "Összes egyesített PR", - it: "PR totali uniti", - ja: "マージされた PR の総数", - kr: "병합된 총 PR", - nl: "Totaal samengevoegde PR's", - "pt-pt": "Total de PRs Fundidos", - "pt-br": "Total de PRs Integrados", - np: "कुल PRs मर्ज गरिएको", - el: "Σύνολο Συγχωνευμένων PR", - ro: "Total PR-uri Fuzionate", - ru: "Всего объединённых запросов", - "uk-ua": "Всього об'єднаних pull request`iв", - id: "Total PR Digabungkan", - my: "Jumlah PR Digabungkan", - sk: "Celkový počet zlúčených PR", - tr: "Toplam Birleştirilmiş PR", - pl: "Łącznie połączonych PR", - uz: "Birlangan PR-lar soni", - vi: "Tổng Số PR Đã Hợp Nhất", - se: "Totalt antal sammanfogade PR", - }, - "statcard.prs-merged-percentage": { - ar: "نسبة طلبات السحب المُدمجة", - az: "Birləşdirilmiş PR-ların Faizi", - cn: "被合并的 PR 占比", - "zh-tw": "合併的 PR 百分比", - cs: "Sloučené PRs v procentech", - de: "Zusammengeführte PRs in Prozent", - en: "Merged PRs Percentage", - bn: "PR একত্রীকরণের শতাংশ", - es: "Porcentaje de PR fusionados", - fi: "Yhdistettyjen PR:ien prosentti", - fr: "Pourcentage de PR fusionnés", - hu: "Egyesített PR-k százaléka", - it: "Percentuale di PR uniti", - ja: "マージされた PR の割合", - kr: "병합된 PR의 비율", - nl: "Percentage samengevoegde PR's", - "pt-pt": "Percentagem de PRs Fundidos", - "pt-br": "Porcentagem de PRs Integrados", - np: "PR मर्ज गरिएको प्रतिशत", - el: "Ποσοστό Συγχωνευμένων PR", - ro: "Procentaj PR-uri Fuzionate", - ru: "Процент объединённых запросов", - "uk-ua": "Відсоток об'єднаних pull request`iв", - id: "Persentase PR Digabungkan", - my: "Peratus PR Digabungkan", - sk: "Percento zlúčených PR", - tr: "Birleştirilmiş PR Yüzdesi", - pl: "Procent połączonych PR", - uz: "Birlangan PR-lar foizi", - vi: "Tỷ Lệ PR Đã Hợp Nhất", - se: "Procent av sammanfogade PR", - }, - }; -}; - -const repoCardLocales = { - "repocard.template": { - ar: "قالب", - az: "Şablon", - bn: "টেমপ্লেট", - cn: "模板", - "zh-tw": "模板", - cs: "Šablona", - de: "Vorlage", - en: "Template", - es: "Plantilla", - fi: "Malli", - fr: "Modèle", - hu: "Sablon", - it: "Template", - ja: "テンプレート", - kr: "템플릿", - nl: "Sjabloon", - "pt-pt": "Modelo", - "pt-br": "Modelo", - np: "टेम्पलेट", - el: "Πρότυπο", - ro: "Șablon", - ru: "Шаблон", - "uk-ua": "Шаблон", - id: "Pola", - ml: "ടെംപ്ലേറ്റ്", - my: "Templat", - sk: "Šablóna", - tr: "Şablon", - pl: "Szablony", - uz: "Shablon", - vi: "Mẫu", - se: "Mall", - }, - "repocard.archived": { - ar: "مُؤرشف", - az: "Arxiv", - bn: "আর্কাইভড", - cn: "已归档", - "zh-tw": "已封存", - cs: "Archivováno", - de: "Archiviert", - en: "Archived", - es: "Archivados", - fi: "Arkistoitu", - fr: "Archivé", - hu: "Archivált", - it: "Archiviata", - ja: "アーカイブ済み", - kr: "보관됨", - nl: "Gearchiveerd", - "pt-pt": "Arquivados", - "pt-br": "Arquivados", - np: "अभिलेख राखियो", - el: "Αρχειοθετημένα", - ro: "Arhivat", - ru: "Архивирован", - "uk-ua": "Архивований", - id: "Arsip", - ml: "ശേഖരിച്ചത്", - my: "Arkib", - sk: "Archivované", - tr: "Arşiv", - pl: "Zarchiwizowano", - uz: "Arxivlangan", - vi: "Đã Lưu Trữ", - se: "Arkiverade", - }, - "repocard.prs-authored": { - en: "my created PRs", - }, - "repocard.prs-commented": { - en: "my commented PRs", - }, - "repocard.prs-reviewed": { - en: "my reviewed PRs", - }, - "repocard.issues-authored": { - en: "my created issues", - }, - "repocard.issues-commented": { - en: "my commented issues", - }, -}; - -const langCardLocales = { - "langcard.title": { - ar: "أكثر اللغات استخدامًا", - az: "Ən Çox İstifadə Olunan Dillər", - cn: "最常用的语言", - "zh-tw": "最常用的語言", - cs: "Nejpoužívanější jazyky", - de: "Meist verwendete Sprachen", - bn: "সর্বাধিক ব্যবহৃত ভাষা সমূহ", - en: "Most Used Languages", - es: "Lenguajes más usados", - fi: "Käytetyimmät kielet", - fr: "Langages les plus utilisés", - hu: "Leggyakrabban használt nyelvek", - it: "Linguaggi più utilizzati", - ja: "最もよく使っている言語", - kr: "가장 많이 사용된 언어", - nl: "Meest gebruikte talen", - "pt-pt": "Linguagens mais usadas", - "pt-br": "Linguagens mais usadas", - np: "अधिक प्रयोग गरिएको भाषाहरू", - el: "Οι περισσότερο χρησιμοποιούμενες γλώσσες", - ro: "Cele Mai Folosite Limbaje", - ru: "Наиболее используемые языки", - "uk-ua": "Найчастіше використовувані мови", - id: "Bahasa Yang Paling Banyak Digunakan", - ml: "കൂടുതൽ ഉപയോഗിച്ച ഭാഷകൾ", - my: "Bahasa Paling Digunakan", - sk: "Najviac používané jazyky", - tr: "En Çok Kullanılan Diller", - pl: "Najczęściej używane języki", - uz: "Eng koʻp ishlatiladigan tillar", - vi: "Ngôn Ngữ Thường Sử Dụng", - se: "Mest använda språken", - }, - "langcard.nodata": { - ar: "لا توجد بيانات للغات.", - az: "Dil məlumatı yoxdur.", - cn: "没有语言数据。", - "zh-tw": "沒有語言資料。", - cs: "Žádné jazykové údaje.", - de: "Keine Sprachdaten.", - bn: "কোন ভাষার ডেটা নেই।", - en: "No languages data.", - es: "Sin datos de idiomas.", - fi: "Ei kielitietoja.", - fr: "Aucune donnée sur les langues.", - hu: "Nincsenek nyelvi adatok.", - it: "Nessun dato sulle lingue.", - ja: "言語データがありません。", - kr: "언어 데이터가 없습니다.", - nl: "Ingen sprogdata.", - "pt-pt": "Sem dados de linguagens.", - "pt-br": "Sem dados de linguagens.", - np: "कुनै भाषा डाटा छैन।", - el: "Δεν υπάρχουν δεδομένα γλωσσών.", - ro: "Lipsesc date despre limbă.", - ru: "Нет данных о языках.", - "uk-ua": "Немає даних про мови.", - id: "Tidak ada data bahasa.", - ml: "ഭാഷാ ഡാറ്റയില്ല.", - my: "Tiada data bahasa.", - sk: "Žiadne údaje o jazykoch.", - tr: "Dil verisi yok.", - pl: "Brak danych dotyczących języków.", - uz: "Til haqida ma'lumot yo'q.", - vi: "Không có dữ liệu ngôn ngữ.", - se: "Inga språkdata.", - }, -}; - -const wakatimeCardLocales = { - "wakatimecard.title": { - ar: "إحصائيات واكا تايم", - az: "WakaTime Statistikası", - cn: "WakaTime 周统计", - "zh-tw": "WakaTime 周統計", - cs: "Statistiky WakaTime", - de: "WakaTime Status", - en: "WakaTime Stats", - bn: "WakaTime স্ট্যাটাস", - es: "Estadísticas de WakaTime", - fi: "WakaTime-tilastot", - fr: "Statistiques de WakaTime", - hu: "WakaTime statisztika", - it: "Statistiche WakaTime", - ja: "WakaTime ワカタイム統計", - kr: "WakaTime 주간 통계", - nl: "WakaTime-statistieken", - "pt-pt": "Estatísticas WakaTime", - "pt-br": "Estatísticas WakaTime", - np: "WakaTime तथ्या .्क", - el: "Στατιστικά WakaTime", - ro: "Statistici WakaTime", - ru: "Статистика WakaTime", - "uk-ua": "Статистика WakaTime", - id: "Status WakaTime", - ml: "വേക്ക് ടൈം സ്ഥിതിവിവരക്കണക്കുകൾ", - my: "Statistik WakaTime", - sk: "WakaTime štatistika", - tr: "WakaTime İstatistikler", - pl: "Statystyki WakaTime", - uz: "WakaTime statistikasi", - vi: "Thống Kê WakaTime", - se: "WakaTime statistik", - }, - "wakatimecard.lastyear": { - ar: "العام الماضي", - az: "Ötən il", - cn: "去年", - "zh-tw": "去年", - cs: "Minulý rok", - de: "Letztes Jahr", - en: "last year", - bn: "গত বছর", - es: "El año pasado", - fi: "Viime vuosi", - fr: "L'année dernière", - hu: "Tavaly", - it: "L'anno scorso", - ja: "昨年", - kr: "작년", - nl: "Vorig jaar", - "pt-pt": "Ano passado", - "pt-br": "Ano passado", - np: "गत वर्ष", - el: "Πέρυσι", - ro: "Anul trecut", - ru: "За прошлый год", - "uk-ua": "За минулий рік", - id: "Tahun lalu", - ml: "കഴിഞ്ഞ വർഷം", - my: "Tahun lepas", - sk: "Minulý rok", - tr: "Geçen yıl", - pl: "W zeszłym roku", - uz: "O'tgan yil", - vi: "Năm ngoái", - se: "Förra året", - }, - "wakatimecard.last7days": { - ar: "آخر 7 أيام", - az: "Son 7 gün", - cn: "最近 7 天", - "zh-tw": "最近 7 天", - cs: "Posledních 7 dní", - de: "Letzte 7 Tage", - en: "last 7 days", - bn: "গত ৭ দিন", - es: "Últimos 7 días", - fi: "Viimeiset 7 päivää", - fr: "7 derniers jours", - hu: "Elmúlt 7 nap", - it: "Ultimi 7 giorni", - ja: "過去 7 日間", - kr: "지난 7 일", - nl: "Afgelopen 7 dagen", - "pt-pt": "Últimos 7 dias", - "pt-br": "Últimos 7 dias", - np: "गत ७ दिन", - el: "Τελευταίες 7 ημέρες", - ro: "Ultimele 7 zile", - ru: "Последние 7 дней", - "uk-ua": "Останні 7 днів", - id: "7 hari terakhir", - ml: "കഴിഞ്ഞ 7 ദിവസം", - my: "7 hari lepas", - sk: "Posledných 7 dní", - tr: "Son 7 gün", - pl: "Ostatnie 7 dni", - uz: "O'tgan 7 kun", - vi: "7 ngày qua", - se: "Senaste 7 dagarna", - }, - "wakatimecard.notpublic": { - ar: "ملف مستخدم واكا تايم شخصي", - az: "WakaTime istifadəçi profili ictimai deyil", - cn: "WakaTime 用户个人资料未公开", - "zh-tw": "WakaTime 使用者個人資料未公開", - cs: "Profil uživatele WakaTime není veřejný", - de: "WakaTime-Benutzerprofil nicht öffentlich", - en: "WakaTime user profile not public", - bn: "WakaTime ব্যবহারকারীর প্রোফাইল প্রকাশ্য নয়", - es: "Perfil de usuario de WakaTime no público", - fi: "WakaTime-käyttäjäprofiili ei ole julkinen", - fr: "Profil utilisateur WakaTime non public", - hu: "A WakaTime felhasználói profilja nem nyilvános", - it: "Profilo utente WakaTime non pubblico", - ja: "WakaTime ユーザープロファイルは公開されていません", - kr: "WakaTime 사용자 프로필이 공개되지 않았습니다", - nl: "WakaTime gebruikersprofiel niet openbaar", - "pt-pt": "Perfil de utilizador WakaTime não público", - "pt-br": "Perfil de usuário WakaTime não público", - np: "WakaTime प्रयोगकर्ता प्रोफाइल सार्वजनिक छैन", - el: "Το προφίλ χρήστη WakaTime δεν είναι δημόσιο", - ro: "Profilul utilizatorului de Wakatime nu este public", - ru: "Профиль пользователя WakaTime не общедоступный", - "uk-ua": "Профіль користувача WakaTime не є публічним", - id: "Profil pengguna WakaTime tidak publik", - ml: "WakaTime ഉപയോക്തൃ പ്രൊഫൈൽ പൊതുവായി പ്രസിദ്ധീകരിക്കപ്പെടാത്തതാണ്", - my: "Profil pengguna WakaTime tidak awam", - sk: "Profil používateľa WakaTime nie je verejný", - tr: "WakaTime kullanıcı profili herkese açık değil", - pl: "Profil użytkownika WakaTime nie jest publiczny", - uz: "WakaTime foydalanuvchi profili ochiq emas", - vi: "Hồ sơ người dùng WakaTime không công khai", - se: "WakaTime användarprofil inte offentlig", - }, - "wakatimecard.nocodedetails": { - ar: "المستخدم لا يشارك المعلومات التفصيلية", - az: "İstifadəçi kod statistikalarını ictimai şəkildə paylaşmır", - cn: "用户不公开分享详细的代码统计信息", - "zh-tw": "使用者不公開分享詳細的程式碼統計資訊", - cs: "Uživatel nesdílí podrobné statistiky kódu", - de: "Benutzer teilt keine detaillierten Code-Statistiken", - en: "User doesn't publicly share detailed code statistics", - bn: "ব্যবহারকারী বিস্তারিত কোড পরিসংখ্যান প্রকাশ করেন না", - es: "El usuario no comparte públicamente estadísticas detalladas de código", - fi: "Käyttäjä ei jaa julkisesti tarkkoja kooditilastoja", - fr: "L'utilisateur ne partage pas publiquement de statistiques de code détaillées", - hu: "A felhasználó nem osztja meg nyilvánosan a részletes kódstatisztikákat", - it: "L'utente non condivide pubblicamente statistiche dettagliate sul codice", - ja: "ユーザーは詳細なコード統計を公開しません", - kr: "사용자는 자세한 코드 통계를 공개하지 않습니다", - nl: "Gebruiker deelt geen gedetailleerde code-statistieken", - "pt-pt": - "O utilizador não partilha publicamente estatísticas detalhadas de código", - "pt-br": - "O usuário não compartilha publicamente estatísticas detalhadas de código", - np: "प्रयोगकर्ता सार्वजनिक रूपमा विस्तृत कोड तथ्याङ्क साझा गर्दैन", - el: "Ο χρήστης δεν δημοσιεύει δημόσια λεπτομερείς στατιστικές κώδικα", - ro: "Utilizatorul nu își publică statisticile detaliate ale codului", - ru: "Пользователь не делится подробной статистикой кода", - "uk-ua": "Користувач не публікує детальну статистику коду", - id: "Pengguna tidak membagikan statistik kode terperinci secara publik", - ml: "ഉപയോക്താവ് പൊതുവെ വിശദീകരിച്ച കോഡ് സ്റ്റാറ്റിസ്റ്റിക്സ് പങ്കിടുന്നില്ല", - my: "Pengguna tidak berkongsi statistik kod terperinci secara awam", - sk: "Používateľ neposkytuje verejne podrobné štatistiky kódu", - tr: "Kullanıcı ayrıntılı kod istatistiklerini herkese açık olarak paylaşmıyor", - pl: "Użytkownik nie udostępnia publicznie szczegółowych statystyk kodu", - uz: "Foydalanuvchi umumiy ko`d statistikasini ochiq ravishda almashmaydi", - vi: "Người dùng không chia sẻ thống kê mã chi tiết công khai", - se: "Användaren delar inte offentligt detaljerad kodstatistik", - }, - "wakatimecard.nocodingactivity": { - ar: "لا يوجد نشاط برمجي لهذا الأسبوع", - az: "Bu həftə heç bir kodlaşdırma fəaliyyəti olmayıb", - cn: "本周没有编程活动", - "zh-tw": "本周沒有編程活動", - cs: "Tento týden žádná aktivita v kódování", - de: "Keine Aktivitäten in dieser Woche", - en: "No coding activity this week", - bn: "এই সপ্তাহে কোন কোডিং অ্যাক্টিভিটি নেই", - es: "No hay actividad de codificación esta semana", - fi: "Ei koodaustoimintaa tällä viikolla", - fr: "Aucune activité de codage cette semaine", - hu: "Nem volt aktivitás ezen a héten", - it: "Nessuna attività in questa settimana", - ja: "今週のコーディング活動はありません", - kr: "이번 주 작업내역 없음", - nl: "Geen programmeeractiviteit deze week", - "pt-pt": "Sem atividade esta semana", - "pt-br": "Nenhuma atividade de codificação esta semana", - np: "यस हप्ता कुनै कोडिंग गतिविधि छैन", - el: "Δεν υπάρχει δραστηριότητα κώδικα γι' αυτή την εβδομάδα", - ro: "Nicio activitate de programare săptămâna aceasta", - ru: "На этой неделе не было активности", - "uk-ua": "На цьому тижні не було активності", - id: "Tidak ada aktivitas perkodingan minggu ini", - ml: "ഈ ആഴ്ച കോഡിംഗ് പ്രവർത്തനങ്ങളൊന്നുമില്ല", - my: "Tiada aktiviti pengekodan minggu ini", - sk: "Žiadna kódovacia aktivita tento týždeň", - tr: "Bu hafta herhangi bir kod yazma aktivitesi olmadı", - pl: "Brak aktywności w tym tygodniu", - uz: "Bu hafta faol bo'lmadi", - vi: "Không Có Hoạt Động Trong Tuần Này", - se: "Ingen aktivitet denna vecka", - }, -}; - -const availableLocales = Object.keys(repoCardLocales["repocard.archived"]); - -/** - * Checks whether the locale is available or not. - * - * @param {string} locale The locale to check. - * @returns {boolean} Boolean specifying whether the locale is available or not. - */ -const isLocaleAvailable = (locale) => { - return availableLocales.includes(locale.toLowerCase()); -}; - -export { - availableLocales, - isLocaleAvailable, - langCardLocales, - repoCardLocales, - statCardLocales, - wakatimeCardLocales, -}; diff --git a/frontend/frontend/src/backend/src/users.js b/frontend/frontend/src/backend/src/users.js deleted file mode 100644 index a71c302f..00000000 --- a/frontend/frontend/src/backend/src/users.js +++ /dev/null @@ -1,93 +0,0 @@ -import axios from "axios"; -import { storeUser } from "./common/database.js"; - -/** - * Given an access token, return the GitHub login (userId) or null if invalid - * - * @param {string} accessToken GitHub access token - * @returns {Promise} login name or null if invalid access_token - */ -async function getUserFromToken(accessToken) { - const res = await axios.get("https://api.github.com/user", { - headers: { - Accept: "application/vnd.github.v3+json", - Authorization: `bearer ${accessToken}`, - }, - }); - - return res.data && res.data.login ? res.data.login : null; -} - -/** - * Exchanges OAuth code for access token and returns userId + accessToken - * - * @param {string} code GitHub authentication code from OAuth process - * @returns {Promise<{userId: string, accessToken: string}>} user_id and access_token of authenticated user - */ -async function githubAuthenticate(code) { - if ( - !process.env.OAUTH_CLIENT_ID || - !process.env.OAUTH_CLIENT_SECRET || - !process.env.OAUTH_REDIRECT_URI - ) { - throw new Error( - "OAuth Error: One or more required environment variables (OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_REDIRECT_URI) are not set.", - ); - } - - const start = Date.now(); - const params = new URLSearchParams({ - client_id: process.env.OAUTH_CLIENT_ID, - client_secret: process.env.OAUTH_CLIENT_SECRET, - code, - redirect_uri: process.env.OAUTH_REDIRECT_URI, - }); - - try { - const res = await axios.post( - "https://github.com/login/oauth/access_token", - params.toString(), - { - headers: { - "Content-Type": "application/x-www-form-urlencoded", - Accept: "application/json", - }, - }, - ); - - const body = res.data; - const accessToken = body && body.access_token ? body.access_token : null; - - if (!accessToken) { - throw new Error("OAuth Error: access_token missing from response"); - } - - const userId = await getUserFromToken(accessToken); - - if (!userId) { - throw new Error("OAuth Error: Invalid user_id/access_token"); - } - - console.log("GitHub Authentication", `${Date.now() - start} ms`); - return { userId, accessToken }; - } catch (err) { - if (err.response) { - throw new Error(`OAuth Error: ${err.response.status}`); - } - throw err; - } -} - -/** - * Authenticate using the OAuth code and update DB with associated user info. - * - * @param {string} code GitHub authentication code from OAuth process - * @param {boolean} privateAccess whether private access was requested - * @param {string} userKey user key to associate with the user - * @returns {Promise} user_id of authenticated user - */ -export async function authenticate(code, privateAccess, userKey) { - const { userId, accessToken } = await githubAuthenticate(code); - await storeUser(userId, accessToken, userKey, privateAccess); - return userId; -} diff --git a/frontend/frontend/src/backend/themes/README.md b/frontend/frontend/src/backend/themes/README.md deleted file mode 100644 index c8dffda2..00000000 --- a/frontend/frontend/src/backend/themes/README.md +++ /dev/null @@ -1,229 +0,0 @@ -## Available Themes - - - -With inbuilt themes, you can customize the look of the card without doing any manual customization. - -Use `?theme=THEME_NAME` parameter like so: - -```md -![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&theme=dark&show_icons=true) -``` - -## Stats - -> These themes work with all five of our cards: Stats Card, Repo Card, Gist Card, Top Languages Card, and WakaTime Card. - -| | | | -| :--: | :--: | :--: | -| `default` ![default][default] | `transparent` ![transparent][transparent] | `shadow_red` ![shadow_red][shadow_red] | -| `shadow_green` ![shadow_green][shadow_green] | `shadow_blue` ![shadow_blue][shadow_blue] | `dark` ![dark][dark] | -| `radical` ![radical][radical] | `merko` ![merko][merko] | `gruvbox` ![gruvbox][gruvbox] | -| `gruvbox_light` ![gruvbox_light][gruvbox_light] | `tokyonight` ![tokyonight][tokyonight] | `onedark` ![onedark][onedark] | -| `cobalt` ![cobalt][cobalt] | `synthwave` ![synthwave][synthwave] | `highcontrast` ![highcontrast][highcontrast] | -| `dracula` ![dracula][dracula] | `prussian` ![prussian][prussian] | `monokai` ![monokai][monokai] | -| `vue` ![vue][vue] | `vue-dark` ![vue-dark][vue-dark] | `shades-of-purple` ![shades-of-purple][shades-of-purple] | -| `nightowl` ![nightowl][nightowl] | `buefy` ![buefy][buefy] | `blue-green` ![blue-green][blue-green] | -| `algolia` ![algolia][algolia] | `great-gatsby` ![great-gatsby][great-gatsby] | `darcula` ![darcula][darcula] | -| `bear` ![bear][bear] | `solarized-dark` ![solarized-dark][solarized-dark] | `solarized-light` ![solarized-light][solarized-light] | -| `chartreuse-dark` ![chartreuse-dark][chartreuse-dark] | `nord` ![nord][nord] | `gotham` ![gotham][gotham] | -| `material-palenight` ![material-palenight][material-palenight] | `graywhite` ![graywhite][graywhite] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark] | -| `ayu-mirage` ![ayu-mirage][ayu-mirage] | `midnight-purple` ![midnight-purple][midnight-purple] | `calm` ![calm][calm] | -| `flag-india` ![flag-india][flag-india] | `omni` ![omni][omni] | `react` ![react][react] | -| `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] | -| `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] | -| `outrun` ![outrun][outrun] | `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] | -| `github_dark` ![github_dark][github_dark] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | -| `aura_dark` ![aura_dark][aura_dark] | `panda` ![panda][panda] | `noctis_minimus` ![noctis_minimus][noctis_minimus] | -| `cobalt2` ![cobalt2][cobalt2] | `swift` ![swift][swift] | `aura` ![aura][aura] | -| `apprentice` ![apprentice][apprentice] | `moltack` ![moltack][moltack] | `codeSTACKr` ![codeSTACKr][codeSTACKr] | -| `rose_pine` ![rose_pine][rose_pine] | `catppuccin_latte` ![catppuccin_latte][catppuccin_latte] | `catppuccin_mocha` ![catppuccin_mocha][catppuccin_mocha] | -| `date_night` ![date_night][date_night] | `one_dark_pro` ![one_dark_pro][one_dark_pro] | `rose` ![rose][rose] | -| `holi` ![holi][holi] | `neon` ![neon][neon] | `blue_navy` ![blue_navy][blue_navy] | -| `calm_pink` ![calm_pink][calm_pink] | `ambient_gradient` ![ambient_gradient][ambient_gradient] | | - -## Repo Card - -> These themes work with all five of our cards: Stats Card, Repo Card, Gist Card, Top Languages Card, and WakaTime Card. - -| | | | -| :--: | :--: | :--: | -| `default_repocard` ![default_repocard][default_repocard_repo] | `transparent` ![transparent][transparent_repo] | `shadow_red` ![shadow_red][shadow_red_repo] | -| `shadow_green` ![shadow_green][shadow_green_repo] | `shadow_blue` ![shadow_blue][shadow_blue_repo] | `dark` ![dark][dark_repo] | -| `radical` ![radical][radical_repo] | `merko` ![merko][merko_repo] | `gruvbox` ![gruvbox][gruvbox_repo] | -| `gruvbox_light` ![gruvbox_light][gruvbox_light_repo] | `tokyonight` ![tokyonight][tokyonight_repo] | `onedark` ![onedark][onedark_repo] | -| `cobalt` ![cobalt][cobalt_repo] | `synthwave` ![synthwave][synthwave_repo] | `highcontrast` ![highcontrast][highcontrast_repo] | -| `dracula` ![dracula][dracula_repo] | `prussian` ![prussian][prussian_repo] | `monokai` ![monokai][monokai_repo] | -| `vue` ![vue][vue_repo] | `vue-dark` ![vue-dark][vue-dark_repo] | `shades-of-purple` ![shades-of-purple][shades-of-purple_repo] | -| `nightowl` ![nightowl][nightowl_repo] | `buefy` ![buefy][buefy_repo] | `blue-green` ![blue-green][blue-green_repo] | -| `algolia` ![algolia][algolia_repo] | `great-gatsby` ![great-gatsby][great-gatsby_repo] | `darcula` ![darcula][darcula_repo] | -| `bear` ![bear][bear_repo] | `solarized-dark` ![solarized-dark][solarized-dark_repo] | `solarized-light` ![solarized-light][solarized-light_repo] | -| `chartreuse-dark` ![chartreuse-dark][chartreuse-dark_repo] | `nord` ![nord][nord_repo] | `gotham` ![gotham][gotham_repo] | -| `material-palenight` ![material-palenight][material-palenight_repo] | `graywhite` ![graywhite][graywhite_repo] | `vision-friendly-dark` ![vision-friendly-dark][vision-friendly-dark_repo] | -| `ayu-mirage` ![ayu-mirage][ayu-mirage_repo] | `midnight-purple` ![midnight-purple][midnight-purple_repo] | `calm` ![calm][calm_repo] | -| `flag-india` ![flag-india][flag-india_repo] | `omni` ![omni][omni_repo] | `react` ![react][react_repo] | -| `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] | -| `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] | -| `outrun` ![outrun][outrun_repo] | `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] | -| `github_dark` ![github_dark][github_dark_repo] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | -| `aura_dark` ![aura_dark][aura_dark_repo] | `panda` ![panda][panda_repo] | `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | -| `cobalt2` ![cobalt2][cobalt2_repo] | `swift` ![swift][swift_repo] | `aura` ![aura][aura_repo] | -| `apprentice` ![apprentice][apprentice_repo] | `moltack` ![moltack][moltack_repo] | `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | -| `rose_pine` ![rose_pine][rose_pine_repo] | `catppuccin_latte` ![catppuccin_latte][catppuccin_latte_repo] | `catppuccin_mocha` ![catppuccin_mocha][catppuccin_mocha_repo] | -| `date_night` ![date_night][date_night_repo] | `one_dark_pro` ![one_dark_pro][one_dark_pro_repo] | `rose` ![rose][rose_repo] | -| `holi` ![holi][holi_repo] | `neon` ![neon][neon_repo] | `blue_navy` ![blue_navy][blue_navy_repo] | -| `calm_pink` ![calm_pink][calm_pink_repo] | `ambient_gradient` ![ambient_gradient][ambient_gradient_repo] | | - - -[default]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default -[default_repocard]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default_repocard -[transparent]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=transparent -[shadow_red]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=shadow_red -[shadow_green]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=shadow_green -[shadow_blue]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=shadow_blue -[dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=dark -[radical]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=radical -[merko]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=merko -[gruvbox]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=gruvbox -[gruvbox_light]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=gruvbox_light -[tokyonight]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=tokyonight -[onedark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=onedark -[cobalt]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt -[synthwave]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=synthwave -[highcontrast]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=highcontrast -[dracula]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=dracula -[prussian]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=prussian -[monokai]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=monokai -[vue]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=vue -[vue-dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=vue-dark -[shades-of-purple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=shades-of-purple -[nightowl]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=nightowl -[buefy]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=buefy -[blue-green]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=blue-green -[algolia]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=algolia -[great-gatsby]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=great-gatsby -[darcula]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=darcula -[bear]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=bear -[solarized-dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=solarized-dark -[solarized-light]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=solarized-light -[chartreuse-dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=chartreuse-dark -[nord]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=nord -[gotham]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=gotham -[material-palenight]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=material-palenight -[graywhite]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=graywhite -[vision-friendly-dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=vision-friendly-dark -[ayu-mirage]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ayu-mirage -[midnight-purple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=midnight-purple -[calm]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=calm -[flag-india]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=flag-india -[omni]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=omni -[react]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=react -[jolly]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=jolly -[maroongold]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=maroongold -[yeblu]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=yeblu -[blueberry]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=blueberry -[slateorange]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=slateorange -[kacho_ga]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=kacho_ga -[outrun]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=outrun -[ocean_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ocean_dark -[city_lights]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=city_lights -[github_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark -[github_dark_dimmed]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark_dimmed -[discord_old_blurple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=discord_old_blurple -[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark -[panda]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=panda -[noctis_minimus]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=noctis_minimus -[cobalt2]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt2 -[swift]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=swift -[aura]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura -[apprentice]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=apprentice -[moltack]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=moltack -[codeSTACKr]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=codeSTACKr -[rose_pine]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=rose_pine -[catppuccin_latte]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=catppuccin_latte -[catppuccin_mocha]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=catppuccin_mocha -[date_night]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=date_night -[one_dark_pro]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=one_dark_pro -[rose]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=rose -[holi]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=holi -[neon]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=neon -[blue_navy]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=blue_navy -[calm_pink]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=calm_pink -[ambient_gradient]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ambient_gradient - - -[default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default -[default_repocard_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default_repocard -[transparent_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=transparent -[shadow_red_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=shadow_red -[shadow_green_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=shadow_green -[shadow_blue_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=shadow_blue -[dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=dark -[radical_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=radical -[merko_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=merko -[gruvbox_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=gruvbox -[gruvbox_light_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=gruvbox_light -[tokyonight_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=tokyonight -[onedark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=onedark -[cobalt_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt -[synthwave_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=synthwave -[highcontrast_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=highcontrast -[dracula_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=dracula -[prussian_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=prussian -[monokai_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=monokai -[vue_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=vue -[vue-dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=vue-dark -[shades-of-purple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=shades-of-purple -[nightowl_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=nightowl -[buefy_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=buefy -[blue-green_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=blue-green -[algolia_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=algolia -[great-gatsby_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=great-gatsby -[darcula_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=darcula -[bear_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=bear -[solarized-dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=solarized-dark -[solarized-light_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=solarized-light -[chartreuse-dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=chartreuse-dark -[nord_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=nord -[gotham_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=gotham -[material-palenight_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=material-palenight -[graywhite_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=graywhite -[vision-friendly-dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=vision-friendly-dark -[ayu-mirage_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ayu-mirage -[midnight-purple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=midnight-purple -[calm_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=calm -[flag-india_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=flag-india -[omni_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=omni -[react_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=react -[jolly_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=jolly -[maroongold_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=maroongold -[yeblu_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=yeblu -[blueberry_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=blueberry -[slateorange_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=slateorange -[kacho_ga_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=kacho_ga -[outrun_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=outrun -[ocean_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ocean_dark -[city_lights_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=city_lights -[github_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark -[github_dark_dimmed_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark_dimmed -[discord_old_blurple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=discord_old_blurple -[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark -[panda_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=panda -[noctis_minimus_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=noctis_minimus -[cobalt2_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt2 -[swift_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=swift -[aura_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura -[apprentice_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=apprentice -[moltack_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=moltack -[codeSTACKr_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=codeSTACKr -[rose_pine_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=rose_pine -[catppuccin_latte_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=catppuccin_latte -[catppuccin_mocha_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=catppuccin_mocha -[date_night_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=date_night -[one_dark_pro_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=one_dark_pro -[rose_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=rose -[holi_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=holi -[neon_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=neon -[blue_navy_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=blue_navy -[calm_pink_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=calm_pink -[ambient_gradient_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ambient_gradient diff --git a/frontend/frontend/src/backend/themes/index.js b/frontend/frontend/src/backend/themes/index.js deleted file mode 100644 index f5d8d916..00000000 --- a/frontend/frontend/src/backend/themes/index.js +++ /dev/null @@ -1,467 +0,0 @@ -export const themes = { - default: { - title_color: "2f80ed", - icon_color: "4c71f2", - text_color: "434d58", - bg_color: "fffefe", - border_color: "e4e2e2", - }, - default_repocard: { - title_color: "2f80ed", - icon_color: "586069", // icon color is different - text_color: "434d58", - bg_color: "fffefe", - }, - transparent: { - title_color: "006AFF", - icon_color: "0579C3", - text_color: "417E87", - bg_color: "ffffff00", - }, - shadow_red: { - title_color: "9A0000", - text_color: "444", - icon_color: "4F0000", - border_color: "4F0000", - bg_color: "ffffff00", - }, - shadow_green: { - title_color: "007A00", - text_color: "444", - icon_color: "003D00", - border_color: "003D00", - bg_color: "ffffff00", - }, - shadow_blue: { - title_color: "00779A", - text_color: "444", - icon_color: "004450", - border_color: "004490", - bg_color: "ffffff00", - }, - dark: { - title_color: "fff", - icon_color: "79ff97", - text_color: "9f9f9f", - bg_color: "151515", - }, - radical: { - title_color: "fe428e", - icon_color: "f8d847", - text_color: "a9fef7", - bg_color: "141321", - }, - merko: { - title_color: "abd200", - icon_color: "b7d364", - text_color: "68b587", - bg_color: "0a0f0b", - }, - gruvbox: { - title_color: "fabd2f", - icon_color: "fe8019", - text_color: "8ec07c", - bg_color: "282828", - }, - gruvbox_light: { - title_color: "b57614", - icon_color: "af3a03", - text_color: "427b58", - bg_color: "fbf1c7", - }, - tokyonight: { - title_color: "70a5fd", - icon_color: "bf91f3", - text_color: "38bdae", - bg_color: "1a1b27", - }, - onedark: { - title_color: "e4bf7a", - icon_color: "8eb573", - text_color: "df6d74", - bg_color: "282c34", - }, - cobalt: { - title_color: "e683d9", - icon_color: "0480ef", - text_color: "75eeb2", - bg_color: "193549", - }, - synthwave: { - title_color: "e2e9ec", - icon_color: "ef8539", - text_color: "e5289e", - bg_color: "2b213a", - }, - highcontrast: { - title_color: "e7f216", - icon_color: "00ffff", - text_color: "fff", - bg_color: "000", - }, - dracula: { - title_color: "ff6e96", - icon_color: "79dafa", - text_color: "f8f8f2", - bg_color: "282a36", - }, - prussian: { - title_color: "bddfff", - icon_color: "38a0ff", - text_color: "6e93b5", - bg_color: "172f45", - }, - monokai: { - title_color: "eb1f6a", - icon_color: "e28905", - text_color: "f1f1eb", - bg_color: "272822", - }, - vue: { - title_color: "41b883", - icon_color: "41b883", - text_color: "273849", - bg_color: "fffefe", - }, - "vue-dark": { - title_color: "41b883", - icon_color: "41b883", - text_color: "fffefe", - bg_color: "273849", - }, - "shades-of-purple": { - title_color: "fad000", - icon_color: "b362ff", - text_color: "a599e9", - bg_color: "2d2b55", - }, - nightowl: { - title_color: "c792ea", - icon_color: "ffeb95", - text_color: "7fdbca", - bg_color: "011627", - }, - buefy: { - title_color: "7957d5", - icon_color: "ff3860", - text_color: "363636", - bg_color: "ffffff", - }, - "blue-green": { - title_color: "2f97c1", - icon_color: "f5b700", - text_color: "0cf574", - bg_color: "040f0f", - }, - algolia: { - title_color: "00AEFF", - icon_color: "2DDE98", - text_color: "FFFFFF", - bg_color: "050F2C", - }, - "great-gatsby": { - title_color: "ffa726", - icon_color: "ffb74d", - text_color: "ffd95b", - bg_color: "000000", - }, - darcula: { - title_color: "BA5F17", - icon_color: "84628F", - text_color: "BEBEBE", - bg_color: "242424", - }, - bear: { - title_color: "e03c8a", - icon_color: "00AEFF", - text_color: "bcb28d", - bg_color: "1f2023", - }, - "solarized-dark": { - title_color: "268bd2", - icon_color: "b58900", - text_color: "859900", - bg_color: "002b36", - }, - "solarized-light": { - title_color: "268bd2", - icon_color: "b58900", - text_color: "859900", - bg_color: "fdf6e3", - }, - "chartreuse-dark": { - title_color: "7fff00", - icon_color: "00AEFF", - text_color: "fff", - bg_color: "000", - }, - nord: { - title_color: "81a1c1", - text_color: "d8dee9", - icon_color: "88c0d0", - bg_color: "2e3440", - }, - gotham: { - title_color: "2aa889", - icon_color: "599cab", - text_color: "99d1ce", - bg_color: "0c1014", - }, - "material-palenight": { - title_color: "c792ea", - icon_color: "89ddff", - text_color: "a6accd", - bg_color: "292d3e", - }, - graywhite: { - title_color: "24292e", - icon_color: "24292e", - text_color: "24292e", - bg_color: "ffffff", - }, - "vision-friendly-dark": { - title_color: "ffb000", - icon_color: "785ef0", - text_color: "ffffff", - bg_color: "000000", - }, - "ayu-mirage": { - title_color: "f4cd7c", - icon_color: "73d0ff", - text_color: "c7c8c2", - bg_color: "1f2430", - }, - "midnight-purple": { - title_color: "9745f5", - icon_color: "9f4bff", - text_color: "ffffff", - bg_color: "000000", - }, - calm: { - title_color: "e07a5f", - icon_color: "edae49", - text_color: "ebcfb2", - bg_color: "373f51", - }, - "flag-india": { - title_color: "ff8f1c", - icon_color: "250E62", - text_color: "509E2F", - bg_color: "ffffff", - }, - omni: { - title_color: "FF79C6", - icon_color: "e7de79", - text_color: "E1E1E6", - bg_color: "191622", - }, - react: { - title_color: "61dafb", - icon_color: "61dafb", - text_color: "ffffff", - bg_color: "20232a", - }, - jolly: { - title_color: "ff64da", - icon_color: "a960ff", - text_color: "ffffff", - bg_color: "291B3E", - }, - maroongold: { - title_color: "F7EF8A", - icon_color: "F7EF8A", - text_color: "E0AA3E", - bg_color: "260000", - }, - yeblu: { - title_color: "ffff00", - icon_color: "ffff00", - text_color: "ffffff", - bg_color: "002046", - }, - blueberry: { - title_color: "82aaff", - icon_color: "89ddff", - text_color: "27e8a7", - bg_color: "242938", - }, - slateorange: { - title_color: "faa627", - icon_color: "faa627", - text_color: "ffffff", - bg_color: "36393f", - }, - kacho_ga: { - title_color: "bf4a3f", - icon_color: "a64833", - text_color: "d9c8a9", - bg_color: "402b23", - }, - outrun: { - title_color: "ffcc00", - icon_color: "ff1aff", - text_color: "8080ff", - bg_color: "141439", - }, - ocean_dark: { - title_color: "8957B2", - icon_color: "FFFFFF", - text_color: "92D534", - bg_color: "151A28", - }, - city_lights: { - title_color: "5D8CB3", - icon_color: "4798FF", - text_color: "718CA1", - bg_color: "1D252C", - }, - github_dark: { - title_color: "58A6FF", - icon_color: "1F6FEB", - text_color: "C3D1D9", - bg_color: "0D1117", - }, - github_dark_dimmed: { - title_color: "539bf5", - icon_color: "539bf5", - text_color: "ADBAC7", - bg_color: "24292F", - border_color: "373E47", - }, - discord_old_blurple: { - title_color: "7289DA", - icon_color: "7289DA", - text_color: "FFFFFF", - bg_color: "2C2F33", - }, - aura_dark: { - title_color: "ff7372", - icon_color: "6cffd0", - text_color: "dbdbdb", - bg_color: "252334", - }, - panda: { - title_color: "19f9d899", - icon_color: "19f9d899", - text_color: "FF75B5", - bg_color: "31353a", - }, - noctis_minimus: { - title_color: "d3b692", - icon_color: "72b7c0", - text_color: "c5cdd3", - bg_color: "1b2932", - }, - cobalt2: { - title_color: "ffc600", - icon_color: "ffffff", - text_color: "0088ff", - bg_color: "193549", - }, - swift: { - title_color: "000000", - icon_color: "f05237", - text_color: "000000", - bg_color: "f7f7f7", - }, - aura: { - title_color: "a277ff", - icon_color: "ffca85", - text_color: "61ffca", - bg_color: "15141b", - }, - apprentice: { - title_color: "ffffff", - icon_color: "ffffaf", - text_color: "bcbcbc", - bg_color: "262626", - }, - moltack: { - title_color: "86092C", - icon_color: "86092C", - text_color: "574038", - bg_color: "F5E1C0", - }, - codeSTACKr: { - title_color: "ff652f", - icon_color: "FFE400", - text_color: "ffffff", - bg_color: "09131B", - border_color: "0c1a25", - }, - rose_pine: { - title_color: "9ccfd8", - icon_color: "ebbcba", - text_color: "e0def4", - bg_color: "191724", - }, - catppuccin_latte: { - title_color: "137980", - icon_color: "8839ef", - text_color: "4c4f69", - bg_color: "eff1f5", - }, - catppuccin_mocha: { - title_color: "94e2d5", - icon_color: "cba6f7", - text_color: "cdd6f4", - bg_color: "1e1e2e", - }, - date_night: { - title_color: "DA7885", - text_color: "E1B2A2", - icon_color: "BB8470", - border_color: "170F0C", - bg_color: "170F0C", - }, - one_dark_pro: { - title_color: "61AFEF", - text_color: "E5C06E", - icon_color: "C678DD", - border_color: "3B4048", - bg_color: "23272E", - }, - rose: { - title_color: "8d192b", - text_color: "862931", - icon_color: "B71F36", - border_color: "e9d8d4", - bg_color: "e9d8d4", - }, - holi: { - title_color: "5FABEE", - text_color: "D6E7FF", - icon_color: "5FABEE", - border_color: "85A4C0", - bg_color: "030314", - }, - neon: { - title_color: "00EAD3", - text_color: "FF449F", - icon_color: "00EAD3", - border_color: "ffffff", - bg_color: "000000", - }, - blue_navy: { - title_color: "82AAFF", - text_color: "82AAFF", - icon_color: "82AAFF", - border_color: "ffffff", - bg_color: "000000", - }, - calm_pink: { - title_color: "e07a5f", - text_color: "edae49", - icon_color: "ebcfb2", - border_color: "e1bc29", - bg_color: "2b2d40", - }, - ambient_gradient: { - title_color: "ffffff", - text_color: "ffffff", - icon_color: "ffffff", - bg_color: "35,4158d0,c850c0,ffcc70", - }, -}; - -export default themes; diff --git a/frontend/frontend/src/pages/Home/Home.js b/frontend/frontend/src/pages/Home/Home.js index fb917522..de8d0ab9 100644 --- a/frontend/frontend/src/pages/Home/Home.js +++ b/frontend/frontend/src/pages/Home/Home.js @@ -1,4 +1,6 @@ import axios from 'axios'; +import { default as router } from "../../backend/.vercel/output/functions/api.func/router"; + import React, { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import BounceLoader from 'react-spinners/BounceLoader'; @@ -130,6 +132,8 @@ const HomeScreen = () => { ); login(newUserId, userKey); try { + console.log(router); +/* const restResult = await axios.get( 'https://api.github.com/search/commits?q=author:martin-mfg', ); @@ -147,6 +151,7 @@ const HomeScreen = () => { }, ); console.log(graphqlResult); +*/ } catch (e) { console.error(e); } diff --git a/vercel-preparation.sh b/vercel-preparation.sh new file mode 100644 index 00000000..da4c3794 --- /dev/null +++ b/vercel-preparation.sh @@ -0,0 +1,7 @@ +# move into the folder of this script +cd "$(dirname "$0")" || exit 1 + +mkdir --parents backend/.vercel/output/functions/api.func/ +cp --recursive backend/* backend/.vercel/output/functions/api.func/ +cp --recursive backend/.vercel/output/functions/api.func/_dot_vercel_copy/output/ backend/.vercel/ +cp --recursive backend frontend/frontend/src/backend/