guardflux
Version:
A light callable lib to keep your API alive
170 lines (145 loc) • 7.39 kB
text/typescript
import { MikroORM, Options } from '@mikro-orm/core';
import { CheckResult, DbConfig, DBType, RateLimitOptions } from './lib/types';
import { RateLimit } from './lib/entity';
import { dbDefualtName } from './lib/constants';
import { emptyObj, userReachMaxRateLimit } from './lib/messages';
import { MongoDriver } from '@mikro-orm/mongodb';
import { MySqlDriver } from '@mikro-orm/mysql';
import { PostgreSqlDriver } from '@mikro-orm/postgresql';
import { isObjectEmpty, devDebugger } from './lib/helpers';
import Joi = require('joi');
/**
* Returns the appropriate MikroORM driver based on the specified database type.
*
* @param dbType - The type of the database. It can be one of 'postgresql', 'mysql', or 'mongodb'.
* @returns The MikroORM driver for the specified database type.
* @throws An error if the provided database type is not supported.
*/
export function getDriver(dbType: DBType) {
switch (dbType) {
case 'postgresql':
return PostgreSqlDriver; // Returns the PostgreSQL driver
case 'mysql':
return MySqlDriver; // Returns the MySQL driver
case 'mongodb':
return MongoDriver; // Returns the MongoDB driver
default:
throw new Error('Unsupported database type'); // Throws an error if the database type is not recognized
}
}
/**
* The schema constant is initialized as a Joi Root instance,
* which provides the main API for creating schemas.
*
* @constant {Joi.Root} schema - The root Joi object used for schema validation.
*/
export const schema: Joi.Root = Joi; // Exports the Joi root object for use in validation
/**
* Checks if an object conforms to a specified Joi schema and returns the validation result.
*
* @param {any} obj - The object to validate against the schema.
* @param {Joi.ObjectSchema<any>} schema - The Joi schema to validate the object against.
* @param {boolean} [devMode=true] - Flag to enable or disable debugging logs. Default is true.
* @returns {Promise<CheckResult>} - A promise that resolves to a CheckResult object containing
* the validation status and any log information.
*/
export async function checkObject(
obj: any,
schema: Joi.ObjectSchema<any>,
devMode: boolean = true
): Promise<CheckResult> {
let result: CheckResult = {
isValid: false, // Initialize result with isValid set to false
};
// Check if the object is empty
if (isObjectEmpty(obj)) {
result.log = emptyObj; // Log the empty object message
return result; // Return the result early
}
try {
// Validate the object against the schema asynchronously
await schema.validateAsync(obj).then(() => {
result.isValid = true; // Set isValid to true if validation passes
});
} catch (error) {
// If validation fails, log the error
result.log = error;
}
// Debugging log if in development mode
devDebugger(result, devMode);
return result; // Return the result object
}
/**
* Implements rate limiting for a user based on specified options.
* This function checks the user's request count and manages their rate limit status in the database.
*
* @param {string} userId - The unique identifier for the user to apply rate limiting.
* @param {RateLimitOptions} options - Options defining the rate limiting parameters, including the route and maximum requests.
* @param {DbConfig} dbConfig - Configuration details for connecting to the database.
* @param {boolean} [devMode=true] - Optional flag to enable debugging output. Defaults to true.
* @returns {Promise<CheckResult>} - A promise that resolves to a CheckResult object containing
* the validation status and any log information.
*/
export async function rateLimit(
userId: string,
options: RateLimitOptions,
dbConfig: DbConfig,
devMode: boolean = true
): Promise<CheckResult> {
let result: CheckResult = {
isValid: true, // Initialize result as valid
};
// Configuration for the MikroORM connection
const config: Options = {
dbName: dbConfig.dbName || dbDefualtName, // Use provided DB name or default
clientUrl: dbConfig.dbURI, // MongoDB connection URI
entities: [RateLimit], // Specify the RateLimit entity to manage
debug: dbConfig.dbDebug, // Debug mode from DB configuration
driver: getDriver(dbConfig.dbType), // Determine the database driver based on type
allowGlobalContext: true // Allow usage of global context for ORM
};
// Initialize MikroORM
const orm = await MikroORM.init(config);
const entityManager = orm.em.fork(); // Create a fork of the entity manager for isolated operations
const currentTime = new Date(); // Get the current time
const cycleStart = new Date(currentTime.getTime() - options.cycleTime * 1000); // Calculate the start time of the current cycle
// Find the current rate limit record for the user and route
let rateLimit = await entityManager.findOne(RateLimit, { userId: userId, route: options.route });
if (!rateLimit) {
// If no rate limit record exists, create a new one
rateLimit = new RateLimit();
rateLimit.userId = userId; // Set user ID
rateLimit.route = options.route; // Set the current route
rateLimit.requestCount = 1; // Initialize request count
rateLimit.lastRequest = currentTime; // Set the last request time
devDebugger(rateLimit, devMode); // Log the new rate limit record for debugging
entityManager.create(RateLimit, rateLimit); // Create the new entity
await entityManager.flush(); // Save changes to the database
return result; // Return valid result as the rate limit is not exceeded
} else {
// If the rate limit record exists, check the last request time
if (rateLimit.lastRequest <= cycleStart) {
// If the last request is older than the cycle start, reset the count
rateLimit.requestCount = 1; // Reset request count
rateLimit.lastRequest = currentTime; // Update last request time
devDebugger(rateLimit, devMode); // Log the updated rate limit record for debugging
await entityManager.persistAndFlush(rateLimit); // Save changes to the database
return result; // Return valid result as the rate limit is not exceeded
}
// If the request count is below the maximum allowed
if (rateLimit.requestCount < options.maxRequests) {
rateLimit.requestCount++; // Increment the request count
rateLimit.lastRequest = currentTime; // Update last request time
devDebugger(rateLimit, devMode); // Log the updated rate limit record for debugging
await entityManager.persistAndFlush(rateLimit); // Save changes to the database
return result; // Return valid result as the rate limit is not exceeded
}
}
// If none of the above conditions are met, the rate limit has been reached
result = {
isValid: false, // Set result as invalid
log: userReachMaxRateLimit // Log the maximum rate limit reached
};
devDebugger(result, devMode); // Log the result for debugging
return result; // Return the result object
}