2e7860637e
- Restructured server layer with 5 domains: shared, screener, portfolio, calls, finance - Migrated 58 TypeScript files to domain-driven structure - Updated CLAUDE.md with new architecture documentation - Added .gitignore rules for .md files (except CLAUDE.md) - Removed unused CatalystAnalyst import from app.ts - Fixed lint errors: removed unused imports, fixed regex escape, added console suppressions - Verified no sensitive data in git history - Server code compiles cleanly with TypeScript strict mode
143 lines
3.8 KiB
TypeScript
143 lines
3.8 KiB
TypeScript
/**
|
|
* Sanitize a ticker symbol.
|
|
* - Converts to uppercase
|
|
* - Trims whitespace
|
|
* - Validates non-empty
|
|
*
|
|
* @param ticker The ticker symbol (e.g. "aapl", " MSFT ", "BRK.B")
|
|
* @returns Normalized ticker (e.g. "AAPL", "MSFT", "BRK.B")
|
|
* @throws Error if ticker is empty or invalid
|
|
*/
|
|
export function sanitizeTicker(ticker: string): string {
|
|
if (!ticker || typeof ticker !== 'string') {
|
|
throw new Error('Invalid ticker: must be a non-empty string');
|
|
}
|
|
|
|
const normalized = ticker.trim().toUpperCase();
|
|
|
|
if (!normalized) {
|
|
throw new Error('Invalid ticker: cannot be empty or whitespace');
|
|
}
|
|
|
|
// Optional: validate ticker format (alphanumeric + dots/hyphens)
|
|
if (!/^[A-Z0-9-.]+$/.test(normalized)) {
|
|
throw new Error(`Invalid ticker format: ${normalized}`);
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
/**
|
|
* Sanitize an array of tickers.
|
|
*
|
|
* @param tickers Array of ticker symbols
|
|
* @returns Array of normalized tickers
|
|
* @throws Error if any ticker is invalid
|
|
*/
|
|
export function sanitizeTickers(tickers: unknown): string[] {
|
|
if (!Array.isArray(tickers)) {
|
|
throw new Error('Invalid tickers: must be an array');
|
|
}
|
|
|
|
if (tickers.length === 0) {
|
|
throw new Error('Invalid tickers: array cannot be empty');
|
|
}
|
|
|
|
return tickers.map((t) => {
|
|
if (typeof t !== 'string') {
|
|
throw new Error(`Invalid ticker in array: ${t} (expected string)`);
|
|
}
|
|
return sanitizeTicker(t);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Sanitize a string field.
|
|
* - Trims whitespace
|
|
* - Validates non-empty
|
|
* - Optional: enforces max length
|
|
*
|
|
* @param value The string value
|
|
* @param fieldName Name of the field (for error messages)
|
|
* @param maxLength Maximum allowed length (optional)
|
|
* @returns Trimmed string
|
|
* @throws Error if value is invalid
|
|
*/
|
|
export function sanitizeString(value: unknown, fieldName: string, maxLength?: number): string {
|
|
if (typeof value !== 'string') {
|
|
throw new Error(`Invalid ${fieldName}: must be a string`);
|
|
}
|
|
|
|
const trimmed = value.trim();
|
|
|
|
if (!trimmed) {
|
|
throw new Error(`Invalid ${fieldName}: cannot be empty or whitespace`);
|
|
}
|
|
|
|
if (maxLength && trimmed.length > maxLength) {
|
|
throw new Error(`Invalid ${fieldName}: exceeds max length of ${maxLength} characters`);
|
|
}
|
|
|
|
return trimmed;
|
|
}
|
|
|
|
/**
|
|
* Sanitize a number field.
|
|
* - Validates it's a number
|
|
* - Optional: enforces min/max bounds
|
|
*
|
|
* @param value The numeric value
|
|
* @param fieldName Name of the field (for error messages)
|
|
* @param min Minimum allowed value (optional)
|
|
* @param max Maximum allowed value (optional)
|
|
* @returns The validated number
|
|
* @throws Error if value is invalid
|
|
*/
|
|
export function sanitizeNumber(
|
|
value: unknown,
|
|
fieldName: string,
|
|
options?: { min?: number; max?: number },
|
|
): number {
|
|
const num = typeof value === 'number' ? value : Number(value);
|
|
|
|
if (isNaN(num)) {
|
|
throw new Error(`Invalid ${fieldName}: must be a valid number`);
|
|
}
|
|
|
|
if (options?.min !== undefined && num < options.min) {
|
|
throw new Error(`Invalid ${fieldName}: must be at least ${options.min}`);
|
|
}
|
|
|
|
if (options?.max !== undefined && num > options.max) {
|
|
throw new Error(`Invalid ${fieldName}: must be at most ${options.max}`);
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
/**
|
|
* Sanitize an ISO date string.
|
|
* - Validates it's a valid ISO date
|
|
* - Converts to string format YYYY-MM-DD
|
|
*
|
|
* @param value The date value (ISO string or Date)
|
|
* @param fieldName Name of the field (for error messages)
|
|
* @returns Date as YYYY-MM-DD string
|
|
* @throws Error if date is invalid
|
|
*/
|
|
export function sanitizeDate(value: unknown, fieldName: string): string {
|
|
let date: Date | null = null;
|
|
|
|
if (typeof value === 'string') {
|
|
date = new Date(value);
|
|
} else if (value instanceof Date) {
|
|
date = value;
|
|
}
|
|
|
|
if (!date || isNaN(date.getTime())) {
|
|
throw new Error(`Invalid ${fieldName}: must be a valid date`);
|
|
}
|
|
|
|
return date.toISOString().slice(0, 10); // YYYY-MM-DD
|
|
}
|