## Security Improvements - Fix timing attack in verifyApiKey with fixed 256-byte buffer - Fix sortOrder SQL injection with whitelist validation - Fix rate limiting bypass for non-Cloudflare traffic (fail-closed) - Remove stack trace exposure in error responses - Add request_id for audit trail (X-Request-ID header) - Sanitize origin header to prevent log injection - Add content-length validation for /sync endpoint (10KB limit) - Replace Math.random() with crypto.randomUUID() for sync IDs - Expand sensitive data masking patterns (8 → 18) ## Performance Improvements - Reduce rate limiter KV reads from 3 to 1 per request (66% reduction) - Increase sync batch size from 100 to 500 (80% fewer batches) - Fix health check N+1 query with efficient JOINs - Fix COUNT(*) Cartesian product with COUNT(DISTINCT) - Implement shared logger cache pattern across repositories - Add CacheService singleton pattern in recommend.ts - Add composite index for recommendation queries - Implement Anvil pricing query batching (100 per chunk) ## QA Improvements - Add BATCH_SIZE bounds validation (1-1000) - Add pagination bounds (page >= 1, MAX_OFFSET = 100000) - Add min/max range consistency validation - Add DB reference validation for singleton services - Add type guards for database result validation - Add timeout mechanism for external API calls (10-60s) - Use SUPPORTED_PROVIDERS constant instead of hardcoded list ## Removed - Remove Vault integration (using Wrangler secrets) - Remove 6-hour pricing cron (daily sync only) ## Configuration - Add idx_instance_types_specs_filter composite index - Add CORS Access-Control-Expose-Headers Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
143 lines
3.4 KiB
TypeScript
143 lines
3.4 KiB
TypeScript
/**
|
|
* Providers Repository
|
|
* Handles CRUD operations for cloud providers
|
|
*/
|
|
|
|
import { BaseRepository } from './base';
|
|
import { Provider, ProviderInput, RepositoryError, ErrorCodes } from '../types';
|
|
|
|
export class ProvidersRepository extends BaseRepository<Provider> {
|
|
protected tableName = 'providers';
|
|
protected allowedColumns = [
|
|
'name',
|
|
'display_name',
|
|
'api_base_url',
|
|
'last_sync_at',
|
|
'sync_status',
|
|
'sync_error',
|
|
];
|
|
|
|
/**
|
|
* Find provider by name
|
|
*/
|
|
async findByName(name: string): Promise<Provider | null> {
|
|
try {
|
|
const result = await this.db
|
|
.prepare('SELECT * FROM providers WHERE name = ?')
|
|
.bind(name)
|
|
.first<Provider>();
|
|
|
|
return result || null;
|
|
} catch (error) {
|
|
this.logger.error('findByName failed', {
|
|
name,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
});
|
|
throw new RepositoryError(
|
|
`Failed to find provider by name: ${name}`,
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update sync status for a provider
|
|
*/
|
|
async updateSyncStatus(
|
|
name: string,
|
|
status: 'pending' | 'syncing' | 'success' | 'error',
|
|
error?: string
|
|
): Promise<Provider> {
|
|
try {
|
|
const now = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
|
|
const result = await this.db
|
|
.prepare(
|
|
`UPDATE providers
|
|
SET sync_status = ?,
|
|
sync_error = ?,
|
|
last_sync_at = ?
|
|
WHERE name = ?
|
|
RETURNING *`
|
|
)
|
|
.bind(status, error || null, now, name)
|
|
.first<Provider>();
|
|
|
|
if (!result) {
|
|
throw new RepositoryError(
|
|
`Provider not found: ${name}`,
|
|
ErrorCodes.NOT_FOUND
|
|
);
|
|
}
|
|
|
|
return result;
|
|
} catch (error) {
|
|
this.logger.error('updateSyncStatus failed', {
|
|
name,
|
|
status,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
});
|
|
|
|
if (error instanceof RepositoryError) {
|
|
throw error;
|
|
}
|
|
|
|
throw new RepositoryError(
|
|
`Failed to update sync status for provider: ${name}`,
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all providers with specific sync status
|
|
*/
|
|
async findByStatus(status: Provider['sync_status']): Promise<Provider[]> {
|
|
try {
|
|
const result = await this.db
|
|
.prepare('SELECT * FROM providers WHERE sync_status = ?')
|
|
.bind(status)
|
|
.all<Provider>();
|
|
|
|
return result.results;
|
|
} catch (error) {
|
|
this.logger.error('findByStatus failed', {
|
|
status,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
});
|
|
throw new RepositoryError(
|
|
`Failed to find providers by status: ${status}`,
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create or update a provider
|
|
*/
|
|
async upsert(data: ProviderInput): Promise<Provider> {
|
|
try {
|
|
const existing = await this.findByName(data.name);
|
|
|
|
if (existing) {
|
|
return await this.update(existing.id, data);
|
|
}
|
|
|
|
return await this.create(data);
|
|
} catch (error) {
|
|
this.logger.error('upsert failed', {
|
|
name: data.name,
|
|
error: error instanceof Error ? error.message : 'Unknown error'
|
|
});
|
|
throw new RepositoryError(
|
|
`Failed to upsert provider: ${data.name}`,
|
|
ErrorCodes.DATABASE_ERROR,
|
|
error
|
|
);
|
|
}
|
|
}
|
|
}
|