move to src

This commit is contained in:
Alex Holliday
2025-07-25 15:33:48 -07:00
parent acc910cbc0
commit 1c47c8ce2c
127 changed files with 71 additions and 19 deletions
+100
View File
@@ -0,0 +1,100 @@
const calculatePercentile = (arr, percentile) => {
const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
const index = (percentile / 100) * (sorted.length - 1);
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= sorted.length) return sorted[lower].responseTime;
return sorted[lower].responseTime * (1 - weight) + sorted[upper].responseTime * weight;
};
const calculatePercentileUptimeDetails = (arr, percentile) => {
const sorted = arr.slice().sort((a, b) => a.avgResponseTime - b.avgResponseTime);
const index = (percentile / 100) * (sorted.length - 1);
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= sorted.length) return sorted[lower].avgResponseTime;
return sorted[lower].avgResponseTime * (1 - weight) + sorted[upper].avgResponseTime * weight;
};
const NormalizeData = (checks, rangeMin, rangeMax) => {
if (checks.length > 1) {
// Get the 5th and 95th percentile
const min = calculatePercentile(checks, 0);
const max = calculatePercentile(checks, 95);
const normalizedChecks = checks.map((check) => {
const originalResponseTime = check.responseTime;
// Normalize the response time between 1 and 100
let normalizedResponseTime = rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
// Put a floor on the response times so we don't have extreme outliers
// Better visuals
normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
return {
...check,
responseTime: normalizedResponseTime,
originalResponseTime: originalResponseTime,
};
});
return normalizedChecks;
} else {
return checks.map((check) => {
return { ...check, originalResponseTime: check.responseTime };
});
}
};
const NormalizeDataUptimeDetails = (checks, rangeMin, rangeMax) => {
if (checks.length > 1) {
// Get the 5th and 95th percentile
const min = calculatePercentileUptimeDetails(checks, 0);
const max = calculatePercentileUptimeDetails(checks, 95);
const normalizedChecks = checks.map((check) => {
const originalResponseTime = check.avgResponseTime;
// Normalize the response time between 1 and 100
let normalizedResponseTime = rangeMin + ((check.avgResponseTime - min) * (rangeMax - rangeMin)) / (max - min);
// Put a floor on the response times so we don't have extreme outliers
// Better visuals
normalizedResponseTime = Math.max(rangeMin, Math.min(rangeMax, normalizedResponseTime));
return {
...check,
avgResponseTime: normalizedResponseTime,
originalAvgResponseTime: originalResponseTime,
};
});
return normalizedChecks;
} else {
return checks.map((check) => {
return { ...check, originalResponseTime: check.responseTime };
});
}
};
const safelyParseFloat = (value, defaultValue = 0) => {
if (value === null || typeof value === "undefined") {
return defaultValue;
}
const stringValue = String(value).trim();
if (typeof value === "number" && !isNaN(value)) {
return value;
}
if (stringValue === "") {
return defaultValue;
}
const parsedValue = parseFloat(stringValue);
if (isNaN(parsedValue) || !isFinite(parsedValue)) {
return defaultValue;
}
return parsedValue;
};
export { safelyParseFloat, calculatePercentile, NormalizeData, calculatePercentileUptimeDetails, NormalizeDataUptimeDetails };
+22
View File
@@ -0,0 +1,22 @@
[
{
"name": "Google",
"url": "https://www.google.com"
},
{
"name": "Facebook",
"url": "https://www.facebook.com"
},
{
"name": "Yahoo",
"url": "https://www.yahoo.com"
},
{
"name": "Amazon",
"url": "https://www.amazon.com"
},
{
"name": "Apple",
"url": "https://www.apple.com"
}
]
+1271
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
import sharp from "sharp";
/**
* Generates a 64 * 64 pixel image from a given image
* @param {} file
*/
const GenerateAvatarImage = async (file) => {
try {
// Resize to target 64 * 64
let resizedImageBuffer = await sharp(file.buffer)
.resize({
width: 64,
height: 64,
fit: "cover",
})
.toBuffer();
//Get b64 string
const base64Image = resizedImageBuffer.toString("base64");
return base64Image;
} catch (error) {
throw error;
}
};
export { GenerateAvatarImage };
+151
View File
@@ -0,0 +1,151 @@
import { createLogger, format, transports } from "winston";
import dotenv from "dotenv";
dotenv.config();
const SERVICE_NAME = "Logger";
class Logger {
static SERVICE_NAME = SERVICE_NAME;
constructor() {
this.logCache = [];
this.maxCacheSize = 1000;
const consoleFormat = format.printf(({ level, message, service, method, details, timestamp, stack }) => {
if (message instanceof Object) {
message = JSON.stringify(message, null, 2);
}
if (details instanceof Object) {
details = JSON.stringify(details, null, 2);
}
let msg = `${timestamp} ${level}:`;
service && (msg += ` [${service}]`);
method && (msg += `(${method})`);
message && (msg += ` ${message}`);
details && (msg += ` (details: ${details})`);
if (typeof stack !== "undefined") {
const stackTrace = stack
?.split("\n")
.slice(1) // Remove first line (error message)
.map((line) => {
const match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/);
if (match) {
return {
function: match[1],
file: match[2],
line: parseInt(match[3]),
column: parseInt(match[4]),
};
}
return line.trim();
});
stack && (msg += ` (stack: ${JSON.stringify(stackTrace, null, 2)})`);
}
return msg;
});
const logLevel = process.env.LOG_LEVEL || "info";
this.logger = createLogger({
level: logLevel,
format: format.combine(format.timestamp()),
transports: [
new transports.Console({
format: format.combine(format.colorize(), format.prettyPrint(), format.json(), consoleFormat),
}),
new transports.File({
format: format.combine(format.json()),
filename: "app.log",
}),
],
});
}
get serviceName() {
return Logger.SERVICE_NAME;
}
/**
* Logs an informational message.
* @param {Object} config - The configuration object.
* @param {string} config.message - The message to log.
* @param {string} config.service - The service name.
* @param {string} config.method - The method name.
* @param {Object} config.details - Additional details.
*/
info(config) {
const logEntry = this.buildLogEntry("info", config);
this.cacheLog(logEntry);
this.logger.info(logEntry);
}
/**
* Logs a warning message.
* @param {Object} config - The configuration object.
* @param {string} config.message - The message to log.
* @param {string} config.service - The service name.
* @param {string} config.method - The method name.
* @param {Object} config.details - Additional details.
*/
warn(config) {
const logEntry = this.buildLogEntry("warn", config);
this.cacheLog(logEntry);
this.logger.warn(logEntry);
}
/**
* Logs an error message.
* @param {Object} config - The configuration object.
* @param {string} config.message - The message to log.
* @param {string} config.service - The service name.
* @param {string} config.method - The method name.
* @param {Object} config.details - Additional details.
*/
error(config) {
const logEntry = this.buildLogEntry("error", config);
this.cacheLog(logEntry);
this.logger.error(logEntry);
}
/**
* Logs a debug message.
* @param {Object} config - The configuration object.
* @param {string} config.message - The message to log.
* @param {string} config.service - The service name.
* @param {string} config.method - The method name.
* @param {Object} config.details - Additional details.
*/
debug(config) {
const logEntry = this.buildLogEntry("debug", config);
this.cacheLog(logEntry);
this.logger.debug(logEntry);
}
cacheLog(entry) {
this.logCache.push(entry);
if (this.logCache.length > this.maxCacheSize) {
this.logCache.shift();
}
}
getLogs() {
return this.logCache;
}
buildLogEntry(level, config) {
return {
level,
message: config.message,
service: config.service,
method: config.method,
details: config.details,
stack: config.stack,
timestamp: new Date().toISOString(),
};
}
}
const logger = new Logger();
export { Logger };
export default logger;
+13
View File
@@ -0,0 +1,13 @@
export const ROLES = {
SUPERADMIN: "superadmin",
ADMIN: "admin",
USER: "user",
DEMO: "demo",
};
export const VALID_ROLES = [ROLES.ADMIN, ROLES.USER, ROLES.DEMO];
export const EDITABLE_ROLES = [
{ role: ROLES.ADMIN, _id: ROLES.ADMIN },
{ role: ROLES.USER, _id: ROLES.USER },
];
+24
View File
@@ -0,0 +1,24 @@
/**
* Converts a request body parameter to a boolean.
* @param {string | boolean} value
* @returns {boolean}
*/
const ParseBoolean = (value) => {
if (value === true || value === "true") {
return true;
} else if (value === false || value === "false" || value === null || value === undefined) {
return false;
}
};
const getTokenFromHeaders = (headers) => {
const authorizationHeader = headers.authorization;
if (!authorizationHeader) throw new Error("No auth headers");
const parts = authorizationHeader.split(" ");
if (parts.length !== 2 || parts[0] !== "Bearer") throw new Error("Invalid auth headers");
return parts[1];
};
export { ParseBoolean, getTokenFromHeaders };