mirror of
https://github.com/Aviortheking/codestats-readme.git
synced 2025-04-22 02:32:09 +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 fetchTopLanguages from '../src/fetchers/top-languages-fetcher'
|
||||
import { fetchTopLanguages } from '../src/fetcher'
|
||||
import renderTopLanguages from '../src/cards/top-languages-card'
|
||||
import blacklist from '../src/common/blacklist'
|
||||
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 Card from '../common/Card'
|
||||
import { query } from "../../api/top-langs";
|
||||
import { data } from "../fetchers/top-languages-fetcher";
|
||||
import { data } from "../fetcher";
|
||||
import React from 'react'
|
||||
import themes from "../../themes";
|
||||
|
||||
|
@ -14,7 +14,7 @@ export default class Card {
|
||||
constructor(
|
||||
public width = 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 titlePrefixIcon?: string
|
||||
) {}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from 'react'
|
||||
|
||||
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"/>,
|
||||
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"/>,
|
||||
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"/>,
|
||||
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"/>,
|
||||
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"/>,
|
||||
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"/>,
|
||||
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"/>,
|
||||
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 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 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 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 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 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 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
|
||||
|
@ -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) {
|
||||
throw new CustomError("Maximum retries exceeded", CustomError.MAX_RETRY);
|
||||
throw new CustomError("Maximum retries exceeded", '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
|
||||
);
|
||||
variables
|
||||
)
|
||||
|
||||
// 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;
|
||||
} 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";
|
||||
|
||||
if (isBadCredential) {
|
||||
logger.log(`PAT_${retries + 1} Failed`);
|
||||
retries++;
|
||||
// directly return from the function
|
||||
return retryer(fetcher, variables, retries);
|
||||
}
|
||||
return retryer(fetcher, variables, 1 + retries);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
import axios from 'axios'
|
||||
import axios, { AxiosPromise } from 'axios'
|
||||
import wrap from 'word-wrap'
|
||||
import themes from '../../themes'
|
||||
import { CodeStatsResponse } from '../fetcher/interface';
|
||||
|
||||
export const renderError = (message: string, secondaryMessage = "") => {
|
||||
return (
|
||||
@ -13,7 +14,7 @@ export const renderError = (message: string, secondaryMessage = "") => {
|
||||
.gray { fill: #858585 }
|
||||
`}</style>
|
||||
<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">
|
||||
<tspan x="25" dy="18">{encodeHTML(message)}</tspan>
|
||||
<tspan x="25" dy="18" className="gray">{secondaryMessage}</tspan>
|
||||
@ -33,7 +34,7 @@ export function encodeHTML(str: string) {
|
||||
|
||||
export function kFormatter(num: number) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -78,16 +79,7 @@ export function fallbackColor(color: string, fallbackColor: Array<string>| strin
|
||||
);
|
||||
}
|
||||
|
||||
export function request(data?: any, headers?: any) {
|
||||
return axios({
|
||||
url: "https://api.github.com/graphql",
|
||||
method: "post",
|
||||
headers,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
export function codeStatsRequest(data?: any) {
|
||||
export function request(data: {login: string}): AxiosPromise<CodeStatsResponse> {
|
||||
return axios({
|
||||
url: "https://codestats.net/api/users/" + data.login,
|
||||
method: "get",
|
||||
@ -182,9 +174,7 @@ export const CONSTANTS = {
|
||||
};
|
||||
|
||||
export const SECONDARY_ERROR_MESSAGES = {
|
||||
MAX_RETRY:
|
||||
"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",
|
||||
MAX_RETRY: "Make sur your profile is not private"
|
||||
};
|
||||
|
||||
export class CustomError extends Error {
|
||||
@ -198,3 +188,16 @@ export class CustomError extends Error {
|
||||
static MAX_RETRY = "MAX_RETRY";
|
||||
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 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 {
|
||||
name: string
|
||||
size: number
|
||||
@ -30,10 +9,23 @@ export interface data {
|
||||
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");
|
||||
|
||||
let res: {data: response} = await retryer(fetcher, { login: username });
|
||||
let res = await retryer(request, { login: username });
|
||||
|
||||
let repoNodes = res.data.languages;
|
||||
|
||||
@ -76,5 +68,3 @@ async function fetchTopLanguages(username: string) {
|
||||
|
||||
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 = ({
|
||||
// titleColor,
|
||||
// textColor,
|
||||
// iconColor,
|
||||
// show_icons,
|
||||
// progress,
|
||||
// }) => {
|
||||
// return `
|
||||
// .stat {
|
||||
// font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
|
||||
// }
|
||||
// .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;
|
||||
// }
|
||||
export const getStyles = ({
|
||||
titleColor,
|
||||
textColor,
|
||||
iconColor,
|
||||
show_icons,
|
||||
progress,
|
||||
}: any) => {
|
||||
return `
|
||||
.stat {
|
||||
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: ${textColor};
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
// .bold { font-weight: 700 }
|
||||
// .icon {
|
||||
// fill: ${iconColor};
|
||||
// display: ${!!show_icons ? "block" : "none"};
|
||||
// }
|
||||
.bold { font-weight: 700 }
|
||||
.icon {
|
||||
fill: ${iconColor};
|
||||
display: ${!!show_icons ? "block" : "none"};
|
||||
}
|
||||
|
||||
// .rank-circle-rim {
|
||||
// stroke: ${titleColor};
|
||||
// fill: none;
|
||||
// stroke-width: 6;
|
||||
// opacity: 0.2;
|
||||
// }
|
||||
// .rank-circle {
|
||||
// stroke: ${titleColor};
|
||||
// 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 })}
|
||||
// `;
|
||||
// };
|
||||
.rank-circle-rim {
|
||||
stroke: ${titleColor};
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.rank-circle {
|
||||
stroke: ${titleColor};
|
||||
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 })}
|
||||
`;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user