refactor: simplify pricing tables to USD-only
- 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>
This commit is contained in:
250
ANVIL_IMPLEMENTATION.md
Normal file
250
ANVIL_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Anvil Product Tables Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
Implemented complete database schema and repositories for Anvil-branded cloud products, including regions, instances, pricing, and transfer pricing.
|
||||
|
||||
## Implementation Summary
|
||||
|
||||
### 1. Database Migration (004_anvil_tables.sql)
|
||||
|
||||
Created 4 new tables with proper indexes, triggers, and foreign key constraints:
|
||||
|
||||
#### anvil_regions
|
||||
- Maps Anvil-branded regions to source provider regions
|
||||
- Contains 6 initial regions (Tokyo 1-3, Osaka 1-2, Seoul 1)
|
||||
- Links to existing `regions` table via `source_region_id`
|
||||
|
||||
**Columns:**
|
||||
- `id`, `name` (unique), `display_name`, `country_code`
|
||||
- `source_provider`, `source_region_code`, `source_region_id` (FK)
|
||||
- `active`, `created_at`, `updated_at`
|
||||
|
||||
**Initial Data:**
|
||||
```
|
||||
anvil-tyo1 → Linode ap-northeast (region_id: 26)
|
||||
anvil-tyo2 → Linode jp-tyo-3 (region_id: 3)
|
||||
anvil-tyo3 → Vultr nrt (region_id: 323)
|
||||
anvil-osa1 → Linode jp-osa (region_id: 13)
|
||||
anvil-osa2 → Vultr itm (region_id: 314)
|
||||
anvil-sel1 → Vultr icn (region_id: 313)
|
||||
```
|
||||
|
||||
#### anvil_instances
|
||||
- Defines Anvil product specifications
|
||||
- Supports categories: vm, gpu, g8, vpu
|
||||
- GPU-specific fields: `gpu_model`, `gpu_vram_gb`
|
||||
|
||||
**Columns:**
|
||||
- `id`, `name` (unique), `display_name`, `category`
|
||||
- `vcpus`, `memory_gb`, `disk_gb`
|
||||
- `transfer_tb`, `network_gbps`
|
||||
- `gpu_model`, `gpu_vram_gb` (nullable)
|
||||
- `active`, `created_at`, `updated_at`
|
||||
|
||||
#### anvil_pricing
|
||||
- Retail pricing with cost tracking
|
||||
- Auto-calculates KRW prices using exchange rates
|
||||
- Links to both Anvil instances/regions and source instances
|
||||
|
||||
**Columns:**
|
||||
- `id`, `anvil_instance_id` (FK), `anvil_region_id` (FK)
|
||||
- `hourly_price`, `monthly_price` (retail USD)
|
||||
- `hourly_price_krw`, `monthly_price_krw` (retail KRW)
|
||||
- `cost_hourly`, `cost_monthly` (wholesale USD)
|
||||
- `source_instance_id` (optional FK to source tables)
|
||||
- `active`, `created_at`, `updated_at`
|
||||
- UNIQUE constraint on (anvil_instance_id, anvil_region_id)
|
||||
|
||||
#### anvil_transfer_pricing
|
||||
- Data transfer pricing per region
|
||||
- KRW conversion support
|
||||
|
||||
**Columns:**
|
||||
- `id`, `anvil_region_id` (FK)
|
||||
- `price_per_gb` (USD), `price_per_gb_krw` (KRW)
|
||||
- `included_tb` (reference only)
|
||||
- `active`, `created_at`, `updated_at`
|
||||
- UNIQUE constraint on anvil_region_id
|
||||
|
||||
### 2. TypeScript Types (src/types.ts)
|
||||
|
||||
Added 8 new type definitions:
|
||||
|
||||
```typescript
|
||||
AnvilRegion
|
||||
AnvilInstance
|
||||
AnvilPricing
|
||||
AnvilTransferPricing
|
||||
AnvilRegionInput
|
||||
AnvilInstanceInput
|
||||
AnvilPricingInput
|
||||
AnvilTransferPricingInput
|
||||
```
|
||||
|
||||
All Input types auto-derive from their base types, excluding `id`, `created_at`, and `updated_at`.
|
||||
|
||||
### 3. Repositories
|
||||
|
||||
Created 4 repository classes following existing BaseRepository pattern:
|
||||
|
||||
#### AnvilRegionsRepository
|
||||
- `findByName(name: string)` - Find by Anvil region name
|
||||
- `findByCountry(countryCode: string)` - Get all regions in a country
|
||||
- `findBySourceRegion(sourceRegionId: number)` - Reverse lookup
|
||||
- `findActive()` - Get all active regions
|
||||
- `updateActive(id: number, active: boolean)` - Toggle active status
|
||||
- `upsertMany(regions: AnvilRegionInput[])` - Bulk insert/update
|
||||
|
||||
#### AnvilInstancesRepository
|
||||
- `findByName(name: string)` - Find by instance name
|
||||
- `findByCategory(category)` - Filter by vm/gpu/g8/vpu
|
||||
- `findActive(category?)` - Get active instances with optional category filter
|
||||
- `searchByResources(minVcpus?, minMemoryGb?, minDiskGb?, category?)` - Resource-based search
|
||||
- `updateActive(id: number, active: boolean)` - Toggle active status
|
||||
- `upsertMany(instances: AnvilInstanceInput[])` - Bulk insert/update
|
||||
|
||||
#### AnvilPricingRepository
|
||||
- `findByInstance(anvilInstanceId: number)` - All pricing for an instance
|
||||
- `findByRegion(anvilRegionId: number)` - All pricing in a region
|
||||
- `findByInstanceAndRegion(instanceId, regionId)` - Specific pricing record
|
||||
- `findActive(instanceId?, regionId?)` - Active pricing with optional filters
|
||||
- `searchByPriceRange(minHourly?, maxHourly?, minMonthly?, maxMonthly?)` - Price range search
|
||||
- `updateActive(id: number, active: boolean)` - Toggle active status
|
||||
- `upsertMany(pricing: AnvilPricingInput[])` - Bulk insert/update with auto KRW calculation
|
||||
|
||||
#### AnvilTransferPricingRepository
|
||||
- `findByRegion(anvilRegionId: number)` - Get transfer pricing for a region
|
||||
- `findActive()` - Get all active transfer pricing
|
||||
- `updateActive(id: number, active: boolean)` - Toggle active status
|
||||
- `upsertMany(pricing: AnvilTransferPricingInput[])` - Bulk insert/update with KRW conversion
|
||||
|
||||
### 4. Repository Factory Updates
|
||||
|
||||
Updated `RepositoryFactory` class to include:
|
||||
- `anvilRegions: AnvilRegionsRepository`
|
||||
- `anvilInstances: AnvilInstancesRepository`
|
||||
- `anvilPricing: AnvilPricingRepository`
|
||||
- `anvilTransferPricing: AnvilTransferPricingRepository`
|
||||
|
||||
All repositories use lazy singleton pattern for efficient caching.
|
||||
|
||||
### 5. KRW Pricing Support
|
||||
|
||||
Pricing repositories automatically calculate KRW prices using environment variables:
|
||||
- `KRW_EXCHANGE_RATE` - Base USD to KRW conversion rate (default: 1450)
|
||||
- `KRW_VAT_RATE` - VAT multiplier (default: 1.1 for 10% VAT)
|
||||
- `KRW_MARKUP_RATE` - Markup multiplier (default: 1.1 for 10% markup)
|
||||
|
||||
KRW prices are auto-calculated during `upsertMany()` operations.
|
||||
|
||||
## Database Schema
|
||||
|
||||
```
|
||||
anvil_regions (1) ──< anvil_pricing (M)
|
||||
anvil_instances (1) ──< anvil_pricing (M)
|
||||
anvil_regions (1) ──< anvil_transfer_pricing (1)
|
||||
regions (1) ──< anvil_regions (M) [source mapping]
|
||||
```
|
||||
|
||||
## Deployment Status
|
||||
|
||||
✅ Migration 004 applied to remote database (2026-01-25)
|
||||
✅ 17 queries executed successfully
|
||||
✅ 6 initial regions inserted
|
||||
✅ All 4 tables created with indexes and triggers
|
||||
✅ TypeScript compilation successful
|
||||
✅ Deployed to production (Version: 5905e5df-7265-4872-b05e-e3fe4b7d0619)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Query Anvil Regions
|
||||
```typescript
|
||||
const repos = new RepositoryFactory(env.DB, env);
|
||||
|
||||
// Get all Japan regions
|
||||
const jpRegions = await repos.anvilRegions.findByCountry('JP');
|
||||
// Returns: anvil-tyo1, anvil-tyo2, anvil-tyo3, anvil-osa1, anvil-osa2
|
||||
|
||||
// Find specific region
|
||||
const tyo1 = await repos.anvilRegions.findByName('anvil-tyo1');
|
||||
```
|
||||
|
||||
### Create Anvil Instance
|
||||
```typescript
|
||||
await repos.anvilInstances.create({
|
||||
name: 'anvil-1g-1c',
|
||||
display_name: 'Basic 1GB',
|
||||
category: 'vm',
|
||||
vcpus: 1,
|
||||
memory_gb: 1,
|
||||
disk_gb: 25,
|
||||
transfer_tb: 1,
|
||||
network_gbps: 1,
|
||||
gpu_model: null,
|
||||
gpu_vram_gb: null,
|
||||
active: 1,
|
||||
});
|
||||
```
|
||||
|
||||
### Set Pricing (Auto KRW Calculation)
|
||||
```typescript
|
||||
await repos.anvilPricing.upsertMany([
|
||||
{
|
||||
anvil_instance_id: 1,
|
||||
anvil_region_id: 1,
|
||||
hourly_price: 0.015,
|
||||
monthly_price: 10,
|
||||
cost_hourly: 0.012,
|
||||
cost_monthly: 8,
|
||||
source_instance_id: 123,
|
||||
active: 1,
|
||||
}
|
||||
]);
|
||||
// KRW prices auto-calculated:
|
||||
// hourly_price_krw = 0.015 × 1450 × 1.1 × 1.1 ≈ 26.37
|
||||
// monthly_price_krw = 10 × 1450 × 1.1 × 1.1 ≈ 17,545
|
||||
```
|
||||
|
||||
### Search by Resources
|
||||
```typescript
|
||||
// Find instances with at least 2 vCPUs and 4GB RAM
|
||||
const instances = await repos.anvilInstances.searchByResources(
|
||||
2, // minVcpus
|
||||
4, // minMemoryGb
|
||||
null, // minDiskGb
|
||||
'vm' // category
|
||||
);
|
||||
```
|
||||
|
||||
## Files Changed
|
||||
|
||||
### Created
|
||||
- `/migrations/004_anvil_tables.sql` - Database schema
|
||||
- `/src/repositories/anvil-regions.ts` - Regions repository
|
||||
- `/src/repositories/anvil-instances.ts` - Instances repository
|
||||
- `/src/repositories/anvil-pricing.ts` - Pricing repository
|
||||
- `/src/repositories/anvil-transfer-pricing.ts` - Transfer pricing repository
|
||||
- `/src/repositories/anvil-regions.test.ts` - Unit tests
|
||||
|
||||
### Modified
|
||||
- `/src/types.ts` - Added 8 new types
|
||||
- `/src/repositories/index.ts` - Added 4 new repository exports and factory getters
|
||||
|
||||
## Next Steps
|
||||
|
||||
To complete the Anvil product implementation:
|
||||
|
||||
1. **Populate Instance Data**: Create Anvil instance definitions (VM, GPU, G8, VPU tiers)
|
||||
2. **Set Pricing**: Map Anvil instances to source providers and set retail pricing
|
||||
3. **API Endpoints**: Create endpoints for querying Anvil products
|
||||
4. **Sync Service**: Implement sync logic to update costs from source providers
|
||||
5. **Documentation**: Add API documentation for Anvil endpoints
|
||||
|
||||
## Testing
|
||||
|
||||
Basic repository tests created in `anvil-regions.test.ts`. Additional test coverage recommended for:
|
||||
- Instance search and filtering
|
||||
- Pricing calculations and KRW conversion
|
||||
- Transfer pricing queries
|
||||
- Edge cases and error handling
|
||||
Reference in New Issue
Block a user