Initial commit: Cloud Instances API
Multi-cloud VM instance database with Cloudflare Workers - Linode, Vultr, AWS connector integration - D1 database with regions, instances, pricing - Query API with filtering, caching, pagination - Cron-based auto-sync (daily + 6-hourly) - Health monitoring endpoint Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
238
src/repositories/instances.ts
Normal file
238
src/repositories/instances.ts
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Instance Types Repository
|
||||
* Handles CRUD operations for VM instance types
|
||||
*/
|
||||
|
||||
import { BaseRepository } from './base';
|
||||
import { InstanceType, InstanceTypeInput, InstanceFamily, RepositoryError, ErrorCodes } from '../types';
|
||||
|
||||
export class InstancesRepository extends BaseRepository<InstanceType> {
|
||||
protected tableName = 'instance_types';
|
||||
|
||||
/**
|
||||
* Find all instance types for a specific provider
|
||||
*/
|
||||
async findByProvider(providerId: number): Promise<InstanceType[]> {
|
||||
try {
|
||||
const result = await this.db
|
||||
.prepare('SELECT * FROM instance_types WHERE provider_id = ?')
|
||||
.bind(providerId)
|
||||
.all<InstanceType>();
|
||||
|
||||
return result.results;
|
||||
} catch (error) {
|
||||
console.error('[InstancesRepository] findByProvider failed:', error);
|
||||
throw new RepositoryError(
|
||||
`Failed to find instance types for provider: ${providerId}`,
|
||||
ErrorCodes.DATABASE_ERROR,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find instance types by family
|
||||
*/
|
||||
async findByFamily(family: InstanceFamily): Promise<InstanceType[]> {
|
||||
try {
|
||||
const result = await this.db
|
||||
.prepare('SELECT * FROM instance_types WHERE instance_family = ?')
|
||||
.bind(family)
|
||||
.all<InstanceType>();
|
||||
|
||||
return result.results;
|
||||
} catch (error) {
|
||||
console.error('[InstancesRepository] findByFamily failed:', error);
|
||||
throw new RepositoryError(
|
||||
`Failed to find instance types by family: ${family}`,
|
||||
ErrorCodes.DATABASE_ERROR,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an instance type by provider ID and instance ID
|
||||
*/
|
||||
async findByInstanceId(providerId: number, instanceId: string): Promise<InstanceType | null> {
|
||||
try {
|
||||
const result = await this.db
|
||||
.prepare('SELECT * FROM instance_types WHERE provider_id = ? AND instance_id = ?')
|
||||
.bind(providerId, instanceId)
|
||||
.first<InstanceType>();
|
||||
|
||||
return result || null;
|
||||
} catch (error) {
|
||||
console.error('[InstancesRepository] findByInstanceId failed:', error);
|
||||
throw new RepositoryError(
|
||||
`Failed to find instance type: ${instanceId}`,
|
||||
ErrorCodes.DATABASE_ERROR,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk upsert instance types for a provider
|
||||
* Uses batch operations for efficiency
|
||||
*/
|
||||
async upsertMany(providerId: number, instances: InstanceTypeInput[]): Promise<number> {
|
||||
if (instances.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
// Build upsert statements for each instance type
|
||||
const statements = instances.map((instance) => {
|
||||
return this.db.prepare(
|
||||
`INSERT INTO instance_types (
|
||||
provider_id, instance_id, instance_name, vcpu, memory_mb,
|
||||
storage_gb, transfer_tb, network_speed_gbps, gpu_count,
|
||||
gpu_type, instance_family, metadata
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(provider_id, instance_id)
|
||||
DO UPDATE SET
|
||||
instance_name = excluded.instance_name,
|
||||
vcpu = excluded.vcpu,
|
||||
memory_mb = excluded.memory_mb,
|
||||
storage_gb = excluded.storage_gb,
|
||||
transfer_tb = excluded.transfer_tb,
|
||||
network_speed_gbps = excluded.network_speed_gbps,
|
||||
gpu_count = excluded.gpu_count,
|
||||
gpu_type = excluded.gpu_type,
|
||||
instance_family = excluded.instance_family,
|
||||
metadata = excluded.metadata`
|
||||
).bind(
|
||||
providerId,
|
||||
instance.instance_id,
|
||||
instance.instance_name,
|
||||
instance.vcpu,
|
||||
instance.memory_mb,
|
||||
instance.storage_gb,
|
||||
instance.transfer_tb || null,
|
||||
instance.network_speed_gbps || null,
|
||||
instance.gpu_count,
|
||||
instance.gpu_type || null,
|
||||
instance.instance_family || null,
|
||||
instance.metadata || null
|
||||
);
|
||||
});
|
||||
|
||||
const results = await this.executeBatch(statements);
|
||||
|
||||
// Count successful operations
|
||||
const successCount = results.reduce(
|
||||
(sum, result) => sum + (result.meta.changes ?? 0),
|
||||
0
|
||||
);
|
||||
|
||||
console.log(`[InstancesRepository] Upserted ${successCount} instance types for provider ${providerId}`);
|
||||
return successCount;
|
||||
} catch (error) {
|
||||
console.error('[InstancesRepository] upsertMany failed:', error);
|
||||
throw new RepositoryError(
|
||||
`Failed to upsert instance types for provider: ${providerId}`,
|
||||
ErrorCodes.TRANSACTION_FAILED,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find GPU instances only
|
||||
*/
|
||||
async findGpuInstances(providerId?: number): Promise<InstanceType[]> {
|
||||
try {
|
||||
let query = 'SELECT * FROM instance_types WHERE gpu_count > 0';
|
||||
const params: any[] = [];
|
||||
|
||||
if (providerId !== undefined) {
|
||||
query += ' AND provider_id = ?';
|
||||
params.push(providerId);
|
||||
}
|
||||
|
||||
const result = await this.db
|
||||
.prepare(query)
|
||||
.bind(...params)
|
||||
.all<InstanceType>();
|
||||
|
||||
return result.results;
|
||||
} catch (error) {
|
||||
console.error('[InstancesRepository] findGpuInstances failed:', error);
|
||||
throw new RepositoryError(
|
||||
'Failed to find GPU instances',
|
||||
ErrorCodes.DATABASE_ERROR,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search instances by specifications
|
||||
*/
|
||||
async search(criteria: {
|
||||
providerId?: number;
|
||||
minVcpu?: number;
|
||||
maxVcpu?: number;
|
||||
minMemoryMb?: number;
|
||||
maxMemoryMb?: number;
|
||||
family?: InstanceFamily;
|
||||
hasGpu?: boolean;
|
||||
}): Promise<InstanceType[]> {
|
||||
try {
|
||||
const conditions: string[] = [];
|
||||
const params: any[] = [];
|
||||
|
||||
if (criteria.providerId !== undefined) {
|
||||
conditions.push('provider_id = ?');
|
||||
params.push(criteria.providerId);
|
||||
}
|
||||
|
||||
if (criteria.minVcpu !== undefined) {
|
||||
conditions.push('vcpu >= ?');
|
||||
params.push(criteria.minVcpu);
|
||||
}
|
||||
|
||||
if (criteria.maxVcpu !== undefined) {
|
||||
conditions.push('vcpu <= ?');
|
||||
params.push(criteria.maxVcpu);
|
||||
}
|
||||
|
||||
if (criteria.minMemoryMb !== undefined) {
|
||||
conditions.push('memory_mb >= ?');
|
||||
params.push(criteria.minMemoryMb);
|
||||
}
|
||||
|
||||
if (criteria.maxMemoryMb !== undefined) {
|
||||
conditions.push('memory_mb <= ?');
|
||||
params.push(criteria.maxMemoryMb);
|
||||
}
|
||||
|
||||
if (criteria.family !== undefined) {
|
||||
conditions.push('instance_family = ?');
|
||||
params.push(criteria.family);
|
||||
}
|
||||
|
||||
if (criteria.hasGpu !== undefined) {
|
||||
conditions.push(criteria.hasGpu ? 'gpu_count > 0' : 'gpu_count = 0');
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? ' WHERE ' + conditions.join(' AND ') : '';
|
||||
const query = 'SELECT * FROM instance_types' + whereClause;
|
||||
|
||||
const result = await this.db
|
||||
.prepare(query)
|
||||
.bind(...params)
|
||||
.all<InstanceType>();
|
||||
|
||||
return result.results;
|
||||
} catch (error) {
|
||||
console.error('[InstancesRepository] search failed:', error);
|
||||
throw new RepositoryError(
|
||||
'Failed to search instance types',
|
||||
ErrorCodes.DATABASE_ERROR,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user