Files
cloud-server/ANVIL_IMPLEMENTATION.md
kappa 9f3d3a245a 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>
2026-01-25 21:16:25 +09:00

251 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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