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 = `
-
- `;
- 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 `
-
- `;
-};
-
-/**
- * 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";