/** * GPU Pricing Repository * Handles CRUD operations for GPU instance pricing data */ import { BaseRepository } from './base'; import { GpuPricing, GpuPricingInput, RepositoryError, ErrorCodes } from '../types'; import { createLogger } from '../utils/logger'; import { calculateRetailHourly, calculateRetailMonthly } from '../constants'; export class GpuPricingRepository extends BaseRepository { protected tableName = 'gpu_pricing'; protected logger = createLogger('[GpuPricingRepository]'); protected allowedColumns = [ 'gpu_instance_id', 'region_id', 'hourly_price', 'monthly_price', 'hourly_price_retail', 'monthly_price_retail', 'currency', 'available', ]; constructor(db: D1Database) { super(db); } /** * Find pricing for a specific GPU instance */ async findByGpuInstance(gpuInstanceId: number): Promise { try { const result = await this.db .prepare('SELECT * FROM gpu_pricing WHERE gpu_instance_id = ?') .bind(gpuInstanceId) .all(); return result.results; } catch (error) { this.logger.error('findByGpuInstance failed', { gpuInstanceId, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( `Failed to find pricing for GPU instance: ${gpuInstanceId}`, ErrorCodes.DATABASE_ERROR, error ); } } /** * Find pricing for a specific region */ async findByRegion(regionId: number): Promise { try { const result = await this.db .prepare('SELECT * FROM gpu_pricing WHERE region_id = ?') .bind(regionId) .all(); return result.results; } catch (error) { this.logger.error('findByRegion failed', { regionId, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( `Failed to find GPU pricing for region: ${regionId}`, ErrorCodes.DATABASE_ERROR, error ); } } /** * Find specific pricing record by GPU instance and region */ async findByGpuInstanceAndRegion( gpuInstanceId: number, regionId: number ): Promise { try { const result = await this.db .prepare('SELECT * FROM gpu_pricing WHERE gpu_instance_id = ? AND region_id = ?') .bind(gpuInstanceId, regionId) .first(); return result || null; } catch (error) { this.logger.error('findByGpuInstanceAndRegion failed', { gpuInstanceId, regionId, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( `Failed to find GPU pricing for instance ${gpuInstanceId} in region ${regionId}`, ErrorCodes.DATABASE_ERROR, error ); } } /** * Bulk upsert GPU pricing records * Uses batch operations for efficiency */ async upsertMany(pricingData: GpuPricingInput[]): Promise { if (pricingData.length === 0) { return 0; } try { const statements = pricingData.map((pricing) => { const hourlyRetail = calculateRetailHourly(pricing.hourly_price); const monthlyRetail = calculateRetailMonthly(pricing.monthly_price); return this.db.prepare( `INSERT INTO gpu_pricing ( gpu_instance_id, region_id, hourly_price, monthly_price, hourly_price_retail, monthly_price_retail, currency, available ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(gpu_instance_id, region_id) DO UPDATE SET hourly_price = excluded.hourly_price, monthly_price = excluded.monthly_price, hourly_price_retail = excluded.hourly_price_retail, monthly_price_retail = excluded.monthly_price_retail, currency = excluded.currency, available = excluded.available` ).bind( pricing.gpu_instance_id, pricing.region_id, pricing.hourly_price, pricing.monthly_price, hourlyRetail, monthlyRetail, pricing.currency, pricing.available ); }); const results = await this.executeBatch(statements); const successCount = results.reduce( (sum, result) => sum + (result.meta.changes ?? 0), 0 ); this.logger.info('Upserted GPU pricing records', { count: successCount }); return successCount; } catch (error) { this.logger.error('upsertMany failed', { count: pricingData.length, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( 'Failed to upsert GPU pricing records', ErrorCodes.TRANSACTION_FAILED, error ); } } /** * Search GPU pricing by price range */ async searchByPriceRange( minHourly?: number, maxHourly?: number, minMonthly?: number, maxMonthly?: number ): Promise { try { const conditions: string[] = []; const params: (string | number | boolean | null)[] = []; if (minHourly !== undefined) { conditions.push('hourly_price >= ?'); params.push(minHourly); } if (maxHourly !== undefined) { conditions.push('hourly_price <= ?'); params.push(maxHourly); } if (minMonthly !== undefined) { conditions.push('monthly_price >= ?'); params.push(minMonthly); } if (maxMonthly !== undefined) { conditions.push('monthly_price <= ?'); params.push(maxMonthly); } const whereClause = conditions.length > 0 ? ' WHERE ' + conditions.join(' AND ') : ''; const query = 'SELECT * FROM gpu_pricing' + whereClause + ' ORDER BY hourly_price'; const result = await this.db .prepare(query) .bind(...params) .all(); return result.results; } catch (error) { this.logger.error('searchByPriceRange failed', { minHourly, maxHourly, minMonthly, maxMonthly, error: error instanceof Error ? error.message : 'Unknown error' }); throw new RepositoryError( 'Failed to search GPU pricing by price range', ErrorCodes.DATABASE_ERROR, error ); } } }