diff --git a/backend/api-renamed/authenticate.js b/backend/api-renamed/authenticate.js index b5852982..a0a697e6 100644 --- a/backend/api-renamed/authenticate.js +++ b/backend/api-renamed/authenticate.js @@ -1,4 +1,4 @@ -import { logger } from "../src/common/utils.js"; +import { logger } from "../src/common/log.js"; import { authenticate } from "../src/users.js"; /** diff --git a/backend/api-renamed/delete-user.js b/backend/api-renamed/delete-user.js index 0a561668..25e991ad 100644 --- a/backend/api-renamed/delete-user.js +++ b/backend/api-renamed/delete-user.js @@ -1,4 +1,4 @@ -import { logger } from "../src/common/utils.js"; +import { logger } from "../src/common/log.js"; import { deleteUser } from "../src/common/database.js"; /** diff --git a/backend/api-renamed/pin.js b/backend/api-renamed/pin.js index cde17ee4..5e409b9f 100644 --- a/backend/api-renamed/pin.js +++ b/backend/api-renamed/pin.js @@ -13,7 +13,7 @@ import { retrieveSecondaryMessage, } from "../src/common/error.js"; import { parseBoolean } from "../src/common/ops.js"; -import { parseArray } from "../src/common/utils.js"; +import { parseArray } from "../src/common/ops.js"; import { renderError } from "../src/common/render.js"; import { fetchRepo } from "../src/fetchers/repo.js"; import { isLocaleAvailable } from "../src/translations.js"; @@ -110,13 +110,12 @@ export default async (req, res) => { showStats.includes("issues_authored"), showStats.includes("issues_commented"), ); - const repoData = await fetchRepo(username, repo); const cacheSeconds = resolveCacheSeconds({ requested: parseInt(cache_seconds, 10), - def: CONSTANTS.PIN_CARD_CACHE_SECONDS, - min: CONSTANTS.FOUR_HOURS, - max: CONSTANTS.TEN_DAY, + def: CACHE_TTL.PIN_CARD.DEFAULT, + min: CACHE_TTL.PIN_CARD.MIN, + max: CACHE_TTL.PIN_CARD.MAX, }); setCacheHeaders(res, cacheSeconds); diff --git a/backend/api-renamed/user-access.js b/backend/api-renamed/user-access.js index cb5720ea..dfadac35 100644 --- a/backend/api-renamed/user-access.js +++ b/backend/api-renamed/user-access.js @@ -1,4 +1,4 @@ -import { logger } from "../src/common/utils.js"; +import { logger } from "../src/common/log.js"; import { getUserAccess } from "../src/common/database.js"; /** diff --git a/backend/api-renamed/wakatime-proxy.js b/backend/api-renamed/wakatime-proxy.js index 45be7b65..77dca8ba 100644 --- a/backend/api-renamed/wakatime-proxy.js +++ b/backend/api-renamed/wakatime-proxy.js @@ -1,5 +1,5 @@ import { fetchWakatimeStats } from "../src/fetchers/wakatime.js"; -import { logger } from "../src/common/utils.js"; +import { logger } from "../src/common/log.js"; /** * @param {any} req The request. diff --git a/backend/src/cards/repo.js b/backend/src/cards/repo.js index aac1d353..93ec28ba 100644 --- a/backend/src/cards/repo.js +++ b/backend/src/cards/repo.js @@ -7,7 +7,7 @@ import { encodeHTML } from "../common/html.js"; import { I18n } from "../common/I18n.js"; import { icons } from "../common/icons.js"; import { clampValue, parseEmojis } from "../common/ops.js"; -import { buildSearchFilter } from "../common/utils.js"; +import { buildSearchFilter } from "../common/ops.js"; import { flexLayout, measureText, diff --git a/backend/src/cards/stats.js b/backend/src/cards/stats.js index 6e200cd4..5a786fed 100644 --- a/backend/src/cards/stats.js +++ b/backend/src/cards/stats.js @@ -7,7 +7,7 @@ import { kFormatter } from "../common/fmt.js"; import { I18n } from "../common/I18n.js"; import { icons, rankIcon } from "../common/icons.js"; import { clampValue } from "../common/ops.js"; -import { buildSearchFilter } from "../common/utils.js"; +import { buildSearchFilter } from "../common/ops.js"; import { flexLayout, measureText } from "../common/render.js"; import { statCardLocales, wakatimeCardLocales } from "../translations.js"; diff --git a/backend/src/cards/wakatime.js b/backend/src/cards/wakatime.js index 6f55cb21..17177403 100644 --- a/backend/src/cards/wakatime.js +++ b/backend/src/cards/wakatime.js @@ -235,7 +235,7 @@ const normalizeCardWidth = ({ value, layout }) => { */ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { let { languages = [] } = stats; - let { + const { hide_title = false, hide_border = false, card_width, diff --git a/backend/src/common/error.js b/backend/src/common/error.js index 3f8a1104..fd1f8d6e 100644 --- a/backend/src/common/error.js +++ b/backend/src/common/error.js @@ -1,6 +1,6 @@ // @ts-check -import { OWNER_AFFILIATIONS } from "./utils.js"; +import { OWNER_AFFILIATIONS } from "./render.js"; /** * @type {string} A general message to ask user to try again later. diff --git a/backend/src/common/ops.js b/backend/src/common/ops.js index b4db6e60..cfdc2c2a 100644 --- a/backend/src/common/ops.js +++ b/backend/src/common/ops.js @@ -1,6 +1,7 @@ // @ts-check import toEmoji from "emoji-name-map"; +import { SECONDARY_ERROR_MESSAGES, TRY_AGAIN_LATER } from "./error.js"; /** * Returns boolean if value is either "true" or "false" else the value as it is. @@ -113,6 +114,49 @@ const dateDiff = (d1, d2) => { return Math.round(diff / (1000 * 60)); }; +/** + * 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; +}; + +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; +}; + export { parseBoolean, parseArray, @@ -121,4 +165,6 @@ export { chunkArray, parseEmojis, dateDiff, + parseOwnerAffiliations, + buildSearchFilter, }; diff --git a/backend/src/common/render.js b/backend/src/common/render.js index 21282a05..6b970c35 100644 --- a/backend/src/common/render.js +++ b/backend/src/common/render.js @@ -117,26 +117,6 @@ const iconWithLabel = (icon, label, testid, iconSize) => { return flexLayout({ items: [iconSvg, text], gap: 20 }).join(""); }; -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; -}; - -// Script parameters. -const ERROR_CARD_LENGTH = 576.5; - -const UPSTREAM_API_ERRORS = [ - TRY_AGAIN_LATER, - SECONDARY_ERROR_MESSAGES.MAX_RETRY, -]; - /** * Renders error message on the card. * @@ -242,36 +222,13 @@ const measureText = (str, fontSize = 10) => { ); }; -/** - * 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"]; +// Script parameters. +const ERROR_CARD_LENGTH = 576.5; - // 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; -}; +const UPSTREAM_API_ERRORS = [ + TRY_AGAIN_LATER, + SECONDARY_ERROR_MESSAGES.MAX_RETRY, +]; export { ERROR_CARD_LENGTH, @@ -279,9 +236,7 @@ export { createLanguageNode, createProgressNode, iconWithLabel, - buildSearchFilter, flexLayout, OWNER_AFFILIATIONS, measureText, - parseOwnerAffiliations, }; diff --git a/backend/src/common/utils.js b/backend/src/common/utils.js deleted file mode 100644 index a4b5573c..00000000 --- a/backend/src/common/utils.js +++ /dev/null @@ -1,261 +0,0 @@ -// @ts-check - -import { SECONDARY_ERROR_MESSAGES, TRY_AGAIN_LATER } from "./error.js"; -import { getCardColors } from "./color.js"; - -const OWNER_AFFILIATIONS = ["OWNER", "COLLABORATOR", "ORGANIZATION_MEMBER"]; - -/** - * 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(""); -}; - -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; -}; - -// 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 {object} args Function arguments. - * @param {string} args.message Main error message. - * @param {string} [args.secondaryMessage=""] The secondary error message. - * @param {object} [args.renderOptions={}] Render options. - * @param {string=} args.renderOptions.title_color Card title color. - * @param {string=} args.renderOptions.text_color Card text color. - * @param {string=} args.renderOptions.bg_color Card background color. - * @param {string=} args.renderOptions.border_color Card border color. - * @param {Parameters[0]["theme"]=} args.renderOptions.theme Card theme. - * @param {boolean=} args.renderOptions.show_repo_link Whether to show repo link or not. - * @returns {string} The SVG markup. - */ -const renderError = ({ - message, - secondaryMessage = "", - renderOptions = {}, -}) => { - const { - title_color, - text_color, - bg_color, - border_color, - theme = "default", - show_repo_link = true, - } = renderOptions; - - // 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} - - - `; -}; - -/** - * 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 - ); -}; - -/** - * 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; -}; - -export { - ERROR_CARD_LENGTH, - renderError, - createLanguageNode, - iconWithLabel, - encodeHTML, - buildSearchFilter, - flexLayout, - OWNER_AFFILIATIONS, - measureText, - parseOwnerAffiliations, -}; diff --git a/backend/src/fetchers/stats.js b/backend/src/fetchers/stats.js index 5208e5e9..429cb07e 100644 --- a/backend/src/fetchers/stats.js +++ b/backend/src/fetchers/stats.js @@ -8,7 +8,7 @@ import { retryer } from "../common/retryer.js"; import { buildSearchFilter, parseOwnerAffiliations, -} from "../common/utils.js"; +} from "../common/ops.js"; import { logger } from "../common/log.js"; import { excludeRepositories } from "../common/envs.js"; import { CustomError, MissingParamError } from "../common/error.js"; diff --git a/backend/src/fetchers/top-languages.js b/backend/src/fetchers/top-languages.js index 3de9f9bb..c72ca089 100644 --- a/backend/src/fetchers/top-languages.js +++ b/backend/src/fetchers/top-languages.js @@ -1,7 +1,7 @@ // @ts-check import { retryer } from "../common/retryer.js"; -import { parseOwnerAffiliations } from "../common/utils.js"; +import { parseOwnerAffiliations } from "../common/ops.js"; import { logger } from "../common/log.js"; import { excludeRepositories } from "../common/envs.js"; import { CustomError, MissingParamError } from "../common/error.js";