- Remove KRW pricing calculations from all pricing tables - Simplify pricing table to store only wholesale USD prices - Simplify anvil_pricing to store only retail USD prices - Remove KRW environment variables (KRW_EXCHANGE_RATE, KRW_MARGIN_RATE) - Remove KRW functions from constants.ts - Update GPU/G8/VPU pricing repositories to match - Add Anvil tables and repositories for branded product support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
225 lines
6.3 KiB
TypeScript
225 lines
6.3 KiB
TypeScript
/**
|
|
* 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<GpuPricing> {
|
|
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<GpuPricing[]> {
|
|
try {
|
|
const result = await this.db
|
|
.prepare('SELECT * FROM gpu_pricing WHERE gpu_instance_id = ?')
|
|
.bind(gpuInstanceId)
|
|
.all<GpuPricing>();
|
|
|
|
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<GpuPricing[]> {
|
|
try {
|
|
const result = await this.db
|
|
.prepare('SELECT * FROM gpu_pricing WHERE region_id = ?')
|
|
.bind(regionId)
|
|
.all<GpuPricing>();
|
|
|
|
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<GpuPricing | null> {
|
|
try {
|
|
const result = await this.db
|
|
.prepare('SELECT * FROM gpu_pricing WHERE gpu_instance_id = ? AND region_id = ?')
|
|
.bind(gpuInstanceId, regionId)
|
|
.first<GpuPricing>();
|
|
|
|
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<number> {
|
|
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<GpuPricing[]> {
|
|
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<GpuPricing>();
|
|
|
|
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
|
|
);
|
|
}
|
|
}
|
|
}
|