mirror of
https://github.com/Aviortheking/codestats-readme.git
synced 2025-04-22 10:42:08 +00:00
Added Profile
Signed-off-by: Florian Bouillon <florian.bouillon@delta-wings.net>
This commit is contained in:
parent
f79e691059
commit
4702a72bff
74
api/index.ts
Normal file
74
api/index.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { renderError, parseBoolean, parseArray, CONSTANTS} from '../src/common/utils'
|
||||||
|
import { fetchProfile } from '../src/fetcher'
|
||||||
|
import renderStatsCard from '../src/cards/profileCard'
|
||||||
|
import blacklist from '../src/common/blacklist'
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
import ReactDOMServer from 'react-dom/server'
|
||||||
|
import themes from '../themes';
|
||||||
|
|
||||||
|
export interface query {
|
||||||
|
username: string
|
||||||
|
hide?: string
|
||||||
|
hide_title?: string
|
||||||
|
hide_border?: string
|
||||||
|
hide_rank?: string
|
||||||
|
show_icons?: string
|
||||||
|
line_height?: string
|
||||||
|
title_color?: string
|
||||||
|
icon_color?: string
|
||||||
|
text_color?: string
|
||||||
|
bg_color?: string
|
||||||
|
theme?: keyof typeof themes
|
||||||
|
cache_seconds?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async (req: Request<unknown, unknown, unknown, query>, res: Response) => {
|
||||||
|
const {
|
||||||
|
username,
|
||||||
|
hide,
|
||||||
|
hide_title,
|
||||||
|
hide_border,
|
||||||
|
hide_rank,
|
||||||
|
show_icons,
|
||||||
|
line_height,
|
||||||
|
title_color,
|
||||||
|
icon_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
theme,
|
||||||
|
} = req.query;
|
||||||
|
|
||||||
|
res.setHeader("Content-Type", "image/svg+xml");
|
||||||
|
|
||||||
|
if (blacklist.includes(username)) {
|
||||||
|
return res.send(renderError("Username is in blacklist"));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await fetchProfile(username);
|
||||||
|
|
||||||
|
const cacheSeconds = CONSTANTS.TWO_HOURS
|
||||||
|
|
||||||
|
res.setHeader("Cache-Control", `public, max-age=${cacheSeconds}`);
|
||||||
|
|
||||||
|
return res.send(ReactDOMServer.renderToStaticMarkup(
|
||||||
|
renderStatsCard(data, {
|
||||||
|
hide: parseArray(hide),
|
||||||
|
show_icons: parseBoolean(show_icons),
|
||||||
|
hide_title: parseBoolean(hide_title),
|
||||||
|
hide_border: parseBoolean(hide_border),
|
||||||
|
hide_rank: parseBoolean(hide_rank),
|
||||||
|
line_height: line_height ? parseInt(line_height , 10) : undefined,
|
||||||
|
title_color,
|
||||||
|
icon_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
theme,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return res.send(
|
||||||
|
ReactDOMServer.renderToStaticMarkup(renderError(err.message, err.secondaryMessage))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
import { renderError, clampValue, parseBoolean, parseArray, CONSTANTS} from '../src/common/utils'
|
import { renderError, clampValue, parseBoolean, parseArray, CONSTANTS} from '../src/common/utils'
|
||||||
import fetchTopLanguages from '../src/fetchers/top-languages-fetcher'
|
import { fetchTopLanguages } from '../src/fetcher'
|
||||||
import renderTopLanguages from '../src/cards/top-languages-card'
|
import renderTopLanguages from '../src/cards/top-languages-card'
|
||||||
import blacklist from '../src/common/blacklist'
|
import blacklist from '../src/common/blacklist'
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
|
185
src/cards/profileCard.tsx
Normal file
185
src/cards/profileCard.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { kFormatter, getCardColors, FlexLayout, encodeHTML, getProgress } from '../common/utils'
|
||||||
|
import { getStyles } from '../getStyles'
|
||||||
|
import icons from '../common/icons'
|
||||||
|
import Card from '../common/Card'
|
||||||
|
import React from 'react'
|
||||||
|
import themes from '../../themes'
|
||||||
|
|
||||||
|
const createTextNode = ({
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
index,
|
||||||
|
showIcons
|
||||||
|
}: {
|
||||||
|
icon: JSX.Element,
|
||||||
|
label: string,
|
||||||
|
value: number,
|
||||||
|
index: number,
|
||||||
|
showIcons: boolean
|
||||||
|
}) => {
|
||||||
|
const kValue = kFormatter(value);
|
||||||
|
const staggerDelay = (index + 3) * 150;
|
||||||
|
|
||||||
|
const iconSvg = showIcons
|
||||||
|
? (
|
||||||
|
<svg data-testid="icon" className="icon" viewBox="0 0 16 16" version="1.1" width="16" height="16">
|
||||||
|
{icon}
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
return (
|
||||||
|
<g className="stagger" style={{animationDelay: `${staggerDelay}ms`}} transform="translate(25, 0)">
|
||||||
|
{iconSvg}
|
||||||
|
<text className="stat bold" x={showIcons ? 25 : undefined} y="12.5">{label}:</text>
|
||||||
|
<text
|
||||||
|
className="stat"
|
||||||
|
x={showIcons ? 120 : 100}
|
||||||
|
y="12.5"
|
||||||
|
>{kValue}</text>
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
hide?: Array<string>
|
||||||
|
show_icons?: boolean
|
||||||
|
hide_title?: boolean
|
||||||
|
hide_border?: boolean
|
||||||
|
hide_rank?: boolean
|
||||||
|
line_height?: number
|
||||||
|
title_color?: string
|
||||||
|
icon_color?: string
|
||||||
|
text_color?: string
|
||||||
|
bg_color?: string
|
||||||
|
theme?: keyof typeof themes
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (stats: {username: string, xp: number, recentXp: number, level: number}, options: Options = { hide: [] }) => {
|
||||||
|
const {
|
||||||
|
username,
|
||||||
|
xp,
|
||||||
|
recentXp,
|
||||||
|
level
|
||||||
|
} = stats;
|
||||||
|
const {
|
||||||
|
hide = [],
|
||||||
|
show_icons = false,
|
||||||
|
hide_title = false,
|
||||||
|
hide_border = false,
|
||||||
|
hide_rank = false,
|
||||||
|
line_height = 25,
|
||||||
|
title_color,
|
||||||
|
icon_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
theme = "default",
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const lheight = line_height
|
||||||
|
|
||||||
|
// returns theme based colors with proper overrides and defaults
|
||||||
|
const { titleColor, textColor, iconColor, bgColor } = getCardColors({
|
||||||
|
title_color,
|
||||||
|
icon_color,
|
||||||
|
text_color,
|
||||||
|
bg_color,
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Meta data for creating text nodes with createTextNode function
|
||||||
|
const STATS = {
|
||||||
|
xp: {
|
||||||
|
icon: icons.star,
|
||||||
|
label: "XP",
|
||||||
|
value: xp,
|
||||||
|
id: "xp",
|
||||||
|
},
|
||||||
|
recent_xp: {
|
||||||
|
icon: icons.commits,
|
||||||
|
label: 'Recent xp',
|
||||||
|
value: recentXp,
|
||||||
|
id: "recent_xp",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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({
|
||||||
|
...STATS[key],
|
||||||
|
index,
|
||||||
|
showIcons: show_icons
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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 : 120
|
||||||
|
);
|
||||||
|
|
||||||
|
// Conditionally rendered elements
|
||||||
|
const rankCircle = hide_rank
|
||||||
|
? undefined
|
||||||
|
: (
|
||||||
|
<g data-testid="rank-circle"
|
||||||
|
transform={`translate(400, 0)`}>
|
||||||
|
<circle className="rank-circle-rim" cx="-10" cy="8" r="40" />
|
||||||
|
<circle className="rank-circle" cx="-10" cy="8" r="40" />
|
||||||
|
<g className="rank-text">
|
||||||
|
<text
|
||||||
|
x="-4"
|
||||||
|
y="0"
|
||||||
|
alignmentBaseline="central"
|
||||||
|
dominantBaseline="central"
|
||||||
|
textAnchor="middle"
|
||||||
|
>
|
||||||
|
lv {level}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
)
|
||||||
|
|
||||||
|
const progress = getProgress(xp + recentXp);
|
||||||
|
const cssStyles = getStyles({
|
||||||
|
titleColor,
|
||||||
|
textColor,
|
||||||
|
iconColor,
|
||||||
|
show_icons,
|
||||||
|
progress,
|
||||||
|
});
|
||||||
|
|
||||||
|
const apostrophe = ["x", "s"].includes(username.slice(-1)) ? "" : "s";
|
||||||
|
const card = new Card(
|
||||||
|
495,
|
||||||
|
height,
|
||||||
|
{
|
||||||
|
titleColor,
|
||||||
|
textColor,
|
||||||
|
iconColor,
|
||||||
|
bgColor,
|
||||||
|
},
|
||||||
|
`${encodeHTML(username)}'${apostrophe} Code::stats Profile`
|
||||||
|
)
|
||||||
|
|
||||||
|
card.setHideBorder(hide_border);
|
||||||
|
card.setHideTitle(hide_title);
|
||||||
|
card.setCSS(cssStyles);
|
||||||
|
|
||||||
|
return card.render(
|
||||||
|
<>
|
||||||
|
{rankCircle}
|
||||||
|
<svg x="0" y="0">
|
||||||
|
{FlexLayout({
|
||||||
|
items: statItems,
|
||||||
|
gap: lheight,
|
||||||
|
direction: "column",
|
||||||
|
})}
|
||||||
|
</svg>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import { getCardColors, FlexLayout, clampValue } from "../common/utils"
|
import { getCardColors, FlexLayout, clampValue } from "../common/utils"
|
||||||
import Card from '../common/Card'
|
import Card from '../common/Card'
|
||||||
import { query } from "../../api/top-langs";
|
import { data } from "../fetcher";
|
||||||
import { data } from "../fetchers/top-languages-fetcher";
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import themes from "../../themes";
|
import themes from "../../themes";
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export default class Card {
|
|||||||
constructor(
|
constructor(
|
||||||
public width = 100,
|
public width = 100,
|
||||||
public height = 100,
|
public height = 100,
|
||||||
public colors: {titleColor?: string | Array<string>, textColor?: string | Array<string>, bgColor?: string | Array<string>} = {},
|
public colors: {titleColor?: string | Array<string>, textColor?: string | Array<string>, bgColor?: string | Array<string>, iconColor?: string | Array<string>} = {},
|
||||||
public title = "",
|
public title = "",
|
||||||
public titlePrefixIcon?: string
|
public titlePrefixIcon?: string
|
||||||
) {}
|
) {}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
star: <path fill-rule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/>,
|
star: <path fillRule="evenodd" d="M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z"/>,
|
||||||
commits: <path fill-rule="evenodd" d="M1.643 3.143L.427 1.927A.25.25 0 000 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 00.177-.427L2.715 4.215a6.5 6.5 0 11-1.18 4.458.75.75 0 10-1.493.154 8.001 8.001 0 101.6-5.684zM7.75 4a.75.75 0 01.75.75v2.992l2.028.812a.75.75 0 01-.557 1.392l-2.5-1A.75.75 0 017 8.25v-3.5A.75.75 0 017.75 4z"/>,
|
commits: <path fillRule="evenodd" d="M1.643 3.143L.427 1.927A.25.25 0 000 2.104V5.75c0 .138.112.25.25.25h3.646a.25.25 0 00.177-.427L2.715 4.215a6.5 6.5 0 11-1.18 4.458.75.75 0 10-1.493.154 8.001 8.001 0 101.6-5.684zM7.75 4a.75.75 0 01.75.75v2.992l2.028.812a.75.75 0 01-.557 1.392l-2.5-1A.75.75 0 017 8.25v-3.5A.75.75 0 017.75 4z"/>,
|
||||||
prs: <path fill-rule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/>,
|
prs: <path fillRule="evenodd" d="M7.177 3.073L9.573.677A.25.25 0 0110 .854v4.792a.25.25 0 01-.427.177L7.177 3.427a.25.25 0 010-.354zM3.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122v5.256a2.251 2.251 0 11-1.5 0V5.372A2.25 2.25 0 011.5 3.25zM11 2.5h-1V4h1a1 1 0 011 1v5.628a2.251 2.251 0 101.5 0V5A2.5 2.5 0 0011 2.5zm1 10.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0zM3.75 12a.75.75 0 100 1.5.75.75 0 000-1.5z"/>,
|
||||||
issues: <path fill-rule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"/>,
|
issues: <path fillRule="evenodd" d="M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8zm9 3a1 1 0 11-2 0 1 1 0 012 0zm-.25-6.25a.75.75 0 00-1.5 0v3.5a.75.75 0 001.5 0v-3.5z"/>,
|
||||||
icon: <path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/>,
|
icon: <path fillRule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/>,
|
||||||
contribs: <path fill-rule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/>,
|
contribs: <path fillRule="evenodd" d="M2 2.5A2.5 2.5 0 014.5 0h8.75a.75.75 0 01.75.75v12.5a.75.75 0 01-.75.75h-2.5a.75.75 0 110-1.5h1.75v-2h-8a1 1 0 00-.714 1.7.75.75 0 01-1.072 1.05A2.495 2.495 0 012 11.5v-9zm10.5-1V9h-8c-.356 0-.694.074-1 .208V2.5a1 1 0 011-1h8zM5 12.25v3.25a.25.25 0 00.4.2l1.45-1.087a.25.25 0 01.3 0L8.6 15.7a.25.25 0 00.4-.2v-3.25a.25.25 0 00-.25-.25h-3.5a.25.25 0 00-.25.25z"/>,
|
||||||
fork: <path fill-rule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"/>,
|
fork: <path fillRule="evenodd" d="M5 3.25a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm0 2.122a2.25 2.25 0 10-1.5 0v.878A2.25 2.25 0 005.75 8.5h1.5v2.128a2.251 2.251 0 101.5 0V8.5h1.5a2.25 2.25 0 002.25-2.25v-.878a2.25 2.25 0 10-1.5 0v.878a.75.75 0 01-.75.75h-4.5A.75.75 0 015 6.25v-.878zm3.75 7.378a.75.75 0 11-1.5 0 .75.75 0 011.5 0zm3-8.75a.75.75 0 100-1.5.75.75 0 000 1.5z"/>,
|
||||||
};
|
};
|
||||||
export default icons
|
export default icons
|
||||||
|
@ -1,42 +1,18 @@
|
|||||||
import { logger, CustomError } from './utils'
|
import { CustomError, request } from './utils'
|
||||||
|
import { AxiosPromise } from 'axios';
|
||||||
|
|
||||||
const retryer = async (fetcher, variables, retries = 0) => {
|
const retryer = async <T = AxiosPromise<{error: string}>>(fetcher: (variables: {login: string}) => T, variables: {login: string}, retries = 0): Promise<T> => {
|
||||||
if (retries > 7) {
|
if (retries > 7) {
|
||||||
throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
|
throw new CustomError("Maximum retries exceeded", 'MAX_RETRY');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// try to fetch with the first token since RETRIES is 0 index i'm adding +1
|
|
||||||
let response = await fetcher(
|
let response = await fetcher(
|
||||||
variables,
|
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`);
|
|
||||||
retries++;
|
|
||||||
// directly return from the function
|
|
||||||
return retryer(fetcher, variables, retries);
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally return the response
|
|
||||||
return response;
|
return response;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// prettier-ignore
|
return retryer(fetcher, variables, 1 + retries);
|
||||||
// also checking for bad credentials if any tokens gets invalidated
|
|
||||||
const isBadCredential = err.response.data && err.response.data.message === "Bad credentials";
|
|
||||||
|
|
||||||
if (isBadCredential) {
|
|
||||||
logger.log(`PAT_${retries + 1} Failed`);
|
|
||||||
retries++;
|
|
||||||
// directly return from the function
|
|
||||||
return retryer(fetcher, variables, retries);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import axios from 'axios'
|
import axios, { AxiosPromise } from 'axios'
|
||||||
import wrap from 'word-wrap'
|
import wrap from 'word-wrap'
|
||||||
import themes from '../../themes'
|
import themes from '../../themes'
|
||||||
|
import { CodeStatsResponse } from '../fetcher/interface';
|
||||||
|
|
||||||
export const renderError = (message: string, secondaryMessage = "") => {
|
export const renderError = (message: string, secondaryMessage = "") => {
|
||||||
return (
|
return (
|
||||||
@ -13,7 +14,7 @@ export const renderError = (message: string, secondaryMessage = "") => {
|
|||||||
.gray { fill: #858585 }
|
.gray { fill: #858585 }
|
||||||
`}</style>
|
`}</style>
|
||||||
<rect x="0.5" y="0.5" width="494" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
|
<rect x="0.5" y="0.5" width="494" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
|
||||||
<text x="25" y="45" className="text">Something went wrong! file an issue at https://git.io/JJmN9</text>
|
<text x="25" y="45" className="text">Something went wrong! file an issue at https://dze.io/3nL29</text>
|
||||||
<text data-testid="message" x="25" y="55" className="text small">
|
<text data-testid="message" x="25" y="55" className="text small">
|
||||||
<tspan x="25" dy="18">{encodeHTML(message)}</tspan>
|
<tspan x="25" dy="18">{encodeHTML(message)}</tspan>
|
||||||
<tspan x="25" dy="18" className="gray">{secondaryMessage}</tspan>
|
<tspan x="25" dy="18" className="gray">{secondaryMessage}</tspan>
|
||||||
@ -33,7 +34,7 @@ export function encodeHTML(str: string) {
|
|||||||
|
|
||||||
export function kFormatter(num: number) {
|
export function kFormatter(num: number) {
|
||||||
return Math.abs(num) > 999
|
return Math.abs(num) > 999
|
||||||
? Math.sign(num) * (Math.abs(num) / 1000).toFixed(1) + "k"
|
? Math.sign(num) * parseInt((Math.abs(num) / 1000).toFixed(1)) + "k"
|
||||||
: Math.sign(num) * Math.abs(num);
|
: Math.sign(num) * Math.abs(num);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,16 +79,7 @@ export function fallbackColor(color: string, fallbackColor: Array<string>| strin
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function request(data?: any, headers?: any) {
|
export function request(data: {login: string}): AxiosPromise<CodeStatsResponse> {
|
||||||
return axios({
|
|
||||||
url: "https://api.github.com/graphql",
|
|
||||||
method: "post",
|
|
||||||
headers,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function codeStatsRequest(data?: any) {
|
|
||||||
return axios({
|
return axios({
|
||||||
url: "https://codestats.net/api/users/" + data.login,
|
url: "https://codestats.net/api/users/" + data.login,
|
||||||
method: "get",
|
method: "get",
|
||||||
@ -182,9 +174,7 @@ export const CONSTANTS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SECONDARY_ERROR_MESSAGES = {
|
export const SECONDARY_ERROR_MESSAGES = {
|
||||||
MAX_RETRY:
|
MAX_RETRY: "Make sur your profile is not private"
|
||||||
"Please add an env variable called PAT_1 with your github token in vercel",
|
|
||||||
USER_NOT_FOUND: "Make sure the provided username is not an organization",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class CustomError extends Error {
|
export class CustomError extends Error {
|
||||||
@ -198,3 +188,16 @@ export class CustomError extends Error {
|
|||||||
static MAX_RETRY = "MAX_RETRY";
|
static MAX_RETRY = "MAX_RETRY";
|
||||||
static USER_NOT_FOUND = "USER_NOT_FOUND";
|
static USER_NOT_FOUND = "USER_NOT_FOUND";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getLevel(xp: number): number {
|
||||||
|
return Math.trunc(Math.floor(CONSTANTS.LEVEL_FACTOR * Math.sqrt(xp)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the progress (0-99)% til next level
|
||||||
|
* @param xp Xp number
|
||||||
|
*/
|
||||||
|
export function getProgress(xp: number): number {
|
||||||
|
const currentLvl = getLevel(xp)
|
||||||
|
return Math.trunc((CONSTANTS.LEVEL_FACTOR * Math.sqrt(xp) - currentLvl) * 100)
|
||||||
|
}
|
||||||
|
@ -1,28 +1,7 @@
|
|||||||
import { codeStatsRequest, logger, CONSTANTS } from '../common/utils'
|
import { request, logger, CONSTANTS, getLevel, CustomError } from '../common/utils'
|
||||||
import retryer from '../common/retryer'
|
import retryer from '../common/retryer'
|
||||||
import languageColor from '../../themes/language-bar.json'
|
import languageColor from '../../themes/language-bar.json'
|
||||||
|
|
||||||
const fetcher = (variables: any) => {
|
|
||||||
return codeStatsRequest(
|
|
||||||
variables
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface response {
|
|
||||||
user: string
|
|
||||||
total_xp: number
|
|
||||||
new_xp: number
|
|
||||||
machines: Record<string, {
|
|
||||||
xps: number
|
|
||||||
new_xps: number
|
|
||||||
}>
|
|
||||||
languages: Record<string, {
|
|
||||||
xps: number
|
|
||||||
new_xps: number
|
|
||||||
}>
|
|
||||||
dates: Record<string, number>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface data {
|
export interface data {
|
||||||
name: string
|
name: string
|
||||||
size: number
|
size: number
|
||||||
@ -30,10 +9,23 @@ export interface data {
|
|||||||
recentSize: number
|
recentSize: number
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTopLanguages(username: string) {
|
export async function fetchProfile(username: string) {
|
||||||
|
if (!username) throw Error('Invalid Username')
|
||||||
|
|
||||||
|
const response = await retryer(request, {login: username})
|
||||||
|
|
||||||
|
return {
|
||||||
|
username,
|
||||||
|
xp: response.data.total_xp,
|
||||||
|
recentXp: response.data.new_xp,
|
||||||
|
level: getLevel(response.data.total_xp + response.data.new_xp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTopLanguages(username: string) {
|
||||||
if (!username) throw Error("Invalid username");
|
if (!username) throw Error("Invalid username");
|
||||||
|
|
||||||
let res: {data: response} = await retryer(fetcher, { login: username });
|
let res = await retryer(request, { login: username });
|
||||||
|
|
||||||
let repoNodes = res.data.languages;
|
let repoNodes = res.data.languages;
|
||||||
|
|
||||||
@ -76,5 +68,3 @@ async function fetchTopLanguages(username: string) {
|
|||||||
|
|
||||||
return topLangs as Record<string, data>
|
return topLangs as Record<string, data>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default fetchTopLanguages
|
|
15
src/fetcher/interface.d.ts
vendored
Normal file
15
src/fetcher/interface.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export interface CodeStatsResponse {
|
||||||
|
user: string
|
||||||
|
error?: string
|
||||||
|
total_xp: number
|
||||||
|
new_xp: number
|
||||||
|
machines: Record<string, {
|
||||||
|
xps: number
|
||||||
|
new_xps: number
|
||||||
|
}>
|
||||||
|
languages: Record<string, {
|
||||||
|
xps: number
|
||||||
|
new_xps: number
|
||||||
|
}>
|
||||||
|
dates: Record<string, number>
|
||||||
|
}
|
@ -44,49 +44,49 @@ export const getAnimations = () => {
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const getStyles = ({
|
export const getStyles = ({
|
||||||
// titleColor,
|
titleColor,
|
||||||
// textColor,
|
textColor,
|
||||||
// iconColor,
|
iconColor,
|
||||||
// show_icons,
|
show_icons,
|
||||||
// progress,
|
progress,
|
||||||
// }) => {
|
}: any) => {
|
||||||
// return `
|
return `
|
||||||
// .stat {
|
.stat {
|
||||||
// font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
|
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
|
||||||
// }
|
}
|
||||||
// .stagger {
|
.stagger {
|
||||||
// opacity: 0;
|
opacity: 0;
|
||||||
// animation: fadeInAnimation 0.3s ease-in-out forwards;
|
animation: fadeInAnimation 0.3s ease-in-out forwards;
|
||||||
// }
|
}
|
||||||
// .rank-text {
|
.rank-text {
|
||||||
// font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor};
|
font: 800 24px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor};
|
||||||
// animation: scaleInAnimation 0.3s ease-in-out forwards;
|
animation: scaleInAnimation 0.3s ease-in-out forwards;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// .bold { font-weight: 700 }
|
.bold { font-weight: 700 }
|
||||||
// .icon {
|
.icon {
|
||||||
// fill: ${iconColor};
|
fill: ${iconColor};
|
||||||
// display: ${!!show_icons ? "block" : "none"};
|
display: ${!!show_icons ? "block" : "none"};
|
||||||
// }
|
}
|
||||||
|
|
||||||
// .rank-circle-rim {
|
.rank-circle-rim {
|
||||||
// stroke: ${titleColor};
|
stroke: ${titleColor};
|
||||||
// fill: none;
|
fill: none;
|
||||||
// stroke-width: 6;
|
stroke-width: 6;
|
||||||
// opacity: 0.2;
|
opacity: 0.2;
|
||||||
// }
|
}
|
||||||
// .rank-circle {
|
.rank-circle {
|
||||||
// stroke: ${titleColor};
|
stroke: ${titleColor};
|
||||||
// stroke-dasharray: 250;
|
stroke-dasharray: 250;
|
||||||
// fill: none;
|
fill: none;
|
||||||
// stroke-width: 6;
|
stroke-width: 6;
|
||||||
// stroke-linecap: round;
|
stroke-linecap: round;
|
||||||
// opacity: 0.8;
|
opacity: 0.8;
|
||||||
// transform-origin: -10px 8px;
|
transform-origin: -10px 8px;
|
||||||
// transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
// animation: rankAnimation 1s forwards ease-in-out;
|
animation: rankAnimation 1s forwards ease-in-out;
|
||||||
// }
|
}
|
||||||
// ${process.env.NODE_ENV === "test" ? "" : getProgressAnimation({ progress })}
|
${process.env.NODE_ENV === "test" ? "" : getProgressAnimation({ progress })}
|
||||||
// `;
|
`;
|
||||||
// };
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user