165 lines
12 KiB
JavaScript
165 lines
12 KiB
JavaScript
import { PRODUCT_ACTIONS } from '../config/products.js';
|
|
/**
|
|
* Central registry for all billable functions
|
|
* Manages configuration, costs, and metadata
|
|
*/
|
|
export class FunctionRegistry {
|
|
static registry = new Map();
|
|
static initialized = false;
|
|
/**
|
|
* Initialize the registry with default configurations
|
|
*/
|
|
static initialize() {
|
|
if (this.initialized)
|
|
return;
|
|
// Load legacy PRODUCT_ACTIONS
|
|
for (const [productId, actions] of Object.entries(PRODUCT_ACTIONS)) {
|
|
for (const [actionId, config] of Object.entries(actions)) {
|
|
this.register({
|
|
productId,
|
|
actionId,
|
|
...config
|
|
});
|
|
}
|
|
}
|
|
this.initialized = true;
|
|
}
|
|
/**
|
|
* Register a new billable function
|
|
*/
|
|
static register(config) {
|
|
const key = this.getKey(config.productId, config.actionId);
|
|
this.registry.set(key, config);
|
|
}
|
|
/**
|
|
* Get configuration for a specific function
|
|
*/
|
|
static get(productId, actionId) {
|
|
if (!this.initialized)
|
|
this.initialize();
|
|
const key = this.getKey(productId, actionId);
|
|
return this.registry.get(key) || null;
|
|
}
|
|
/**
|
|
* Get all registered functions
|
|
*/
|
|
static getAll() {
|
|
if (!this.initialized)
|
|
this.initialize();
|
|
return Array.from(this.registry.values());
|
|
}
|
|
/**
|
|
* Find a configuration by matching route endpoint and method
|
|
* (Used for middleware backward compatibility)
|
|
*/
|
|
static findByRoute(path, method) {
|
|
if (!this.initialized)
|
|
this.initialize();
|
|
for (const config of this.registry.values()) {
|
|
if (this.matchesRoute(path, config.endpoint) && method === config.method) {
|
|
return config;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
static getKey(productId, actionId) {
|
|
return `${productId}:${actionId}`;
|
|
}
|
|
static matchesRoute(path, pattern) {
|
|
// Convert pattern to regex
|
|
// Handle both :param (Express/Hono style) and {param} (OpenAPI style)
|
|
// e.g., '/api/competitors/:place_id' or '/api/competitors/{place_id}' -> /^\/api\/competitors\/[^\/]+$/
|
|
const regexPattern = pattern
|
|
.replace(/:[^\\/]+/g, '[^/]+') // Replace :param with regex
|
|
.replace(/\{[^}]+\}/g, '[^/]+') // Replace {param} with regex
|
|
.replace(/\//g, '\\/');
|
|
// Allow optional trailing slash
|
|
const regex = new RegExp(`^${regexPattern}\\/?$`);
|
|
return regex.test(path);
|
|
}
|
|
}
|
|
/**
|
|
* Registry for public endpoints that don't require authentication
|
|
*/
|
|
export class PublicEndpointRegistry {
|
|
static registry = new Set();
|
|
static register(path, method) {
|
|
this.registry.add(`${method.toUpperCase()}:${path}`);
|
|
}
|
|
static getAll() {
|
|
return Array.from(this.registry).map(entry => {
|
|
// Split only on the FIRST colon (METHOD:PATH)
|
|
// Don't split on colons in path parameters like :identifier
|
|
const colonIndex = entry.indexOf(':');
|
|
const method = entry.substring(0, colonIndex);
|
|
const path = entry.substring(colonIndex + 1);
|
|
return { path, method };
|
|
});
|
|
}
|
|
static isPublic(path, method) {
|
|
const methodUpper = method.toUpperCase();
|
|
for (const registered of this.registry) {
|
|
// Split only on the FIRST colon (METHOD:PATH)
|
|
// Don't split on colons in path parameters like :identifier
|
|
const colonIndex = registered.indexOf(':');
|
|
const regMethod = registered.substring(0, colonIndex);
|
|
const regPath = registered.substring(colonIndex + 1);
|
|
if (regMethod !== methodUpper)
|
|
continue;
|
|
// Check if path matches pattern
|
|
if (this.matchesRoute(path, regPath)) {
|
|
return true;
|
|
}
|
|
}
|
|
// Debug: log when a route is not found as public
|
|
// console.log(`[PublicEndpointRegistry] Route not found as public: ${methodUpper} ${path}`);
|
|
// console.log(`[PublicEndpointRegistry] Registered routes:`, Array.from(this.registry));
|
|
return false;
|
|
}
|
|
static matchesRoute(path, pattern) {
|
|
// Convert pattern to regex
|
|
// Handle both :param (Express/Hono style) and {param} (OpenAPI style)
|
|
// e.g., '/api/competitors/:place_id' or '/api/competitors/{place_id}' -> /^\/api\/competitors\/[^\/]+$/
|
|
const regexPattern = pattern
|
|
.replace(/:[^\/]+/g, '[^/]+') // Replace :param with regex
|
|
.replace(/\{[^}]+\}/g, '[^/]+') // Replace {param} with regex
|
|
.replace(/\//g, '\\/');
|
|
// Allow optional trailing slash
|
|
const regex = new RegExp(`^${regexPattern}\\/?$`);
|
|
return regex.test(path);
|
|
}
|
|
}
|
|
/**
|
|
* Registry for admin-only endpoints
|
|
*/
|
|
export class AdminEndpointRegistry {
|
|
static registry = new Set();
|
|
static register(path, method) {
|
|
this.registry.add(`${method.toUpperCase()}:${path}`);
|
|
}
|
|
static isAdmin(path, method) {
|
|
const methodUpper = method.toUpperCase();
|
|
for (const registered of this.registry) {
|
|
const [regMethod, regPath] = registered.split(':');
|
|
if (regMethod !== methodUpper)
|
|
continue;
|
|
// Check if path matches pattern
|
|
if (this.matchesRoute(path, regPath)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
static matchesRoute(path, pattern) {
|
|
// Convert pattern to regex
|
|
// Handle both :param (Express/Hono style) and {param} (OpenAPI style)
|
|
// e.g., '/api/competitors/:place_id' or '/api/competitors/{place_id}' -> /^\/api\/competitors\/[^\/]+$/
|
|
const regexPattern = pattern
|
|
.replace(/:[^\\/]+/g, '[^/]+') // Replace :param with regex
|
|
.replace(/\{[^}]+\}/g, '[^/]+') // Replace {param} with regex
|
|
.replace(/\//g, '\\/');
|
|
const regex = new RegExp(`^${regexPattern}\\/?$`);
|
|
return regex.test(path);
|
|
}
|
|
}
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVnaXN0cnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29tbW9ucy9yZWdpc3RyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQXVCLGVBQWUsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBTzdFOzs7R0FHRztBQUNILE1BQU0sT0FBTyxnQkFBZ0I7SUFDakIsTUFBTSxDQUFDLFFBQVEsR0FBRyxJQUFJLEdBQUcsRUFBa0MsQ0FBQztJQUM1RCxNQUFNLENBQUMsV0FBVyxHQUFHLEtBQUssQ0FBQztJQUVuQzs7T0FFRztJQUNILE1BQU0sQ0FBQyxVQUFVO1FBQ2IsSUFBSSxJQUFJLENBQUMsV0FBVztZQUFFLE9BQU87UUFFN0IsOEJBQThCO1FBQzlCLEtBQUssTUFBTSxDQUFDLFNBQVMsRUFBRSxPQUFPLENBQUMsSUFBSSxNQUFNLENBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxFQUFFLENBQUM7WUFDakUsS0FBSyxNQUFNLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztnQkFDdkQsSUFBSSxDQUFDLFFBQVEsQ0FBQztvQkFDVixTQUFTO29CQUNULFFBQVE7b0JBQ1IsR0FBRyxNQUFNO2lCQUNaLENBQUMsQ0FBQztZQUNQLENBQUM7UUFDTCxDQUFDO1FBRUQsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUM7SUFDNUIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsTUFBTSxDQUFDLFFBQVEsQ0FBQyxNQUE4QjtRQUMxQyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQzNELElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENB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
|