init projectmycelium
This commit is contained in:
222
docs/dev/design/archive/vision/parts/architectural-overview.md
Normal file
222
docs/dev/design/archive/vision/parts/architectural-overview.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Project Mycelium - Architectural Overview
|
||||
|
||||
## Single Source of Truth for AI-Assisted Development
|
||||
|
||||
This document serves as the comprehensive architectural reference for the Project Mycelium, designed specifically to provide AI coders and developers with a unified understanding of the system's design principles, patterns, and implementation standards.
|
||||
|
||||
## Core Design Principles
|
||||
|
||||
### 1. **Log-Free Codebase**
|
||||
- **Policy**: Zero `log::` statements in main/development branches
|
||||
- **Rationale**: Clean code, production readiness, AI-friendly development
|
||||
- **Implementation**: 881+ log statements removed codebase-wide (2025-01-06)
|
||||
- **Reference**: [Log-Free Codebase Policy](./log-free-codebase-policy.md)
|
||||
|
||||
### 2. **Builder Pattern Architecture**
|
||||
- **Centralized Construction**: All complex objects use builder patterns
|
||||
- **Single Source of Truth**: Eliminates duplicate initialization code
|
||||
- **Key Builders**:
|
||||
- `SessionDataBuilder` (UserPersistentData)
|
||||
- `ConfigurationBuilder` (Environment variables)
|
||||
- `ResponseBuilder` (HTTP responses)
|
||||
- `ServiceFactory` (Service instantiation)
|
||||
- **Reference**: [Builder Pattern Architecture](./builder-pattern-architecture.md)
|
||||
|
||||
### 3. **Persistent Data Only**
|
||||
- **No Mock Data**: All MockDataService usage eliminated
|
||||
- **User Data Directory**: Single source of truth for user data
|
||||
- **Persistent Storage**: All data operations use `user_data/` directory
|
||||
- **Migration Complete**: 100% mock data removal achieved
|
||||
|
||||
### 4. **Centralized Response Handling**
|
||||
- **ResponseBuilder**: All HTTP responses use centralized builder
|
||||
- **Migration Status**: 78+ patterns migrated across 4 controllers
|
||||
- **Completed Controllers**: Auth (10), Wallet (49), Product (7), Currency (12)
|
||||
- **Reference**: [ResponseBuilder Migration Guide](./response-builder-migration.md)
|
||||
|
||||
## System Architecture
|
||||
|
||||
### Controllers Layer
|
||||
```
|
||||
src/controllers/
|
||||
├── auth.rs ✅ ResponseBuilder migrated (10 patterns)
|
||||
├── wallet.rs ✅ ResponseBuilder migrated (49 patterns)
|
||||
├── product.rs ✅ ResponseBuilder migrated (7 patterns)
|
||||
├── currency.rs ✅ ResponseBuilder migrated (12 patterns)
|
||||
├── dashboard.rs 🔄 In progress (50+ patterns, log-free)
|
||||
├── marketplace.rs ⏳ Pending migration
|
||||
├── order.rs ⏳ Deferred (complex structure)
|
||||
└── rental.rs ⏳ Pending migration
|
||||
```
|
||||
|
||||
### Services Layer
|
||||
```
|
||||
src/services/
|
||||
├── farmer.rs ✅ ServiceFactory migrated
|
||||
├── currency.rs ✅ ServiceFactory migrated
|
||||
├── user_persistence.rs ✅ ServiceFactory migrated
|
||||
├── session_manager.rs ✅ ServiceFactory migrated
|
||||
├── node_rental.rs ✅ ServiceFactory migrated
|
||||
├── slice_rental.rs ✅ ServiceFactory migrated
|
||||
└── order.rs ✅ ServiceFactory migrated
|
||||
```
|
||||
|
||||
### Utilities Layer
|
||||
```
|
||||
src/utils/
|
||||
├── response_builder.rs ✅ Centralized HTTP response handling
|
||||
├── configuration.rs ✅ ConfigurationBuilder implementation
|
||||
└── data_cleanup.rs ✅ Log-free data utilities
|
||||
```
|
||||
|
||||
### Models Layer
|
||||
```
|
||||
src/models/
|
||||
└── builders.rs ✅ SessionDataBuilder and other builders
|
||||
```
|
||||
|
||||
## Migration Progress
|
||||
|
||||
### Completed Migrations ✅
|
||||
1. **ServiceFactory Migration**: 100% complete
|
||||
- All service instantiations use centralized factory
|
||||
- Mock data completely eliminated
|
||||
- Persistent data access throughout
|
||||
|
||||
2. **ConfigurationBuilder Migration**: 100% complete
|
||||
- All environment variable access centralized
|
||||
- Home and Auth controllers fully migrated
|
||||
- Consistent configuration handling
|
||||
|
||||
3. **SessionDataBuilder Migration**: 100% complete
|
||||
- All UserPersistentData initialization uses builder
|
||||
- Single source of truth for user data construction
|
||||
- Consistent field handling with `..Default::default()`
|
||||
|
||||
4. **Log-Free Codebase**: 100% complete
|
||||
- 881+ log statements removed codebase-wide
|
||||
- Clean compilation with zero errors
|
||||
- Production-ready code quality
|
||||
|
||||
5. **ResponseBuilder Migration**: 78 patterns complete
|
||||
- Auth Controller: 10/10 patterns ✅
|
||||
- Wallet Controller: 49/49 patterns ✅
|
||||
- Product Controller: 7/7 patterns ✅
|
||||
- Currency Controller: 12/12 patterns ✅
|
||||
|
||||
### In Progress 🔄
|
||||
1. **Dashboard Controller ResponseBuilder Migration**
|
||||
- 50+ HttpResponse patterns identified
|
||||
- Log cleanup completed (443 statements removed)
|
||||
- Ready for systematic migration
|
||||
|
||||
### Pending ⏳
|
||||
1. **Remaining Controllers**: marketplace, rental, pool, legal, docs, debug
|
||||
2. **Order Controller**: Deferred due to structural complexity
|
||||
3. **Final Documentation**: Consolidation and review
|
||||
|
||||
## Development Standards
|
||||
|
||||
### Code Quality
|
||||
- **Zero Compilation Errors**: All migrations maintain clean builds
|
||||
- **Minimal Warnings**: Only unused variable warnings acceptable
|
||||
- **Builder Pattern Usage**: Mandatory for complex object construction
|
||||
- **Persistent Data Only**: No mock data in production code
|
||||
|
||||
### Migration Approach
|
||||
- **Manual Systematic Migration**: Preferred over automated scripts
|
||||
- **Bulk All-at-Once Edits**: Efficient approach for simple controllers
|
||||
- **Section-by-Section**: For complex controllers (dashboard, order)
|
||||
- **Compilation Verification**: `cargo check` after each batch
|
||||
|
||||
### AI-Assisted Development
|
||||
- **Log-Free Code**: Simplified analysis and pattern recognition
|
||||
- **Centralized Patterns**: Consistent builder usage across codebase
|
||||
- **Single Source of Truth**: This document for architectural decisions
|
||||
- **Clean Abstractions**: Simplified code structure for AI tools
|
||||
|
||||
## Technical Specifications
|
||||
|
||||
### ResponseBuilder API
|
||||
```rust
|
||||
ResponseBuilder::ok()
|
||||
.json(data)
|
||||
.build()
|
||||
|
||||
ResponseBuilder::bad_request()
|
||||
.json(error_data)
|
||||
.build()
|
||||
|
||||
ResponseBuilder::unauthorized()
|
||||
.json(auth_error)
|
||||
.build()
|
||||
```
|
||||
|
||||
### ServiceFactory Usage
|
||||
```rust
|
||||
let service = ServiceFactory::create_farmer_service()
|
||||
.build()?;
|
||||
```
|
||||
|
||||
### ConfigurationBuilder Access
|
||||
```rust
|
||||
let config = ConfigurationBuilder::new()
|
||||
.jwt_secret()
|
||||
.gitea_client_id()
|
||||
.build();
|
||||
```
|
||||
|
||||
### SessionDataBuilder Pattern
|
||||
```rust
|
||||
let user_data = SessionDataBuilder::new_user(email);
|
||||
let existing_data = SessionDataBuilder::load_or_create(email);
|
||||
```
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
### Before Migration
|
||||
- 881+ log statements across 24 files
|
||||
- Multiple mock data services
|
||||
- Scattered configuration access
|
||||
- Direct HttpResponse usage
|
||||
|
||||
### After Migration
|
||||
- 0 log statements (100% reduction)
|
||||
- Single persistent data source
|
||||
- Centralized configuration management
|
||||
- Unified response handling
|
||||
- Clean compilation with minimal warnings
|
||||
|
||||
## Future Roadmap
|
||||
|
||||
### Phase 1: Complete ResponseBuilder Migration
|
||||
- Dashboard controller (in progress)
|
||||
- Marketplace controller
|
||||
- Rental controller
|
||||
- Remaining simple controllers
|
||||
|
||||
### Phase 2: Advanced Optimizations
|
||||
- Order controller migration (complex)
|
||||
- Performance optimizations
|
||||
- Additional builder patterns
|
||||
- Code consolidation opportunities
|
||||
|
||||
### Phase 3: Documentation and Maintenance
|
||||
- Complete architectural documentation
|
||||
- Developer onboarding guides
|
||||
- AI coding best practices
|
||||
- Maintenance procedures
|
||||
|
||||
## References
|
||||
|
||||
- [Log-Free Codebase Policy](./log-free-codebase-policy.md)
|
||||
- [Builder Pattern Architecture](./builder-pattern-architecture.md)
|
||||
- [ResponseBuilder Migration Guide](./response-builder-migration.md)
|
||||
- [Migration Progress Tracker](./builder-pattern-progress-tracker.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-01-06
|
||||
**Architecture Version**: 2.0
|
||||
**Status**: Active Development
|
||||
**Next Milestone**: Complete Dashboard Controller Migration
|
@@ -0,0 +1,353 @@
|
||||
# Project Mycelium - Builder Pattern Architecture 2025
|
||||
|
||||
**Document Purpose**: Comprehensive documentation of the industry-standard builder pattern architecture implemented across the Project Mycelium codebase for single-source-of-truth construction, maintainability, and maximum code reduction.
|
||||
|
||||
**Last Updated**: 2025-08-06
|
||||
**Status**: Production Implementation Complete
|
||||
**Achievement**: 95.8% compilation error reduction (24 → 0 errors)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Executive Summary
|
||||
|
||||
The Project Mycelium has successfully implemented a comprehensive **single-source-of-truth builder pattern architecture** that centralizes all struct construction, eliminates scattered manual initializations, and provides industry-standard maintainability. This architecture reduces code duplication by **~800 lines** while ensuring consistent, future-proof struct creation across the entire codebase.
|
||||
|
||||
**Key Achievements:**
|
||||
- ✅ **Zero Compilation Errors**: 24 → 0 errors resolved
|
||||
- ✅ **Single Source of Truth**: All `UserPersistentData` creation centralized
|
||||
- ✅ **Future-Proof**: New fields automatically included via `..Default::default()`
|
||||
- ✅ **Industry Standard**: Comprehensive builder pattern implementation
|
||||
- ✅ **Maintainable**: No scattered manual struct initializations
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
### Core Philosophy: Single-Source-of-Truth Construction
|
||||
|
||||
The builder pattern architecture follows these fundamental principles:
|
||||
|
||||
1. **Centralized Construction**: All complex structs use dedicated builders
|
||||
2. **Default Handling**: `..Default::default()` ensures new fields are automatically included
|
||||
3. **Validation**: Builders validate required fields and business logic
|
||||
4. **Fluent Interface**: Method chaining for readable, maintainable code
|
||||
5. **Template Methods**: Common patterns encapsulated in reusable templates
|
||||
|
||||
### Architecture Layers
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ APPLICATION LAYER │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Controllers │ Services │ Middleware │ Routes │
|
||||
│ - wallet.rs │ - farmer.rs│ - auth.rs │ - mod.rs │
|
||||
│ - order.rs │ - user_* │ - logging │ - api routes │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ BUILDER PATTERN LAYER │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ CENTRALIZED BUILDERS (models/builders.rs) │
|
||||
│ ┌─────────────────┬─────────────────┬─────────────────┐ │
|
||||
│ │ USER BUILDERS │ PRODUCT BUILDERS│ SERVICE BUILDERS│ │
|
||||
│ │ SessionDataBuilder│ ProductBuilder │ CurrencyServiceBuilder│
|
||||
│ │ UserBuilder │ OrderBuilder │ ProductServiceBuilder │
|
||||
│ │ AppDeploymentBuilder│ OrderItemBuilder│ OrderServiceBuilder│
|
||||
│ └─────────────────┴─────────────────┴─────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ MODEL LAYER │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Data Models │ Persistence │ Validation │ Serialization │
|
||||
│ - user.rs │ - JSON files│ - Required │ - Serde │
|
||||
│ - product.rs│ - Future DB │ - Business │ - Type Safety │
|
||||
│ - order.rs │ - Migration │ - Logic │ - Defaults │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Implementation Details
|
||||
|
||||
### 1. SessionDataBuilder - The Crown Jewel
|
||||
|
||||
The `SessionDataBuilder` is the most critical builder, handling all `UserPersistentData` construction:
|
||||
|
||||
```rust
|
||||
// Location: src/models/builders.rs
|
||||
impl SessionDataBuilder {
|
||||
/// Create new user with email - single source of truth
|
||||
pub fn new_user(email: &str) -> UserPersistentData {
|
||||
UserPersistentData {
|
||||
user_email: email.to_string(),
|
||||
auto_topup_settings: None,
|
||||
display_currency: Some("USD".to_string()),
|
||||
quick_topup_amounts: Some(vec![dec!(10), dec!(25), dec!(50), dec!(100)]),
|
||||
wallet_balance_usd: dec!(0),
|
||||
// All other fields via Default::default()
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Load existing or create new user - handles both cases
|
||||
pub fn load_or_create(email: &str) -> UserPersistentData {
|
||||
UserPersistence::load_user_data(email)
|
||||
.unwrap_or_else(|| Self::new_user(email))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Migration Success Stories
|
||||
|
||||
#### Before: Scattered Manual Initializations (24 compilation errors)
|
||||
```rust
|
||||
// ❌ OLD: Manual initialization in farmer.rs
|
||||
UserPersistentData {
|
||||
auto_topup_settings: None,
|
||||
display_currency: Some("USD".to_string()),
|
||||
quick_topup_amounts: Some(vec![dec!(10), dec!(25), dec!(50), dec!(100)]),
|
||||
user_email: user_email.to_string(),
|
||||
wallet_balance_usd: Decimal::ZERO,
|
||||
transactions: Vec::new(),
|
||||
// ... 30+ more fields manually specified
|
||||
// ❌ MISSING: orders field -> compilation error
|
||||
}
|
||||
```
|
||||
|
||||
#### After: Centralized Builder Pattern (0 compilation errors)
|
||||
```rust
|
||||
// ✅ NEW: Single line, all fields included automatically
|
||||
let mut persistent_data = SessionDataBuilder::load_or_create(user_email);
|
||||
```
|
||||
|
||||
### 3. Complete Migration Coverage
|
||||
|
||||
**✅ Fully Migrated Modules:**
|
||||
- **Wallet Controller**: 2 instances → `SessionDataBuilder::load_or_create()`
|
||||
- **Session Manager**: 1 instance → `SessionDataBuilder::new_user()`
|
||||
- **Node Rental**: 1 instance → `SessionDataBuilder::new_user()`
|
||||
- **Slice Rental**: 1 instance → `SessionDataBuilder::new_user()`
|
||||
- **Farmer Service**: 6+ instances → `SessionDataBuilder::load_or_create()` & `new_user()`
|
||||
- **User Persistence**: 12+ instances → `SessionDataBuilder::load_or_create()`
|
||||
|
||||
---
|
||||
|
||||
## 📊 Builder Pattern Catalog
|
||||
|
||||
### User Domain Builders
|
||||
|
||||
| Builder | Purpose | Key Methods | Usage Pattern |
|
||||
|---------|---------|-------------|---------------|
|
||||
| `SessionDataBuilder` | UserPersistentData creation | `new_user()`, `load_or_create()` | Primary user data |
|
||||
| `UserBuilder` | User model construction | `name()`, `email()`, `role()` | User profiles |
|
||||
| `AppDeploymentBuilder` | App deployment tracking | `app_id()`, `status()`, `health_score()` | Deployment management |
|
||||
| `PublishedAppBuilder` | Published app metadata | `category()`, `version()`, `deployments()` | App catalog |
|
||||
|
||||
### Product Domain Builders
|
||||
|
||||
| Builder | Purpose | Key Methods | Usage Pattern |
|
||||
|---------|---------|-------------|---------------|
|
||||
| `ProductBuilder` | Product catalog entries | `name()`, `base_price()`, `provider_id()` | Marketplace products |
|
||||
| `OrderBuilder` | Order processing | `user_id()`, `add_item()`, `payment_method()` | Purchase flow |
|
||||
| `OrderItemBuilder` | Order line items | `product_id()`, `quantity()`, `unit_price_base()` | Cart items |
|
||||
|
||||
### Service Domain Builders
|
||||
|
||||
| Builder | Purpose | Key Methods | Usage Pattern |
|
||||
|---------|---------|-------------|---------------|
|
||||
| `CurrencyServiceBuilder` | Currency service config | `base_currency()`, `cache_duration()` | Currency handling |
|
||||
| `ProductServiceBuilder` | Product service config | `currency_service()`, `cache_enabled()` | Product management |
|
||||
| `OrderServiceBuilder` | Order service config | `currency_service()`, `product_service()` | Order processing |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Further Consolidation Opportunities
|
||||
|
||||
### Phase 2: Extended Builder Pattern Implementation
|
||||
|
||||
Based on codebase analysis, we can achieve even greater code reduction by extending the builder pattern to these areas:
|
||||
|
||||
#### 1. Configuration Builders
|
||||
```rust
|
||||
// Current: Scattered configuration in multiple files
|
||||
// Opportunity: Centralized ConfigurationBuilder
|
||||
|
||||
pub struct ConfigurationBuilder;
|
||||
impl ConfigurationBuilder {
|
||||
pub fn development() -> AppConfig { /* ... */ }
|
||||
pub fn production() -> AppConfig { /* ... */ }
|
||||
pub fn testing() -> AppConfig { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Service Factory Pattern
|
||||
```rust
|
||||
// Current: Manual service instantiation
|
||||
// Opportunity: ServiceFactory with builder pattern
|
||||
|
||||
pub struct ServiceFactory;
|
||||
impl ServiceFactory {
|
||||
pub fn create_order_service() -> OrderService { /* ... */ }
|
||||
pub fn create_user_service() -> UserService { /* ... */ }
|
||||
pub fn create_product_service() -> ProductService { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Response Builders
|
||||
```rust
|
||||
// Current: Manual JSON response construction
|
||||
// Opportunity: ResponseBuilder for consistent API responses
|
||||
|
||||
pub struct ApiResponseBuilder<T>;
|
||||
impl<T> ApiResponseBuilder<T> {
|
||||
pub fn success(data: T) -> ApiResponse<T> { /* ... */ }
|
||||
pub fn error(message: &str) -> ApiResponse<T> { /* ... */ }
|
||||
pub fn paginated(data: Vec<T>, page: u32, total: u32) -> ApiResponse<Vec<T>> { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Mock Data Builders
|
||||
```rust
|
||||
// Current: Scattered mock data creation
|
||||
// Opportunity: MockDataBuilder for testing
|
||||
|
||||
pub struct MockDataBuilder;
|
||||
impl MockDataBuilder {
|
||||
pub fn user_with_balance(email: &str, balance: Decimal) -> UserPersistentData { /* ... */ }
|
||||
pub fn product_with_price(name: &str, price: Decimal) -> Product { /* ... */ }
|
||||
pub fn order_with_items(user_id: &str, items: Vec<OrderItem>) -> Order { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Code Reduction Analysis
|
||||
|
||||
### Current Achievement
|
||||
- **Lines Reduced**: ~800 lines of duplicated struct initialization code
|
||||
- **Compilation Errors**: 24 → 0 (100% resolution)
|
||||
- **Maintainability**: Single source of truth for all UserPersistentData
|
||||
- **Future-Proofing**: New fields automatically included
|
||||
|
||||
### Potential Further Reduction
|
||||
Based on codebase analysis, additional opportunities exist:
|
||||
|
||||
| Area | Current Lines | Potential Reduction | Builder Pattern |
|
||||
|------|---------------|-------------------|-----------------|
|
||||
| Configuration | ~200 lines | ~150 lines | ConfigurationBuilder |
|
||||
| Service Creation | ~150 lines | ~100 lines | ServiceFactory |
|
||||
| API Responses | ~300 lines | ~200 lines | ResponseBuilder |
|
||||
| Mock Data | ~250 lines | ~180 lines | MockDataBuilder |
|
||||
| **Total** | **~900 lines** | **~630 lines** | **70% reduction** |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Implementation Roadmap
|
||||
|
||||
### Phase 1: ✅ COMPLETE - UserPersistentData Migration
|
||||
- [x] SessionDataBuilder implementation
|
||||
- [x] All UserPersistentData initializations migrated
|
||||
- [x] Zero compilation errors achieved
|
||||
- [x] Single-source-of-truth architecture
|
||||
|
||||
### Phase 2: Extended Builder Patterns
|
||||
- [ ] ConfigurationBuilder for app settings
|
||||
- [ ] ServiceFactory for dependency injection
|
||||
- [ ] ResponseBuilder for consistent API responses
|
||||
- [ ] MockDataBuilder for testing infrastructure
|
||||
|
||||
### Phase 3: Advanced Patterns
|
||||
- [ ] Macro-based builder generation
|
||||
- [ ] Compile-time validation
|
||||
- [ ] Builder trait abstractions
|
||||
- [ ] Performance optimizations
|
||||
|
||||
---
|
||||
|
||||
## 🔍 AI Understanding Guide
|
||||
|
||||
### For Future AI Assistance
|
||||
|
||||
When working with the Project Mycelium codebase, follow these patterns:
|
||||
|
||||
#### 1. UserPersistentData Creation
|
||||
```rust
|
||||
// ✅ ALWAYS use builder pattern
|
||||
let user_data = SessionDataBuilder::load_or_create(email);
|
||||
let new_user = SessionDataBuilder::new_user(email);
|
||||
|
||||
// ❌ NEVER use manual initialization
|
||||
let user_data = UserPersistentData { /* manual fields */ };
|
||||
```
|
||||
|
||||
#### 2. Adding New Fields
|
||||
```rust
|
||||
// ✅ Add to Default implementation
|
||||
#[derive(Default)]
|
||||
struct UserPersistentData {
|
||||
// existing fields...
|
||||
new_field: Option<String>, // Automatically included via ..Default::default()
|
||||
}
|
||||
|
||||
// ✅ Update builder if needed
|
||||
impl SessionDataBuilder {
|
||||
pub fn new_user(email: &str) -> UserPersistentData {
|
||||
UserPersistentData {
|
||||
user_email: email.to_string(),
|
||||
// specific defaults...
|
||||
..Default::default() // Includes new_field automatically
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Service Construction
|
||||
```rust
|
||||
// ✅ Use existing builders where available
|
||||
let currency_service = CurrencyServiceBuilder::new()
|
||||
.base_currency("USD")
|
||||
.cache_duration(60)
|
||||
.build()?;
|
||||
|
||||
// ✅ Follow builder pattern for new services
|
||||
let new_service = NewServiceBuilder::new()
|
||||
.with_config(config)
|
||||
.with_dependencies(deps)
|
||||
.build()?;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Benefits Achieved
|
||||
|
||||
### 1. Maintainability
|
||||
- **Single Source of Truth**: All struct creation centralized
|
||||
- **Consistent Patterns**: Same approach across entire codebase
|
||||
- **Easy Updates**: New fields added in one place
|
||||
|
||||
### 2. Reliability
|
||||
- **Zero Compilation Errors**: Eliminated missing field errors
|
||||
- **Type Safety**: Builder validation prevents invalid states
|
||||
- **Default Handling**: Automatic inclusion of new fields
|
||||
|
||||
### 3. Developer Experience
|
||||
- **Readable Code**: Fluent interface with method chaining
|
||||
- **Less Boilerplate**: Reduced from 30+ lines to 1 line
|
||||
- **Self-Documenting**: Builder methods clearly show intent
|
||||
|
||||
### 4. Future-Proofing
|
||||
- **Extensible**: Easy to add new builder methods
|
||||
- **Scalable**: Pattern works for any struct complexity
|
||||
- **Migration-Friendly**: Database migrations simplified
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Conclusion
|
||||
|
||||
The Project Mycelium now features a **world-class builder pattern architecture** that serves as a model for industry-standard Rust development. The successful migration from scattered manual initializations to centralized builders has achieved:
|
||||
|
||||
- **95.8% error reduction** (24 → 0 compilation errors)
|
||||
- **~800 lines of code reduction** through elimination of duplication
|
||||
- **Single-source-of-truth architecture** for all struct construction
|
||||
- **Future-proof foundation** for continued development
|
||||
|
||||
This architecture provides the foundation for Phase 2 consolidation opportunities that could achieve an additional **70% code reduction** in configuration, service creation, API responses, and mock data areas.
|
||||
|
||||
The builder pattern implementation demonstrates how thoughtful architecture decisions can dramatically improve code quality, maintainability, and developer productivity while maintaining type safety and business logic integrity.
|
@@ -0,0 +1,818 @@
|
||||
# Project Mycelium - Builder Pattern Maximization Roadmap
|
||||
|
||||
**Document Purpose**: Comprehensive roadmap for achieving maximum builder pattern consolidation across the entire Project Mycelium codebase, targeting industry-standard single-source-of-truth architecture with minimal code duplication.
|
||||
|
||||
**Last Updated**: 2025-08-06
|
||||
**Status**: Phase 2 Planning - Ready for Implementation
|
||||
**Target**: Additional 70% code reduction (~1,430 total lines eliminated)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Executive Summary
|
||||
|
||||
Building on the successful **Phase 1 achievement** (95.8% compilation error reduction, ~800 lines eliminated), this roadmap outlines **Phase 2 consolidation opportunities** that will achieve an additional **70% code reduction** while maintaining all existing features and enhancing maintainability through industry-standard builder patterns.
|
||||
|
||||
**Phase 1 Success Metrics:**
|
||||
- ✅ **24 → 0 compilation errors** (100% resolution)
|
||||
- ✅ **~800 lines eliminated** through SessionDataBuilder consolidation
|
||||
- ✅ **Single-source-of-truth** for all UserPersistentData construction
|
||||
- ✅ **Zero feature regression** - all functionality preserved
|
||||
|
||||
**Phase 2 Target Metrics:**
|
||||
- 🎯 **Additional ~630 lines reduction** (70% of remaining opportunities)
|
||||
- 🎯 **4 major builder consolidations** (Configuration, Service, Response, Mock)
|
||||
- 🎯 **Industry-standard patterns** throughout entire codebase
|
||||
- 🎯 **Enhanced maintainability** and developer experience
|
||||
|
||||
---
|
||||
|
||||
## 📊 Consolidation Opportunity Analysis
|
||||
|
||||
### Current Codebase Analysis Results
|
||||
|
||||
Based on comprehensive codebase analysis, the following patterns have been identified for consolidation:
|
||||
|
||||
| Pattern Type | Current Instances | Lines per Instance | Total Lines | Consolidation Potential |
|
||||
|--------------|-------------------|-------------------|-------------|------------------------|
|
||||
| **Configuration Creation** | 15+ instances | ~10 lines | ~150 lines | **ConfigurationBuilder** |
|
||||
| **Service Instantiation** | 25+ instances | ~6 lines | ~150 lines | **ServiceFactory** |
|
||||
| **API Response Construction** | 50+ instances | ~6 lines | ~300 lines | **ResponseBuilder** |
|
||||
| **Mock Data Elimination** | 20+ instances | ~12 lines | ~240 lines | **DataAggregator** |
|
||||
| **Error Handling** | 30+ instances | ~4 lines | ~120 lines | **ErrorBuilder** |
|
||||
| **Context Building** | 10+ instances | ~8 lines | ~80 lines | **ContextBuilder** |
|
||||
| **Validation Logic** | 15+ instances | ~5 lines | ~75 lines | **ValidationBuilder** |
|
||||
| **Total Opportunity** | **165+ instances** | **~8 avg** | **~1,115 lines** | **~780 lines reduction** |
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Phase 2 Implementation Roadmap
|
||||
|
||||
### **Priority 1: ConfigurationBuilder** (~150 line reduction)
|
||||
|
||||
**Current Problem:**
|
||||
```rust
|
||||
// ❌ Scattered configuration creation in multiple files
|
||||
let config = Config::builder()
|
||||
.set_default("server.host", "127.0.0.1")?
|
||||
.set_default("server.port", 9999)?
|
||||
.set_default("server.workers", None::<u32>)?
|
||||
.set_default("templates.dir", "./src/views")?
|
||||
.add_source(File::with_name("config/default").required(false))
|
||||
.add_source(File::with_name("config/local").required(false))
|
||||
.add_source(config::Environment::with_prefix("APP").separator("__"))
|
||||
.build()?;
|
||||
```
|
||||
|
||||
**Solution: Centralized ConfigurationBuilder**
|
||||
```rust
|
||||
// ✅ Single-source-of-truth configuration
|
||||
pub struct ConfigurationBuilder;
|
||||
|
||||
impl ConfigurationBuilder {
|
||||
pub fn development() -> AppConfig {
|
||||
AppConfig {
|
||||
server: ServerConfig {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 9999,
|
||||
workers: Some(4),
|
||||
},
|
||||
templates: TemplateConfig {
|
||||
dir: "./src/views".to_string(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn production() -> AppConfig {
|
||||
AppConfig {
|
||||
server: ServerConfig {
|
||||
host: "0.0.0.0".to_string(),
|
||||
port: 8080,
|
||||
workers: Some(num_cpus::get() as u32),
|
||||
},
|
||||
templates: TemplateConfig {
|
||||
dir: "/app/templates".to_string(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn testing() -> AppConfig {
|
||||
AppConfig {
|
||||
server: ServerConfig {
|
||||
host: "127.0.0.1".to_string(),
|
||||
port: 0, // Random port
|
||||
workers: Some(1),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_env() -> Result<AppConfig, ConfigError> {
|
||||
match env::var("APP_ENV").unwrap_or_else(|_| "development".to_string()).as_str() {
|
||||
"production" => Ok(Self::production()),
|
||||
"testing" => Ok(Self::testing()),
|
||||
_ => Ok(Self::development()),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Pattern:**
|
||||
```rust
|
||||
// ✅ Clean, readable configuration
|
||||
let config = ConfigurationBuilder::from_env()?;
|
||||
let dev_config = ConfigurationBuilder::development();
|
||||
let prod_config = ConfigurationBuilder::production();
|
||||
```
|
||||
|
||||
### **Priority 2: ServiceFactory** (~100 line reduction)
|
||||
|
||||
**Current Problem:**
|
||||
```rust
|
||||
// ❌ Repeated service instantiation throughout codebase
|
||||
let farmer_service = match crate::services::farmer::FarmerService::builder().build() {
|
||||
Ok(service) => service,
|
||||
Err(e) => {
|
||||
log::error!("Failed to build farmer service: {}", e);
|
||||
return Ok(HttpResponse::InternalServerError().json(serde_json::json!({
|
||||
"error": "Service initialization failed"
|
||||
})));
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Solution: Centralized ServiceFactory**
|
||||
```rust
|
||||
// ✅ Single-source-of-truth service creation
|
||||
pub struct ServiceFactory;
|
||||
|
||||
impl ServiceFactory {
|
||||
pub fn create_farmer_service() -> Result<FarmerService, String> {
|
||||
FarmerService::builder()
|
||||
.auto_sync_enabled(true)
|
||||
.metrics_collection(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn create_order_service() -> Result<OrderService, String> {
|
||||
let currency_service = Self::create_currency_service()?;
|
||||
let product_service = Self::create_product_service()?;
|
||||
|
||||
OrderService::builder()
|
||||
.currency_service(currency_service)
|
||||
.product_service(product_service)
|
||||
.auto_save(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn create_user_service() -> Result<UserService, String> {
|
||||
UserService::builder()
|
||||
.include_metrics(true)
|
||||
.cache_enabled(true)
|
||||
.real_time_updates(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn create_currency_service() -> Result<CurrencyService, String> {
|
||||
CurrencyServiceBuilder::new()
|
||||
.base_currency("USD")
|
||||
.cache_duration(60)
|
||||
.auto_update(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn create_product_service() -> Result<ProductService, String> {
|
||||
let currency_service = Self::create_currency_service()?;
|
||||
|
||||
ProductServiceBuilder::new()
|
||||
.currency_service(currency_service)
|
||||
.cache_enabled(true)
|
||||
.include_slice_products(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
// Convenience method for controllers
|
||||
pub fn create_all_services() -> Result<ServiceBundle, String> {
|
||||
Ok(ServiceBundle {
|
||||
farmer: Self::create_farmer_service()?,
|
||||
order: Self::create_order_service()?,
|
||||
user: Self::create_user_service()?,
|
||||
currency: Self::create_currency_service()?,
|
||||
product: Self::create_product_service()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceBundle {
|
||||
pub farmer: FarmerService,
|
||||
pub order: OrderService,
|
||||
pub user: UserService,
|
||||
pub currency: CurrencyService,
|
||||
pub product: ProductService,
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Pattern:**
|
||||
```rust
|
||||
// ✅ Clean, error-handled service creation
|
||||
let farmer_service = ServiceFactory::create_farmer_service()
|
||||
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
||||
|
||||
// ✅ Or get all services at once
|
||||
let services = ServiceFactory::create_all_services()
|
||||
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
||||
```
|
||||
|
||||
### **Priority 3: ResponseBuilder** (~200 line reduction)
|
||||
|
||||
**Current Problem:**
|
||||
```rust
|
||||
// ❌ Repeated JSON response construction
|
||||
Ok(HttpResponse::Ok().json(serde_json::json!({
|
||||
"success": true,
|
||||
"message": "Operation completed successfully",
|
||||
"data": result_data
|
||||
})))
|
||||
|
||||
Ok(HttpResponse::InternalServerError().json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": "Service initialization failed",
|
||||
"details": error_message
|
||||
})))
|
||||
```
|
||||
|
||||
**Solution: Centralized ResponseBuilder**
|
||||
```rust
|
||||
// ✅ Single-source-of-truth API responses
|
||||
pub struct ApiResponseBuilder;
|
||||
|
||||
impl ApiResponseBuilder {
|
||||
pub fn success<T: Serialize>(data: T) -> HttpResponse {
|
||||
HttpResponse::Ok().json(serde_json::json!({
|
||||
"success": true,
|
||||
"data": data,
|
||||
"timestamp": Utc::now().to_rfc3339()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn success_with_message<T: Serialize>(data: T, message: &str) -> HttpResponse {
|
||||
HttpResponse::Ok().json(serde_json::json!({
|
||||
"success": true,
|
||||
"message": message,
|
||||
"data": data,
|
||||
"timestamp": Utc::now().to_rfc3339()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn error(message: &str) -> HttpResponse {
|
||||
HttpResponse::InternalServerError().json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": message,
|
||||
"timestamp": Utc::now().to_rfc3339()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn error_with_details(message: &str, details: &str) -> HttpResponse {
|
||||
HttpResponse::InternalServerError().json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": message,
|
||||
"details": details,
|
||||
"timestamp": Utc::now().to_rfc3339()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn unauthorized(message: &str) -> HttpResponse {
|
||||
HttpResponse::Unauthorized().json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": message,
|
||||
"code": "UNAUTHORIZED",
|
||||
"timestamp": Utc::now().to_rfc3339()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn not_found(resource: &str) -> HttpResponse {
|
||||
HttpResponse::NotFound().json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": format!("{} not found", resource),
|
||||
"code": "NOT_FOUND",
|
||||
"timestamp": Utc::now().to_rfc3339()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn paginated<T: Serialize>(
|
||||
data: Vec<T>,
|
||||
page: u32,
|
||||
per_page: u32,
|
||||
total: u32
|
||||
) -> HttpResponse {
|
||||
HttpResponse::Ok().json(serde_json::json!({
|
||||
"success": true,
|
||||
"data": data,
|
||||
"pagination": {
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"total": total,
|
||||
"total_pages": (total + per_page - 1) / per_page
|
||||
},
|
||||
"timestamp": Utc::now().to_rfc3339()
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn validation_error(errors: Vec<&str>) -> HttpResponse {
|
||||
HttpResponse::BadRequest().json(serde_json::json!({
|
||||
"success": false,
|
||||
"error": "Validation failed",
|
||||
"validation_errors": errors,
|
||||
"code": "VALIDATION_ERROR",
|
||||
"timestamp": Utc::now().to_rfc3339()
|
||||
}))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Pattern:**
|
||||
```rust
|
||||
// ✅ Clean, consistent API responses
|
||||
return Ok(ApiResponseBuilder::success(farmer_data));
|
||||
return Ok(ApiResponseBuilder::error("Service initialization failed"));
|
||||
return Ok(ApiResponseBuilder::unauthorized("User not authenticated"));
|
||||
return Ok(ApiResponseBuilder::not_found("Node"));
|
||||
return Ok(ApiResponseBuilder::paginated(nodes, page, per_page, total));
|
||||
```
|
||||
|
||||
### **Priority 4: Persistent Data Integration** (~180 line reduction + Mock Elimination)
|
||||
|
||||
**Current Problem:**
|
||||
```rust
|
||||
// ❌ Scattered mock data creation throughout codebase
|
||||
let mock_products = vec![
|
||||
Product { name: "Mock VM".to_string(), /* ... */ },
|
||||
Product { name: "Mock Storage".to_string(), /* ... */ },
|
||||
];
|
||||
|
||||
// ❌ Hardcoded mock services, apps, nodes
|
||||
let mock_services = get_hardcoded_services();
|
||||
let mock_apps = get_hardcoded_apps();
|
||||
```
|
||||
|
||||
**Solution: Persistent Data Integration with DataAggregator**
|
||||
|
||||
**🏭 Industry Standard Architecture: Real Data Only**
|
||||
|
||||
This approach eliminates all mock data and uses only persistent user data from `user_data/` directory, which is the industry-standard production approach:
|
||||
|
||||
```rust
|
||||
// ✅ Real data aggregation from persistent storage
|
||||
pub struct DataAggregator;
|
||||
|
||||
impl DataAggregator {
|
||||
/// Aggregate all marketplace products from real user data
|
||||
pub fn get_marketplace_products() -> Vec<Product> {
|
||||
let mut products = Vec::new();
|
||||
|
||||
// Load all user data files from user_data/ directory
|
||||
if let Ok(entries) = std::fs::read_dir("user_data/") {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(file_name) = entry.file_name().to_str() {
|
||||
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
|
||||
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
|
||||
// Aggregate products from user's services
|
||||
products.extend(Self::services_to_products(&user_data.services));
|
||||
|
||||
// Aggregate products from user's apps
|
||||
products.extend(Self::apps_to_products(&user_data.apps));
|
||||
|
||||
// Aggregate products from user's slice products
|
||||
products.extend(Self::slice_products_to_products(&user_data.slice_products));
|
||||
|
||||
// Aggregate products from user's nodes (for rental)
|
||||
products.extend(Self::nodes_to_products(&user_data.nodes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
products
|
||||
}
|
||||
|
||||
/// Aggregate all marketplace services from real user data
|
||||
pub fn get_marketplace_services() -> Vec<Service> {
|
||||
let mut services = Vec::new();
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir("user_data/") {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(file_name) = entry.file_name().to_str() {
|
||||
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
|
||||
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
|
||||
services.extend(user_data.services.into_iter().filter(|s| s.status == "Active"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
services
|
||||
}
|
||||
|
||||
/// Aggregate all marketplace apps from real user data
|
||||
pub fn get_marketplace_apps() -> Vec<PublishedApp> {
|
||||
let mut apps = Vec::new();
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir("user_data/") {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(file_name) = entry.file_name().to_str() {
|
||||
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
|
||||
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
|
||||
apps.extend(user_data.apps.into_iter().filter(|a| a.status == "Active"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apps
|
||||
}
|
||||
|
||||
/// Aggregate all available nodes for rental from real user data
|
||||
pub fn get_marketplace_nodes() -> Vec<Node> {
|
||||
let mut nodes = Vec::new();
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir("user_data/") {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(file_name) = entry.file_name().to_str() {
|
||||
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
|
||||
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
|
||||
nodes.extend(user_data.nodes.into_iter().filter(|n| n.status == NodeStatus::Online));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodes
|
||||
}
|
||||
|
||||
/// Get marketplace statistics from real user data
|
||||
pub fn get_marketplace_stats() -> MarketplaceStats {
|
||||
let products = Self::get_marketplace_products();
|
||||
let services = Self::get_marketplace_services();
|
||||
let apps = Self::get_marketplace_apps();
|
||||
let nodes = Self::get_marketplace_nodes();
|
||||
|
||||
MarketplaceStats {
|
||||
total_products: products.len(),
|
||||
total_services: services.len(),
|
||||
total_apps: apps.len(),
|
||||
total_nodes: nodes.len(),
|
||||
active_providers: Self::count_active_providers(),
|
||||
total_transactions: Self::count_total_transactions(),
|
||||
}
|
||||
}
|
||||
|
||||
// Private helper methods for data conversion
|
||||
fn services_to_products(services: &[Service]) -> Vec<Product> {
|
||||
services.iter()
|
||||
.filter(|s| s.status == "Active")
|
||||
.map(|service| {
|
||||
ProductBuilder::new()
|
||||
.name(&service.name)
|
||||
.category_id(&service.category)
|
||||
.description(&service.description)
|
||||
.base_price(Decimal::from(service.price_per_hour))
|
||||
.base_currency("USD")
|
||||
.provider_id(&service.provider_email)
|
||||
.provider_name(&service.provider_name)
|
||||
.build()
|
||||
.expect("Failed to convert service to product")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn apps_to_products(apps: &[PublishedApp]) -> Vec<Product> {
|
||||
apps.iter()
|
||||
.filter(|a| a.status == "Active")
|
||||
.map(|app| {
|
||||
ProductBuilder::new()
|
||||
.name(&app.name)
|
||||
.category_id(&app.category)
|
||||
.description(&format!("Published app: {}", app.name))
|
||||
.base_price(Decimal::from(app.monthly_revenue_usd))
|
||||
.base_currency("USD")
|
||||
.provider_id(&format!("app-provider-{}", app.id))
|
||||
.provider_name("App Provider")
|
||||
.build()
|
||||
.expect("Failed to convert app to product")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn slice_products_to_products(slice_products: &[SliceProduct]) -> Vec<Product> {
|
||||
slice_products.iter()
|
||||
.map(|slice| {
|
||||
ProductBuilder::new()
|
||||
.name(&slice.name)
|
||||
.category_id("slice")
|
||||
.description(&slice.description)
|
||||
.base_price(slice.base_price_per_hour)
|
||||
.base_currency("USD")
|
||||
.provider_id(&slice.provider_email)
|
||||
.provider_name("Slice Provider")
|
||||
.build()
|
||||
.expect("Failed to convert slice to product")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn nodes_to_products(nodes: &[Node]) -> Vec<Product> {
|
||||
nodes.iter()
|
||||
.filter(|n| n.status == NodeStatus::Online && n.rental_options.is_some())
|
||||
.map(|node| {
|
||||
ProductBuilder::new()
|
||||
.name(&format!("Node Rental: {}", node.id))
|
||||
.category_id("node-rental")
|
||||
.description(&format!("Rent computing resources from node {}", node.id))
|
||||
.base_price(node.rental_options.as_ref().unwrap().base_price_per_hour)
|
||||
.base_currency("USD")
|
||||
.provider_id(&node.farmer_email)
|
||||
.provider_name("Node Farmer")
|
||||
.build()
|
||||
.expect("Failed to convert node to product")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn count_active_providers() -> usize {
|
||||
// Count unique provider emails across all user data
|
||||
let mut providers = std::collections::HashSet::new();
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir("user_data/") {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(file_name) = entry.file_name().to_str() {
|
||||
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
|
||||
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
|
||||
if !user_data.services.is_empty() || !user_data.apps.is_empty() || !user_data.nodes.is_empty() {
|
||||
providers.insert(user_data.user_email);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
providers.len()
|
||||
}
|
||||
|
||||
fn count_total_transactions() -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
if let Ok(entries) = std::fs::read_dir("user_data/") {
|
||||
for entry in entries.flatten() {
|
||||
if let Some(file_name) = entry.file_name().to_str() {
|
||||
if file_name.ends_with(".json") && !file_name.ends_with("_cart.json") {
|
||||
if let Ok(user_data) = UserPersistence::load_user_data_from_file(&entry.path()) {
|
||||
total += user_data.transactions.len();
|
||||
total += user_data.orders.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MarketplaceStats {
|
||||
pub total_products: usize,
|
||||
pub total_services: usize,
|
||||
pub total_apps: usize,
|
||||
pub total_nodes: usize,
|
||||
pub active_providers: usize,
|
||||
pub total_transactions: usize,
|
||||
}
|
||||
|
||||
pub struct TestEnvironment {
|
||||
pub users: Vec<UserPersistentData>,
|
||||
pub products: Vec<Product>,
|
||||
pub orders: Vec<Order>,
|
||||
}
|
||||
```
|
||||
|
||||
**Usage Pattern:**
|
||||
```rust
|
||||
// ✅ Real marketplace data from persistent storage
|
||||
let marketplace_products = DataAggregator::get_marketplace_products();
|
||||
let marketplace_services = DataAggregator::get_marketplace_services();
|
||||
let marketplace_apps = DataAggregator::get_marketplace_apps();
|
||||
let marketplace_nodes = DataAggregator::get_marketplace_nodes();
|
||||
let marketplace_stats = DataAggregator::get_marketplace_stats();
|
||||
```
|
||||
|
||||
**🏭 Industry Standard Benefits:**
|
||||
- **Real Data Only**: No mock data, all products/services come from actual users in `user_data/`
|
||||
- **Dynamic Marketplace**: Marketplace content grows organically as users add services, apps, nodes
|
||||
- **Authentic Experience**: Users see real offerings from real providers
|
||||
- **Production Ready**: No mock-to-real data migration needed
|
||||
- **Scalable Architecture**: Handles any number of users and their offerings
|
||||
- **Data Integrity**: All marketplace data backed by persistent user storage
|
||||
|
||||
### **Priority 5: ErrorBuilder** (~120 line reduction)
|
||||
|
||||
**Solution: Centralized Error Handling**
|
||||
```rust
|
||||
pub struct ErrorBuilder;
|
||||
|
||||
impl ErrorBuilder {
|
||||
pub fn service_error(service: &str, operation: &str, details: &str) -> String {
|
||||
format!("Failed to {} in {}: {}", operation, service, details)
|
||||
}
|
||||
|
||||
pub fn validation_error(field: &str, reason: &str) -> String {
|
||||
format!("Validation failed for {}: {}", field, reason)
|
||||
}
|
||||
|
||||
pub fn authentication_error(reason: &str) -> String {
|
||||
format!("Authentication failed: {}", reason)
|
||||
}
|
||||
|
||||
pub fn not_found_error(resource: &str, id: &str) -> String {
|
||||
format!("{} with ID '{}' not found", resource, id)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Priority 6: ContextBuilder Enhancement** (~80 line reduction)
|
||||
|
||||
**Solution: Enhanced Template Context Building**
|
||||
```rust
|
||||
pub struct ContextBuilder {
|
||||
context: tera::Context,
|
||||
}
|
||||
|
||||
impl ContextBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
context: tera::Context::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_user(mut self, user: &User) -> Self {
|
||||
self.context.insert("user", user);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_dashboard_data(mut self, section: &str) -> Self {
|
||||
self.context.insert("active_page", "dashboard");
|
||||
self.context.insert("active_section", section);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_marketplace_data(mut self, products: &[Product]) -> Self {
|
||||
self.context.insert("products", products);
|
||||
self.context.insert("active_page", "marketplace");
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> tera::Context {
|
||||
self.context
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Implementation Timeline
|
||||
|
||||
### **Phase 2A: Foundation (Week 1-2)**
|
||||
- [ ] **ConfigurationBuilder** implementation
|
||||
- [ ] **ServiceFactory** implementation
|
||||
- [ ] **ResponseBuilder** implementation
|
||||
- [ ] Core infrastructure and testing
|
||||
|
||||
### **Phase 2B: Enhancement (Week 3-4)**
|
||||
- [ ] **MockDataBuilder** implementation
|
||||
- [ ] **ErrorBuilder** implementation
|
||||
- [ ] **ContextBuilder** enhancement
|
||||
- [ ] Integration testing and validation
|
||||
|
||||
### **Phase 2C: Migration (Week 5-6)**
|
||||
- [ ] Systematic migration of all instances
|
||||
- [ ] Comprehensive testing
|
||||
- [ ] Performance optimization
|
||||
- [ ] Documentation updates
|
||||
|
||||
### **Phase 2D: Validation (Week 7-8)**
|
||||
- [ ] Full regression testing
|
||||
- [ ] Performance benchmarking
|
||||
- [ ] Code review and refinement
|
||||
- [ ] Final documentation
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Success Metrics & Validation
|
||||
|
||||
### **Quantitative Metrics**
|
||||
- **Lines of Code Reduction**: Target 630+ lines (70% of identified opportunities)
|
||||
- **Compilation Time**: Maintain or improve current build times
|
||||
- **Test Coverage**: Maintain 100% test coverage for all builders
|
||||
- **Performance**: No regression in API response times
|
||||
|
||||
### **Qualitative Metrics**
|
||||
- **Developer Experience**: Reduced boilerplate, clearer patterns
|
||||
- **Maintainability**: Single-source-of-truth for all patterns
|
||||
- **Consistency**: Uniform patterns across entire codebase
|
||||
- **Documentation**: Comprehensive guides for all builders
|
||||
|
||||
### **Validation Checklist**
|
||||
- [ ] All existing functionality preserved
|
||||
- [ ] Zero compilation errors
|
||||
- [ ] All tests passing
|
||||
- [ ] Performance benchmarks met
|
||||
- [ ] Code review approved
|
||||
- [ ] Documentation complete
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Advanced Optimization Opportunities
|
||||
|
||||
### **Phase 3: Advanced Patterns** (Future Consideration)
|
||||
|
||||
1. **Macro-Based Builder Generation**
|
||||
```rust
|
||||
#[derive(Builder)]
|
||||
#[builder(pattern = "owned")]
|
||||
struct AutoGeneratedBuilder {
|
||||
// Automatic builder generation
|
||||
}
|
||||
```
|
||||
|
||||
2. **Compile-Time Validation**
|
||||
```rust
|
||||
const_assert!(builder_fields_complete());
|
||||
```
|
||||
|
||||
3. **Builder Trait Abstractions**
|
||||
```rust
|
||||
trait BuilderPattern<T> {
|
||||
fn build(self) -> Result<T, String>;
|
||||
}
|
||||
```
|
||||
|
||||
4. **Performance Optimizations**
|
||||
- Zero-cost abstractions
|
||||
- Compile-time optimizations
|
||||
- Memory pool allocations
|
||||
|
||||
---
|
||||
|
||||
## 📚 Implementation Guidelines
|
||||
|
||||
### **For AI Assistance**
|
||||
|
||||
When implementing Phase 2 consolidation:
|
||||
|
||||
1. **Always use existing patterns** from Phase 1 as templates
|
||||
2. **Maintain backward compatibility** during migration
|
||||
3. **Follow single-source-of-truth principle** for all builders
|
||||
4. **Use `..Default::default()`** for extensibility
|
||||
5. **Include comprehensive error handling** in all builders
|
||||
6. **Add builder methods to existing structs** via `impl` blocks
|
||||
7. **Create template methods** for common use cases
|
||||
8. **Validate all builders** with comprehensive tests
|
||||
|
||||
### **Migration Strategy**
|
||||
|
||||
1. **Implement builder** in `src/models/builders.rs`
|
||||
2. **Add convenience methods** to existing structs
|
||||
3. **Create migration script** to identify all instances
|
||||
4. **Replace instances systematically** one module at a time
|
||||
5. **Test thoroughly** after each module migration
|
||||
6. **Update documentation** for each completed builder
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Expected Final State
|
||||
|
||||
After Phase 2 completion, the Project Mycelium will achieve:
|
||||
|
||||
### **Code Quality**
|
||||
- **~1,430 total lines eliminated** (Phase 1 + Phase 2)
|
||||
- **Industry-standard builder patterns** throughout entire codebase
|
||||
- **Single-source-of-truth architecture** for all major patterns
|
||||
- **Zero code duplication** in construction patterns
|
||||
|
||||
### **Developer Experience**
|
||||
- **Consistent patterns** across all modules
|
||||
- **Self-documenting code** through builder methods
|
||||
- **Reduced cognitive load** for new developers
|
||||
- **Enhanced maintainability** for future features
|
||||
|
||||
### **Architecture Benefits**
|
||||
- **Future-proof foundation** for continued development
|
||||
- **Easy extensibility** for new features
|
||||
- **Simplified testing** through mock builders
|
||||
- **Performance optimizations** through centralized patterns
|
||||
|
||||
This roadmap provides a clear path to achieving maximum builder pattern consolidation while maintaining all existing features and dramatically improving code quality and maintainability.
|
@@ -0,0 +1,221 @@
|
||||
# Builder Pattern Consolidation - Progress Tracker
|
||||
|
||||
## Overview
|
||||
|
||||
This document tracks the systematic migration of the Project Mycelium codebase to comprehensive builder pattern architecture, eliminating mock data and establishing single-source-of-truth patterns throughout.
|
||||
|
||||
## Overall Progress: **85%** Complete
|
||||
|
||||
### ✅ **Phase 1: Foundation Builders** - **100% Complete**
|
||||
- **ConfigurationBuilder**: ✅ Complete (~150 lines reduced)
|
||||
- **SessionDataBuilder**: ✅ Complete (~800 lines reduced)
|
||||
- **ServiceFactory**: ✅ Complete (~100+ lines reduced)
|
||||
|
||||
### 🔄 **Phase 2: API & Response Consolidation** - **80% Complete**
|
||||
- **ResponseBuilder**: ✅ 100% migrated across controllers (~200 lines reduced)
|
||||
- **DataAggregator**: ❌ Not started (~180 lines potential)
|
||||
|
||||
### 📊 **Total Impact So Far**
|
||||
- **Lines Reduced**: ~1,320+ lines of duplicate code eliminated
|
||||
- **Compilation Errors**: Reduced from 67+ to 0 across all phases
|
||||
- **Mock Data Elimination**: 100% complete in migrated areas
|
||||
- **Files Enhanced**: 17+ files migrated to builder patterns
|
||||
- **ResponseBuilder Coverage**: All controllers migrated; redirect support added; `payment_required()` implemented
|
||||
|
||||
---
|
||||
|
||||
## Detailed File-by-File Progress
|
||||
|
||||
### **ConfigurationBuilder Migration** ✅ **100% Complete**
|
||||
|
||||
#### Files Enhanced (All ✅ Complete):
|
||||
- ✅ `src/controllers/home.rs` - Environment variable access centralized
|
||||
- ✅ `src/controllers/auth.rs` - JWT secret and OAuth config migrated
|
||||
- ✅ `src/controllers/wallet.rs` - Configuration usage consolidated
|
||||
- ✅ `src/controllers/order.rs` - Environment access centralized
|
||||
- ✅ `src/controllers/product.rs` - Configuration patterns unified
|
||||
- ✅ `src/controllers/debug.rs` - Config access standardized
|
||||
- ✅ `src/controllers/currency.rs` - Environment variables centralized
|
||||
- ✅ `src/controllers/legal.rs` - Configuration usage migrated
|
||||
- ✅ `src/controllers/dashboard.rs` - Config patterns consolidated
|
||||
- ✅ `src/controllers/gitea_auth.rs` - OAuth configuration centralized
|
||||
- ✅ `src/controllers/pool.rs` - Environment access unified
|
||||
- ✅ `src/controllers/docs.rs` - Configuration patterns migrated
|
||||
- ✅ `src/controllers/marketplace.rs` - Config usage consolidated
|
||||
- ✅ `src/controllers/rental.rs` - Environment variables centralized
|
||||
- ✅ `src/config/oauth.rs` - OAuth configuration migrated
|
||||
|
||||
**Result**: ~150 lines reduced, single source of truth for all app configuration
|
||||
|
||||
---
|
||||
|
||||
### **SessionDataBuilder Migration** ✅ **100% Complete**
|
||||
|
||||
#### Files Enhanced (All ✅ Complete):
|
||||
- ✅ `src/controllers/wallet.rs` - UserPersistentData construction centralized
|
||||
- ✅ `src/services/farmer.rs` - All user data initialization migrated
|
||||
- ✅ `src/services/user_persistence.rs` - All struct construction centralized
|
||||
- ✅ `src/services/session_manager.rs` - User data patterns unified
|
||||
- ✅ `src/services/node_rental.rs` - Data initialization migrated
|
||||
- ✅ `src/services/slice_rental.rs` - User data construction centralized
|
||||
|
||||
**Result**: ~800 lines reduced, eliminated 24+ compilation errors, future-proof with `..Default::default()`
|
||||
|
||||
---
|
||||
|
||||
### **ServiceFactory Migration** ✅ **100% Complete**
|
||||
|
||||
#### Files Enhanced (All ✅ Complete):
|
||||
- ✅ `src/controllers/order.rs` - Service instantiation centralized
|
||||
- ✅ `src/controllers/product.rs` - CurrencyService usage consolidated
|
||||
- ✅ `src/controllers/currency.rs` - Service creation patterns unified
|
||||
- ✅ `src/controllers/marketplace.rs` - Service instantiation migrated
|
||||
- ✅ `src/services/instant_purchase.rs` - Service access centralized
|
||||
- ✅ `src/services/factory.rs` - ServiceFactory implementation complete
|
||||
- ✅ `src/services/navbar.rs` - Service instantiation consolidated
|
||||
- ✅ `src/services/currency.rs` - Mock data completely eliminated
|
||||
|
||||
#### Mock Data Elimination ✅ **100% Complete**:
|
||||
- ✅ Removed all `MockDataService` instantiations (3 instances)
|
||||
- ✅ Eliminated `mock_data` field from CurrencyService struct
|
||||
- ✅ Replaced all `self.mock_data.*` method calls with persistent data logic
|
||||
- ✅ Updated Currency struct initializations with all required fields
|
||||
- ✅ Implemented real USD/EUR/TFT currency data instead of mock data
|
||||
- ✅ Fixed all type mismatches and compilation errors (43 → 0)
|
||||
|
||||
**Result**: ~100+ lines reduced, 100% mock data elimination, thread-safe singleton pattern
|
||||
|
||||
---
|
||||
|
||||
## **Phase 2: Remaining Work** 🔄 **25% Complete**
|
||||
|
||||
### **ResponseBuilder Migration** 🔄 **65% Complete**
|
||||
|
||||
#### Implementation Status:
|
||||
- ✅ **ResponseBuilder Created**: Comprehensive implementation in `src/utils/response_builder.rs`
|
||||
- ✅ **API Structure Defined**: StandardApiResponse, pagination, error handling
|
||||
- ✅ **Fluent Interface**: Complete builder pattern with method chaining
|
||||
- ✅ **Template Methods**: Common response patterns (success, error, not_found, etc.)
|
||||
- ✅ **Testing**: Comprehensive test suite included
|
||||
- ✅ **Redirect Support**: Enhanced with redirect functionality for auth flows
|
||||
- ✅ **Module Export**: Properly exported from utils module for codebase-wide access
|
||||
- ✅ **Compilation Verified**: All enhancements compile successfully with zero errors
|
||||
- ✅ **Payment Required Support**: Added payment_required() method for order flows
|
||||
|
||||
#### Files Successfully Migrated:
|
||||
- ✅ `src/controllers/auth.rs` - **COMPLETE** (10/10 patterns migrated)
|
||||
- ✅ auth_status JSON responses (2 patterns)
|
||||
- ✅ login invalid credentials redirect
|
||||
- ✅ register validation redirects (2 patterns)
|
||||
- ✅ logout redirect with cookie
|
||||
- ✅ login success redirect with cookie
|
||||
- ✅ register success redirect with cookie
|
||||
- ✅ password hash failure redirect
|
||||
- ✅ user exists redirect
|
||||
- ✅ save failed redirect
|
||||
|
||||
- ✅ `src/controllers/wallet.rs` - **COMPLETE** (49/49 patterns migrated) - MAJOR MILESTONE!
|
||||
- ✅ All HttpResponse::Ok() → ResponseBuilder::ok()
|
||||
- ✅ All HttpResponse::BadRequest() → ResponseBuilder::bad_request()
|
||||
- ✅ All HttpResponse::Unauthorized() → ResponseBuilder::unauthorized()
|
||||
- ✅ All HttpResponse::InternalServerError() → ResponseBuilder::internal_error()
|
||||
- ✅ Enhanced ResponseBuilder with ok() and bad_request() methods
|
||||
- ✅ Zero compilation errors - clean build achieved!
|
||||
|
||||
#### Files That Need ResponseBuilder Migration: None — 100% coverage
|
||||
|
||||
**Current Impact**: ~20 lines reduced so far, **Target**: ~200 lines total reduction
|
||||
|
||||
#### Migration Strategy:
|
||||
1. **Pattern Identification**: Find all `HttpResponse::Ok()`, `HttpResponse::BadRequest()`, etc.
|
||||
2. **Systematic Replacement**: Replace with `ResponseBuilder::success()`, `ResponseBuilder::error()`, etc.
|
||||
3. **Compilation Testing**: Ensure zero errors after each file migration
|
||||
4. **Response Standardization**: Ensure consistent JSON structure across all endpoints
|
||||
|
||||
---
|
||||
|
||||
### **DataAggregator Implementation** ❌ **Not Started**
|
||||
|
||||
#### Purpose:
|
||||
Replace remaining mock data usage with real user data aggregation from `user_data/` directory
|
||||
|
||||
#### Files That Need DataAggregator:
|
||||
- ❌ `src/controllers/marketplace.rs` - Replace mock product listings with real user data
|
||||
- ❌ `src/controllers/dashboard.rs` - Use real user statistics instead of mock data
|
||||
- ❌ `src/services/product.rs` - Aggregate real products from user data files
|
||||
- ❌ `src/services/node_marketplace.rs` - Use real node data from farmers
|
||||
- ❌ `src/services/grid.rs` - Aggregate real grid data from users
|
||||
|
||||
#### Implementation Plan:
|
||||
1. **Create DataAggregator**: Centralized service for reading and aggregating user data
|
||||
2. **User Data Reading**: Methods to read from `user_data/*.json` files
|
||||
3. **Data Filtering**: Filter for active/online status only
|
||||
4. **Statistics Generation**: Real-time marketplace statistics from actual data
|
||||
5. **Mock Data Removal**: Eliminate any remaining mock data references
|
||||
|
||||
**Estimated Impact**: ~180 lines reduction, authentic marketplace experience
|
||||
|
||||
---
|
||||
|
||||
## **Next Steps Priority Order**
|
||||
|
||||
### **Immediate Priority 1: ResponseBuilder Migration**
|
||||
1. **Systematic File Migration**: Start with `src/controllers/auth.rs`
|
||||
2. **Pattern Replacement**: Replace all HttpResponse patterns with ResponseBuilder
|
||||
3. **Compilation Testing**: Ensure zero errors after each file
|
||||
4. **Response Standardization**: Verify consistent API structure
|
||||
|
||||
### **Priority 2: DataAggregator Implementation**
|
||||
1. **Service Creation**: Implement DataAggregator in `src/services/`
|
||||
2. **User Data Integration**: Connect to persistent user data files
|
||||
3. **Mock Data Elimination**: Remove any remaining mock data usage
|
||||
4. **Marketplace Enhancement**: Use real user data for authentic experience
|
||||
|
||||
### **Priority 3: Documentation & Testing**
|
||||
1. **Architecture Documentation**: Update all design documents
|
||||
2. **Migration Guides**: Create guides for future AI assistants
|
||||
3. **Testing Validation**: Ensure all migrations work correctly
|
||||
4. **Performance Optimization**: Optimize builder pattern usage
|
||||
|
||||
---
|
||||
|
||||
## **Success Metrics**
|
||||
|
||||
### **Quantitative Goals**:
|
||||
- **Code Reduction**: Target 1,400+ total lines eliminated
|
||||
- **Compilation Errors**: Maintain zero errors throughout migration
|
||||
- **Mock Data**: 100% elimination across entire codebase
|
||||
- **Response Consistency**: 100% API endpoints using ResponseBuilder
|
||||
|
||||
### **Qualitative Goals**:
|
||||
- **Maintainability**: Single source of truth for all patterns
|
||||
- **Scalability**: Industry-standard architecture patterns
|
||||
- **Developer Experience**: Clear, consistent patterns for future development
|
||||
- **Production Readiness**: No mock data, all persistent data usage
|
||||
|
||||
---
|
||||
|
||||
## **Migration Methodology**
|
||||
|
||||
### **Proven Approach** (Based on successful ServiceFactory migration):
|
||||
1. **Systematic Manual Approach**: Line-by-line fixes more effective than scripts
|
||||
2. **Compilation-Driven Development**: Use compiler errors to guide fixes
|
||||
3. **Incremental Testing**: Verify compilation after each major change
|
||||
4. **Documentation First**: Document patterns before implementing
|
||||
5. **Clean Up**: Remove unused imports and dead code
|
||||
|
||||
### **Avoid**:
|
||||
- **Over-reliance on Scripts**: Manual refinement always required
|
||||
- **Bulk Changes**: Can cause compilation cascades
|
||||
- **Mock Data Retention**: Eliminate completely for production readiness
|
||||
|
||||
---
|
||||
|
||||
## **Current Status Summary**
|
||||
|
||||
✅ **Foundation Complete**: ConfigurationBuilder, SessionDataBuilder, ServiceFactory all 100% complete and compiling
|
||||
✅ **API Consolidation**: ResponseBuilder implemented across all controllers
|
||||
❌ **Data Aggregation**: DataAggregator needs implementation
|
||||
📊 **Overall Progress**: **85% Complete** - Foundation complete; API consolidation done; begin data aggregation
|
||||
|
||||
**Next Action**: Implement `DataAggregator` and integrate with controllers/services
|
155
docs/dev/design/archive/vision/parts/log-free-codebase-policy.md
Normal file
155
docs/dev/design/archive/vision/parts/log-free-codebase-policy.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Log-Free Codebase Policy
|
||||
|
||||
## Overview
|
||||
|
||||
The Project Mycelium maintains a **log-free codebase** in main and development branches as a core architectural design decision. This policy ensures clean, production-ready code and simplifies maintenance, debugging, and AI-assisted development.
|
||||
|
||||
## Policy Statement
|
||||
|
||||
**All `log::` statements must be removed from the codebase before merging to main or development branches.**
|
||||
|
||||
## Rationale
|
||||
|
||||
### 1. **Code Cleanliness**
|
||||
- Removes visual clutter and noise from the codebase
|
||||
- Improves code readability and maintainability
|
||||
- Reduces file size and complexity
|
||||
|
||||
### 2. **Production Readiness**
|
||||
- Eliminates potential performance overhead from logging statements
|
||||
- Prevents accidental logging of sensitive information
|
||||
- Ensures consistent behavior across environments
|
||||
|
||||
### 3. **AI-Assisted Development**
|
||||
- Simplifies code analysis and pattern recognition for AI tools
|
||||
- Reduces token count and complexity for AI code generation
|
||||
- Enables more accurate code migrations and refactoring
|
||||
|
||||
### 4. **Maintenance Benefits**
|
||||
- Reduces merge conflicts from logging changes
|
||||
- Eliminates outdated or inconsistent log messages
|
||||
- Simplifies code review process
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### ✅ **Allowed Usage**
|
||||
- **Feature Branches**: Developers may use `log::` statements for troubleshooting and debugging in feature branches
|
||||
- **Local Development**: Temporary logging for development and testing purposes
|
||||
- **Debugging Sessions**: Short-term logging to diagnose specific issues
|
||||
|
||||
### ❌ **Prohibited Usage**
|
||||
- **Main Branch**: No `log::` statements allowed in main branch
|
||||
- **Development Branch**: No `log::` statements allowed in development branch
|
||||
- **Production Code**: No logging statements in production-ready code
|
||||
- **Permanent Logging**: No long-term or permanent logging infrastructure
|
||||
|
||||
## Enforcement
|
||||
|
||||
### 1. **Pre-Merge Cleanup**
|
||||
All feature branches must have `log::` statements removed before creating pull requests:
|
||||
|
||||
```bash
|
||||
# Check for log statements before merging
|
||||
find src -name "*.rs" -exec grep -l "log::" {} \;
|
||||
|
||||
# Remove all log statements (if any found)
|
||||
find src -name "*.rs" -exec perl -i -pe 'BEGIN{undef $/;} s/\s*log::[^;]*;//g' {} \;
|
||||
|
||||
# Verify removal
|
||||
cargo check --lib
|
||||
```
|
||||
|
||||
### 2. **Automated Checks**
|
||||
- CI/CD pipeline should include log statement detection
|
||||
- Pre-commit hooks can prevent accidental commits with logging
|
||||
- Code review checklist includes log statement verification
|
||||
|
||||
### 3. **Migration History**
|
||||
- **2025-01-06**: Removed 881+ log statements from entire codebase
|
||||
- **Dashboard Controller**: 443 log statements removed
|
||||
- **Other Controllers**: 438 log statements removed across 24 files
|
||||
|
||||
## Alternative Approaches
|
||||
|
||||
### For Debugging
|
||||
Instead of permanent logging, use:
|
||||
|
||||
1. **Conditional Compilation**:
|
||||
```rust
|
||||
#[cfg(debug_assertions)]
|
||||
eprintln!("Debug: {}", value);
|
||||
```
|
||||
|
||||
2. **Feature Flags**:
|
||||
```rust
|
||||
#[cfg(feature = "debug-logging")]
|
||||
log::debug!("Debug information");
|
||||
```
|
||||
|
||||
3. **Test-Only Logging**:
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
println!("Test debug: {}", value);
|
||||
```
|
||||
|
||||
### For Production Monitoring
|
||||
- Use external monitoring tools (Prometheus, Grafana)
|
||||
- Implement structured error handling with Result types
|
||||
- Use application metrics instead of log statements
|
||||
- Implement health check endpoints
|
||||
|
||||
## Benefits Realized
|
||||
|
||||
### 1. **Codebase Statistics**
|
||||
- **Before**: 881+ log statements across 24 files
|
||||
- **After**: 0 log statements (100% reduction)
|
||||
- **Compilation**: Clean builds with only minor unused variable warnings
|
||||
|
||||
### 2. **Development Improvements**
|
||||
- Simplified ResponseBuilder migration process
|
||||
- Reduced code complexity for AI-assisted refactoring
|
||||
- Cleaner code reviews and merge processes
|
||||
- Improved code readability and maintainability
|
||||
|
||||
### 3. **Performance Benefits**
|
||||
- Reduced binary size
|
||||
- Eliminated runtime logging overhead
|
||||
- Faster compilation times
|
||||
- Cleaner production deployments
|
||||
|
||||
## Compliance
|
||||
|
||||
### Developer Responsibilities
|
||||
1. **Remove all `log::` statements** before creating pull requests
|
||||
2. **Verify clean builds** after log removal
|
||||
3. **Use alternative debugging approaches** for troubleshooting
|
||||
4. **Follow the log-free policy** consistently across all contributions
|
||||
|
||||
### Code Review Requirements
|
||||
1. **Check for log statements** in all code reviews
|
||||
2. **Verify compilation** after any log-related changes
|
||||
3. **Ensure policy compliance** before approving merges
|
||||
4. **Document any exceptions** (if absolutely necessary)
|
||||
|
||||
## Exceptions
|
||||
|
||||
**No exceptions are currently allowed.** The log-free policy is absolute for main and development branches.
|
||||
|
||||
If logging becomes absolutely necessary for specific use cases, it must be:
|
||||
1. Approved by the architecture team
|
||||
2. Implemented with feature flags
|
||||
3. Documented as an architectural decision
|
||||
4. Reviewed regularly for removal
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Builder Pattern Architecture](./builder-pattern-architecture.md)
|
||||
- [ResponseBuilder Migration Guide](./response-builder-migration.md)
|
||||
- [Code Quality Standards](./code-quality-standards.md)
|
||||
- [AI-Assisted Development Guidelines](./ai-development-guidelines.md)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-01-06
|
||||
**Policy Version**: 1.0
|
||||
**Status**: Active and Enforced
|
@@ -0,0 +1,386 @@
|
||||
# Project Mycelium - Supabase Architecture & Deployment Roadmap 2025
|
||||
|
||||
## 🎯 Executive Summary
|
||||
|
||||
Complete transformation of Project Mycelium from hybrid TFP/USD system with JSON storage to production-ready, highly available platform using industry-standard open source technologies.
|
||||
|
||||
**Key Decisions:**
|
||||
- **Database**: Self-hosted Supabase (PostgreSQL + PostgREST + Auth + Real-time)
|
||||
- **Currency**: Pure USD credits system (eliminate TFP conversion)
|
||||
- **Deployment**: Progressive scaling from single VM to HA k3s cluster
|
||||
- **Infrastructure**: ThreeFold Grid with multi-cloud portability
|
||||
|
||||
---
|
||||
|
||||
## 📊 Current Status Assessment
|
||||
|
||||
### Issues Identified:
|
||||
- ❌ **TFP → Credits Refactor**: PARTIALLY COMPLETE - hybrid logic remains
|
||||
- ❌ **Database**: JSON file storage not scalable for production
|
||||
- ❌ **Single Point of Failure**: Current deployment lacks redundancy
|
||||
- ❌ **Complex Conversion**: TFP/USD conversion scattered throughout codebase
|
||||
|
||||
### Evidence from Codebase:
|
||||
```rust
|
||||
// Current problematic hybrid approach:
|
||||
mock_data.wallet_balance_usd -= rental_cost; // Some USD usage
|
||||
// But TFP references remain in pools.html:
|
||||
// "Buy ThreeFold Points (TFP) with TFT"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Target Architecture
|
||||
|
||||
### Supabase Self-Hosted Stack:
|
||||
- **PostgreSQL**: Core database with ACID compliance
|
||||
- **PostgREST**: Auto-generated REST API from database schema
|
||||
- **GoTrue**: JWT-based authentication service
|
||||
- **Realtime**: WebSocket subscriptions for live updates
|
||||
- **Studio**: Web-based database management
|
||||
|
||||
### Why Supabase Self-Hosted:
|
||||
- ✅ **100% Open Source**: All components under permissive licenses
|
||||
- ✅ **Zero Vendor Lock-in**: Deploy anywhere, migrate anytime
|
||||
- ✅ **Cost Effective**: No managed service fees
|
||||
- ✅ **ThreeFold Aligned**: Decentralized infrastructure
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Three-Phase Deployment Strategy
|
||||
|
||||
### Phase 1: Development & Refactoring (2-4 weeks)
|
||||
|
||||
**Environment Setup:**
|
||||
```bash
|
||||
# Local Ubuntu 24.04 development
|
||||
git clone https://github.com/supabase/supabase
|
||||
cd supabase/docker
|
||||
cp .env.example .env
|
||||
docker compose up -d
|
||||
# Access at http://localhost:8000
|
||||
```
|
||||
|
||||
**Primary Objectives:**
|
||||
|
||||
1. **Complete TFP → USD Refactor**
|
||||
- Remove TFP references from `src/views/dashboard/pools.html`
|
||||
- Eliminate conversion logic in controllers
|
||||
- Update backend storage to pure USD
|
||||
|
||||
2. **Database Schema Design**
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email VARCHAR UNIQUE NOT NULL,
|
||||
wallet_balance_usd DECIMAL(12,2) DEFAULT 0.00,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE transactions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
amount_usd DECIMAL(12,2) NOT NULL,
|
||||
transaction_type VARCHAR NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
3. **Migration Scripts**
|
||||
```rust
|
||||
async fn migrate_user_data() -> Result<(), Error> {
|
||||
let json_files = glob("./user_data/*.json")?;
|
||||
for file_path in json_files {
|
||||
let user_data: UserPersistentData = load_json_file(&file_path)?;
|
||||
// Insert into PostgreSQL via Supabase
|
||||
supabase.from("users").insert(user_data).execute().await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
**Deliverables:**
|
||||
- ✅ Pure USD credits system
|
||||
- ✅ PostgreSQL-based storage
|
||||
- ✅ Supabase API integration
|
||||
- ✅ Local development environment
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Production Deployment (1-2 weeks)
|
||||
|
||||
**Infrastructure:**
|
||||
- **Platform**: ThreeFold Grid VM (Ubuntu 24.04)
|
||||
- **Resources**: 4 CPU, 8GB RAM, 100GB SSD
|
||||
- **Network**: Public IP with SSL
|
||||
|
||||
**Deployment:**
|
||||
```bash
|
||||
# Production Supabase on ThreeFold Grid
|
||||
git clone https://github.com/supabase/supabase
|
||||
cd supabase/docker
|
||||
# Configure production environment
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
```
|
||||
|
||||
**Production Features:**
|
||||
- SSL certificate with Let's Encrypt
|
||||
- Automated PostgreSQL backups
|
||||
- Monitoring and alerting
|
||||
- Performance optimization
|
||||
|
||||
**Deliverables:**
|
||||
- ✅ Production marketplace on ThreeFold Grid
|
||||
- ✅ SSL-secured deployment
|
||||
- ✅ Backup system
|
||||
- ✅ Monitoring setup
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: High Availability (2-3 weeks)
|
||||
|
||||
**Architecture Options:**
|
||||
|
||||
**Option A: ThreeFold Grid HA (Recommended)**
|
||||
```bash
|
||||
# Using tfgrid-k3s (confirmed etcd clustering)
|
||||
git clone https://github.com/ucli-tools/tfgrid-k3s
|
||||
cd tfgrid-k3s
|
||||
# Configure terraform.tfvars
|
||||
make infrastructure # Provision TF Grid VMs
|
||||
make platform # Deploy k3s cluster
|
||||
make app # Deploy Supabase + Marketplace
|
||||
```
|
||||
|
||||
**Option B: Multi-Cloud HA**
|
||||
```bash
|
||||
# Using k3scluster (confirmed etcd clustering)
|
||||
git clone https://github.com/ucli-tools/k3scluster
|
||||
cd k3scluster
|
||||
# Configure ha_cluster.txt
|
||||
make cluster # Deploy HA k3s
|
||||
make app # Deploy applications
|
||||
```
|
||||
|
||||
**Cluster Configuration:**
|
||||
- **Masters**: 3-5 nodes (etcd clustering)
|
||||
- **Workers**: 3+ nodes (workloads)
|
||||
- **Networking**: WireGuard/Mycelium or Tailscale
|
||||
|
||||
**Kubernetes Manifests:**
|
||||
```yaml
|
||||
# PostgreSQL StatefulSet
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgresql-ha
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: postgresql
|
||||
image: postgres:15
|
||||
volumeMounts:
|
||||
- name: postgresql-data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
```
|
||||
|
||||
**Deliverables:**
|
||||
- ✅ Multi-master k3s cluster
|
||||
- ✅ HA Supabase deployment
|
||||
- ✅ Automatic failover
|
||||
- ✅ Horizontal scaling
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Technical Implementation
|
||||
|
||||
### Rust Backend Integration:
|
||||
```rust
|
||||
use supabase_rs::{Supabase, SupabaseClient};
|
||||
|
||||
pub struct MarketplaceDB {
|
||||
client: SupabaseClient,
|
||||
}
|
||||
|
||||
impl MarketplaceDB {
|
||||
pub async fn get_user_balance(&self, user_id: i32) -> Result<Decimal, Error> {
|
||||
let response = self.client
|
||||
.from("users")
|
||||
.select("wallet_balance_usd")
|
||||
.eq("id", user_id.to_string())
|
||||
.execute()
|
||||
.await?;
|
||||
Ok(response.parse()?)
|
||||
}
|
||||
|
||||
pub async fn update_balance(&self, user_id: i32, amount: Decimal) -> Result<(), Error> {
|
||||
self.client
|
||||
.from("users")
|
||||
.update(json!({"wallet_balance_usd": amount}))
|
||||
.eq("id", user_id.to_string())
|
||||
.execute()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Controller Updates:
|
||||
```rust
|
||||
// Pure USD operations - no TFP conversion
|
||||
pub async fn buy_credits(form: web::Json<BuyCreditsRequest>) -> Result<HttpResponse, Error> {
|
||||
let db = MarketplaceDB::new();
|
||||
let amount_usd = form.amount; // Direct USD, no conversion
|
||||
|
||||
let current_balance = db.get_user_balance(user_id).await?;
|
||||
let new_balance = current_balance + amount_usd;
|
||||
|
||||
db.update_balance(user_id, new_balance).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(json!({
|
||||
"success": true,
|
||||
"new_balance": new_balance
|
||||
})))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Migration Procedures
|
||||
|
||||
### Data Migration Strategy:
|
||||
```rust
|
||||
pub async fn migrate_all_user_data() -> Result<(), Error> {
|
||||
let db = MarketplaceDB::new();
|
||||
|
||||
for entry in glob("./user_data/*.json")? {
|
||||
let user_data: Value = serde_json::from_str(&fs::read_to_string(entry?)?)?;
|
||||
|
||||
// Extract and migrate user data
|
||||
let email = user_data["email"].as_str().unwrap();
|
||||
let balance = user_data["wallet_balance_usd"].as_f64().unwrap_or(0.0);
|
||||
|
||||
db.create_user(email, balance.into()).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Validation:
|
||||
```rust
|
||||
pub async fn validate_migration() -> Result<bool, Error> {
|
||||
let db = MarketplaceDB::new();
|
||||
let user_count = db.count_users().await?;
|
||||
let total_balance = db.sum_all_balances().await?;
|
||||
|
||||
let json_stats = analyze_json_files().await?;
|
||||
|
||||
Ok(user_count == json_stats.user_count &&
|
||||
(total_balance - json_stats.total_balance).abs() < Decimal::new(1, 2))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Security Implementation
|
||||
|
||||
### Row Level Security:
|
||||
```sql
|
||||
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "Users view own data" ON users
|
||||
FOR SELECT USING (auth.uid()::text = uuid::text);
|
||||
```
|
||||
|
||||
### JWT Validation:
|
||||
```rust
|
||||
pub async fn validate_jwt(req: ServiceRequest) -> Result<ServiceResponse, Error> {
|
||||
let token = extract_bearer_token(&req)?;
|
||||
let claims = decode::<Claims>(token, &DecodingKey::from_secret(JWT_SECRET.as_bytes()))?;
|
||||
req.extensions_mut().insert(claims);
|
||||
Ok(next.call(req).await?)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring & Backup
|
||||
|
||||
### Health Checks:
|
||||
```rust
|
||||
pub async fn health_check() -> Result<HttpResponse, Error> {
|
||||
let db = MarketplaceDB::new();
|
||||
let db_status = match db.ping().await {
|
||||
Ok(_) => "healthy",
|
||||
Err(_) => "unhealthy",
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(json!({
|
||||
"status": db_status,
|
||||
"timestamp": chrono::Utc::now()
|
||||
})))
|
||||
}
|
||||
```
|
||||
|
||||
### Automated Backups:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Daily backup script
|
||||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||
docker exec supabase-db pg_dump -U postgres marketplace > \
|
||||
"/var/backups/marketplace_$TIMESTAMP.sql"
|
||||
gzip "/var/backups/marketplace_$TIMESTAMP.sql"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Success Metrics
|
||||
|
||||
### Phase 1 Success Criteria:
|
||||
- ✅ Zero TFP references in codebase
|
||||
- ✅ All data in PostgreSQL
|
||||
- ✅ Local environment functional
|
||||
- ✅ All features working
|
||||
|
||||
### Phase 2 Success Criteria:
|
||||
- ✅ Production on ThreeFold Grid
|
||||
- ✅ SSL configured
|
||||
- ✅ Backups running
|
||||
- ✅ Performance optimized
|
||||
|
||||
### Phase 3 Success Criteria:
|
||||
- ✅ Zero downtime on failures
|
||||
- ✅ Automatic scaling
|
||||
- ✅ Sub-second response times
|
||||
- ✅ Disaster recovery ready
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Implementation Timeline
|
||||
|
||||
**Week 1-2**: TFP refactor completion, local Supabase setup
|
||||
**Week 3-4**: Database migration, testing
|
||||
**Week 5-6**: Production deployment on ThreeFold Grid
|
||||
**Week 7-8**: HA cluster setup (optional)
|
||||
**Week 9-10**: Performance optimization, documentation
|
||||
**Week 11-12**: Load testing, final deployment
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation Deliverables
|
||||
|
||||
- [ ] Database schema documentation
|
||||
- [ ] API endpoint documentation
|
||||
- [ ] Deployment guides for each phase
|
||||
- [ ] Migration scripts and procedures
|
||||
- [ ] Monitoring and alerting setup
|
||||
- [ ] Backup and recovery procedures
|
||||
- [ ] Security hardening guide
|
||||
- [ ] Performance tuning guide
|
||||
- [ ] Troubleshooting documentation
|
||||
- [ ] Scaling procedures
|
||||
|
||||
---
|
||||
|
||||
This roadmap provides a clear, actionable path from the current hybrid system to a production-ready, highly available marketplace built on proven open source technologies with complete infrastructure independence.
|
269
docs/dev/design/archive/vision/parts/road-taken.md
Normal file
269
docs/dev/design/archive/vision/parts/road-taken.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# Project Mycelium: Road Taken - Decision Trail
|
||||
|
||||
**Document Purpose**: Track architectural decisions, refactoring choices, and evolution path for future DevOps teams and maintainers.
|
||||
|
||||
**Last Updated**: 2025-08-04
|
||||
**Status**: Active Development
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Decision Log](#decision-log)
|
||||
2. [Architecture Evolution](#architecture-evolution)
|
||||
3. [Refactoring Decisions](#refactoring-decisions)
|
||||
4. [Technical Debt Resolution](#technical-debt-resolution)
|
||||
5. [Future Considerations](#future-considerations)
|
||||
|
||||
---
|
||||
|
||||
## Decision Log
|
||||
|
||||
### Decision #001: TFP → TFC Credits System Refactor
|
||||
**Date**: 2025-08-04
|
||||
**Status**: ✅ **APPROVED** - Implementation in Progress
|
||||
**Decision Makers**: Development Team
|
||||
|
||||
#### **Problem Statement**
|
||||
The marketplace used a hybrid TFP (ThreeFold Points) to USD conversion system that created:
|
||||
- Complex conversion logic throughout the codebase
|
||||
- Confusing user experience with dual currency displays
|
||||
- Maintenance overhead with exchange rate calculations
|
||||
- Difficulty in adjusting credit values without code changes
|
||||
|
||||
#### **Options Considered**
|
||||
|
||||
| Option | Description | Pros | Cons | Decision |
|
||||
|--------|-------------|------|------|----------|
|
||||
| **A. Keep Hybrid TFP/USD** | Maintain current dual system | No breaking changes | Complex, confusing, hard to maintain | ❌ Rejected |
|
||||
| **B. Pure USD Only** | Remove TFP entirely, use USD | Simple, familiar | Loses ThreeFold branding | ❌ Rejected |
|
||||
| **C. TFP → TFC (Configurable)** | Rename to ThreeFold Credits, 1 TFC = 1 USD (configurable) | Clean, brandable, flexible, industry standard | Requires refactor | ✅ **SELECTED** |
|
||||
|
||||
#### **Decision Rationale**
|
||||
- **Industry Standard**: Follows AWS Credits, Google Cloud Credits, Azure Credits model
|
||||
- **User Experience**: Simple mental model (1 credit = 1 dollar)
|
||||
- **Developer Experience**: Clean codebase, no conversion logic
|
||||
- **Business Flexibility**: Credit value adjustable via configuration
|
||||
- **Maintainability**: Single currency system, easier to debug and extend
|
||||
|
||||
#### **Implementation Strategy**
|
||||
1. **Phase 1**: Global rename TFP → TFC throughout codebase
|
||||
2. **Phase 2**: Remove all conversion logic and exchange rate calculations
|
||||
3. **Phase 3**: Implement configurable credit value system
|
||||
4. **Phase 4**: Update UI/UX to reflect simplified credit system
|
||||
|
||||
#### **Success Criteria**
|
||||
- [ ] Zero TFP references remain in codebase
|
||||
- [ ] All calculations use TFC directly (no conversions)
|
||||
- [ ] Credit value configurable via admin panel/config file
|
||||
- [ ] User interface shows clear TFC branding
|
||||
- [ ] All tests pass with new credit system
|
||||
|
||||
#### **Impact Assessment**
|
||||
- **Breaking Changes**: Yes - API responses change from TFP to TFC
|
||||
- **Database Migration**: Required - update all currency fields
|
||||
- **User Impact**: Positive - simpler, clearer credit system
|
||||
- **DevOps Impact**: Simplified deployment, fewer config parameters
|
||||
|
||||
---
|
||||
|
||||
### Decision #002: Database Migration to Supabase
|
||||
**Date**: 2025-08-04
|
||||
**Status**: ✅ **APPROVED** - Architecture Documented
|
||||
**Decision Makers**: Development Team
|
||||
|
||||
#### **Problem Statement**
|
||||
Current JSON file-based storage with git versioning is not scalable for production use.
|
||||
|
||||
#### **Solution Selected**
|
||||
Supabase self-hosted (PostgreSQL + PostgREST + Auth + Realtime) for:
|
||||
- Scalable database backend
|
||||
- REST API auto-generation
|
||||
- Built-in authentication
|
||||
- Real-time subscriptions
|
||||
- Full open-source stack
|
||||
|
||||
#### **Deployment Strategy**
|
||||
- **Phase 1**: Local development with Docker Compose
|
||||
- **Phase 2**: Production single-node on ThreeFold Grid
|
||||
- **Phase 3**: High Availability k3s cluster deployment
|
||||
|
||||
---
|
||||
|
||||
### Decision #003: High Availability Architecture
|
||||
**Date**: 2025-08-04
|
||||
**Status**: ✅ **APPROVED** - Phase 3 Implementation
|
||||
**Decision Makers**: Development Team
|
||||
|
||||
#### **Problem Statement**
|
||||
Need enterprise-grade high availability with no single point of failure.
|
||||
|
||||
#### **Solution Selected**
|
||||
k3s clusters with embedded etcd for true multi-master HA:
|
||||
- **ThreeFold Grid**: Use `tfgrid-k3s` automation (Terraform + Ansible + WireGuard)
|
||||
- **Multi-Cloud**: Use `k3scluster` automation (Tailscale networking)
|
||||
- **Configuration**: 3-5 master nodes + multiple workers
|
||||
|
||||
#### **Benefits**
|
||||
- Zero downtime deployments
|
||||
- Automatic failover
|
||||
- Horizontal scaling
|
||||
- Industry-standard tooling
|
||||
|
||||
---
|
||||
|
||||
## Architecture Evolution
|
||||
|
||||
### Phase 1: Legacy Architecture (Pre-2025)
|
||||
```
|
||||
[Frontend] → [Rust Backend] → [JSON Files + Git]
|
||||
↓
|
||||
[TFP ↔ USD Conversion Logic]
|
||||
```
|
||||
|
||||
**Problems**:
|
||||
- Non-scalable file-based storage
|
||||
- Complex dual currency system
|
||||
- Manual git-based versioning
|
||||
|
||||
### Phase 2: Current Transition (2025)
|
||||
```
|
||||
[Frontend] → [Rust Backend] → [Supabase PostgreSQL]
|
||||
↓ ↓
|
||||
[TFC Credits Only] [PostgREST API]
|
||||
↓
|
||||
[Auth + Realtime]
|
||||
```
|
||||
|
||||
**Improvements**:
|
||||
- Scalable PostgreSQL database
|
||||
- Simplified single currency (TFC)
|
||||
- Auto-generated REST API
|
||||
- Built-in authentication
|
||||
|
||||
### Phase 3: Target HA Architecture (Future)
|
||||
```
|
||||
[Load Balancer] → [k3s Cluster]
|
||||
↓
|
||||
[Multiple Rust Backend Pods] → [HA PostgreSQL Cluster]
|
||||
↓ ↓
|
||||
[TFC Credits System] [Supabase Stack]
|
||||
```
|
||||
|
||||
**Benefits**:
|
||||
- No single point of failure
|
||||
- Horizontal scaling
|
||||
- Enterprise-grade reliability
|
||||
|
||||
---
|
||||
|
||||
## Refactoring Decisions
|
||||
|
||||
### TFP → TFC Refactor Details
|
||||
|
||||
#### **Naming Conventions**
|
||||
- **Old**: TFP, ThreeFold Points, tfp_amount, buyTFP()
|
||||
- **New**: TFC, ThreeFold Credits, tfc_amount, buyCredits()
|
||||
|
||||
#### **Code Changes Required**
|
||||
|
||||
| Component | Changes | Files Affected |
|
||||
|-----------|---------|----------------|
|
||||
| **Rust Backend** | Remove conversion logic, update structs | `models/`, `controllers/`, `services/` |
|
||||
| **Frontend HTML** | Update all UI text and form labels | `views/dashboard/` |
|
||||
| **JavaScript** | Rename functions and variables | `*.html` inline JS |
|
||||
| **Database Schema** | Migrate currency fields | Migration scripts |
|
||||
| **API Endpoints** | Update request/response formats | `routes/mod.rs` |
|
||||
|
||||
#### **Configuration System**
|
||||
```rust
|
||||
// New configurable credit system
|
||||
pub struct MarketplaceConfig {
|
||||
pub tfc_to_usd_rate: Decimal, // Default: 1.0
|
||||
pub credit_currency_code: String, // "TFC"
|
||||
pub credit_display_name: String, // "ThreeFold Credits"
|
||||
pub credit_symbol: String, // "TFC"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Debt Resolution
|
||||
|
||||
### Resolved Issues
|
||||
1. **Hybrid Currency Complexity** → Single TFC system
|
||||
2. **File-based Storage** → PostgreSQL database
|
||||
3. **Manual Scaling** → Container orchestration ready
|
||||
|
||||
### Remaining Technical Debt
|
||||
- [ ] Legacy API endpoints (maintain backward compatibility)
|
||||
- [ ] Old configuration format migration
|
||||
- [ ] Performance optimization for high-traffic scenarios
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Potential Enhancements
|
||||
1. **Multi-Currency Support**: If needed, add other currencies while keeping TFC as base
|
||||
2. **Credit Packages**: Bulk credit purchase discounts
|
||||
3. **Credit Expiration**: Optional expiry dates for credits
|
||||
4. **Credit Transfers**: Peer-to-peer credit transfers
|
||||
5. **Credit Analytics**: Usage tracking and reporting
|
||||
|
||||
### Scalability Roadmap
|
||||
1. **Phase 1**: Single-node production (current)
|
||||
2. **Phase 2**: Multi-node k3s cluster
|
||||
3. **Phase 3**: Multi-region deployment
|
||||
4. **Phase 4**: Global CDN and edge computing
|
||||
|
||||
### Monitoring & Observability
|
||||
- Application metrics via Prometheus
|
||||
- Log aggregation via Loki
|
||||
- Distributed tracing via Jaeger
|
||||
- Health checks and alerting
|
||||
|
||||
---
|
||||
|
||||
## DevOps Guidelines
|
||||
|
||||
### For Future Maintainers
|
||||
|
||||
#### **Understanding the Credit System**
|
||||
- **TFC = ThreeFold Credits**: The single currency used throughout
|
||||
- **1 TFC = 1 USD by default**: Configurable via `marketplace.config`
|
||||
- **No Conversions**: All calculations use TFC directly
|
||||
|
||||
#### **Configuration Management**
|
||||
```bash
|
||||
# Adjust credit value
|
||||
export TFC_USD_RATE=1.0 # 1 TFC = 1 USD
|
||||
|
||||
# Update via config file
|
||||
echo "tfc_to_usd_rate = 1.0" >> marketplace.config
|
||||
```
|
||||
|
||||
#### **Database Schema**
|
||||
- All currency fields store TFC amounts
|
||||
- No exchange rate tables needed
|
||||
- Simple decimal precision for credits
|
||||
|
||||
#### **Deployment Notes**
|
||||
- Use provided Docker Compose for development
|
||||
- Follow k3s automation for production HA
|
||||
- Monitor credit balance calculations in logs
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The TFP → TFC refactor represents a strategic shift toward:
|
||||
- **Simplicity**: Single currency system
|
||||
- **Flexibility**: Configurable credit values
|
||||
- **Maintainability**: Clean, documented codebase
|
||||
- **Scalability**: Modern database and orchestration
|
||||
|
||||
This decision trail ensures future teams understand the rationale and can continue evolving the marketplace architecture effectively.
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**: Execute TFP → TFC refactor implementation as documented in the architecture plan.
|
@@ -0,0 +1,633 @@
|
||||
# ThreeFold Grid Deployment Automation Specification
|
||||
|
||||
**Document Purpose**: Technical specification for automated deployment pipeline from TFC payment to ThreeFold Grid service activation.
|
||||
|
||||
**Last Updated**: 2025-08-04
|
||||
**Status**: Implementation Ready
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the automated deployment pipeline that converts TFC credits to TFT tokens and deploys services on the ThreeFold Grid, providing seamless Web2-to-Web3 bridge functionality.
|
||||
|
||||
---
|
||||
|
||||
## Deployment Pipeline Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[User Purchase Complete] --> B[TFC Deducted]
|
||||
B --> C[Deployment Queued]
|
||||
C --> D[Commission Calculated]
|
||||
D --> E[USD → TFT Conversion]
|
||||
E --> F[Grid Node Selection]
|
||||
F --> G[Resource Allocation]
|
||||
G --> H[Service Deployment]
|
||||
H --> I[Health Check]
|
||||
I --> J{Deployment Success?}
|
||||
J -->|Yes| K[Service Active]
|
||||
J -->|No| L[Rollback & Refund]
|
||||
K --> M[User Notification]
|
||||
L --> N[Error Notification]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. Deployment Queue Service
|
||||
|
||||
```rust
|
||||
// src/services/deployment_queue.rs
|
||||
use tokio::sync::mpsc;
|
||||
use std::collections::VecDeque;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DeploymentJob {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub service_spec: ServiceSpec,
|
||||
pub tfc_amount: Decimal,
|
||||
pub commission_amount: Decimal,
|
||||
pub grid_payment_amount: Decimal,
|
||||
pub priority: DeploymentPriority,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub max_retries: u32,
|
||||
pub retry_count: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum DeploymentPriority {
|
||||
Low,
|
||||
Normal,
|
||||
High,
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServiceSpec {
|
||||
pub service_type: ServiceType,
|
||||
pub cpu_cores: u32,
|
||||
pub memory_gb: u32,
|
||||
pub storage_gb: u32,
|
||||
pub network_config: NetworkConfig,
|
||||
pub environment_vars: HashMap<String, String>,
|
||||
pub docker_image: Option<String>,
|
||||
pub kubernetes_manifest: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServiceType {
|
||||
VM { os: String, version: String },
|
||||
Container { image: String, ports: Vec<u16> },
|
||||
KubernetesCluster { node_count: u32 },
|
||||
Storage { storage_type: String },
|
||||
Application { app_id: String },
|
||||
}
|
||||
|
||||
pub struct DeploymentQueueService {
|
||||
queue: Arc<Mutex<VecDeque<DeploymentJob>>>,
|
||||
workers: Vec<DeploymentWorker>,
|
||||
tx: mpsc::UnboundedSender<DeploymentJob>,
|
||||
rx: Arc<Mutex<mpsc::UnboundedReceiver<DeploymentJob>>>,
|
||||
}
|
||||
|
||||
impl DeploymentQueueService {
|
||||
pub fn new(worker_count: usize) -> Self {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let queue = Arc::new(Mutex::new(VecDeque::new()));
|
||||
|
||||
let mut workers = Vec::new();
|
||||
for i in 0..worker_count {
|
||||
workers.push(DeploymentWorker::new(i, rx.clone()));
|
||||
}
|
||||
|
||||
Self {
|
||||
queue,
|
||||
workers,
|
||||
tx,
|
||||
rx: Arc::new(Mutex::new(rx)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn queue_deployment(&self, job: DeploymentJob) -> Result<(), DeploymentError> {
|
||||
// Add to persistent queue
|
||||
self.queue.lock().await.push_back(job.clone());
|
||||
|
||||
// Send to worker pool
|
||||
self.tx.send(job).map_err(|_| DeploymentError::QueueFull)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start_workers(&self) {
|
||||
for worker in &self.workers {
|
||||
worker.start().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. ThreeFold Grid Integration
|
||||
|
||||
```rust
|
||||
// src/services/threefold_grid.rs
|
||||
use reqwest::Client;
|
||||
use serde_json::json;
|
||||
|
||||
pub struct ThreeFoldGridService {
|
||||
client: Client,
|
||||
grid_proxy_url: String,
|
||||
tft_wallet: TFTWallet,
|
||||
}
|
||||
|
||||
impl ThreeFoldGridService {
|
||||
pub async fn deploy_vm(
|
||||
&self,
|
||||
spec: &ServiceSpec,
|
||||
tft_amount: Decimal,
|
||||
) -> Result<GridDeployment, GridError> {
|
||||
// 1. Find suitable nodes
|
||||
let nodes = self.find_suitable_nodes(spec).await?;
|
||||
let selected_node = self.select_best_node(&nodes, spec)?;
|
||||
|
||||
// 2. Create deployment contract
|
||||
let contract = self.create_deployment_contract(spec, &selected_node, tft_amount).await?;
|
||||
|
||||
// 3. Deploy on grid
|
||||
let deployment = self.execute_deployment(contract).await?;
|
||||
|
||||
// 4. Wait for deployment to be ready
|
||||
self.wait_for_deployment_ready(&deployment.id, Duration::from_secs(300)).await?;
|
||||
|
||||
// 5. Get connection details
|
||||
let connection_info = self.get_deployment_info(&deployment.id).await?;
|
||||
|
||||
Ok(GridDeployment {
|
||||
id: deployment.id,
|
||||
node_id: selected_node.id,
|
||||
contract_id: contract.id,
|
||||
connection_info,
|
||||
status: DeploymentStatus::Active,
|
||||
created_at: Utc::now(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn find_suitable_nodes(&self, spec: &ServiceSpec) -> Result<Vec<GridNode>, GridError> {
|
||||
let query = json!({
|
||||
"cpu": spec.cpu_cores,
|
||||
"memory": spec.memory_gb * 1024 * 1024 * 1024, // Convert GB to bytes
|
||||
"storage": spec.storage_gb * 1024 * 1024 * 1024,
|
||||
"status": "up",
|
||||
"available": true
|
||||
});
|
||||
|
||||
let response = self.client
|
||||
.post(&format!("{}/nodes/search", self.grid_proxy_url))
|
||||
.json(&query)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let nodes: Vec<GridNode> = response.json().await?;
|
||||
Ok(nodes)
|
||||
}
|
||||
|
||||
fn select_best_node(&self, nodes: &[GridNode], spec: &ServiceSpec) -> Result<GridNode, GridError> {
|
||||
// Node selection algorithm:
|
||||
// 1. Filter by resource availability
|
||||
// 2. Prefer nodes with better uptime
|
||||
// 3. Consider geographic proximity
|
||||
// 4. Balance load across nodes
|
||||
|
||||
let suitable_nodes: Vec<_> = nodes.iter()
|
||||
.filter(|node| {
|
||||
node.available_cpu >= spec.cpu_cores &&
|
||||
node.available_memory >= spec.memory_gb * 1024 * 1024 * 1024 &&
|
||||
node.available_storage >= spec.storage_gb * 1024 * 1024 * 1024 &&
|
||||
node.uptime_percentage > 95.0
|
||||
})
|
||||
.collect();
|
||||
|
||||
if suitable_nodes.is_empty() {
|
||||
return Err(GridError::NoSuitableNodes);
|
||||
}
|
||||
|
||||
// Select node with best score (uptime + available resources)
|
||||
let best_node = suitable_nodes.iter()
|
||||
.max_by(|a, b| {
|
||||
let score_a = a.uptime_percentage + (a.available_cpu as f64 * 10.0);
|
||||
let score_b = b.uptime_percentage + (b.available_cpu as f64 * 10.0);
|
||||
score_a.partial_cmp(&score_b).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok((*best_node).clone())
|
||||
}
|
||||
|
||||
async fn create_deployment_contract(
|
||||
&self,
|
||||
spec: &ServiceSpec,
|
||||
node: &GridNode,
|
||||
tft_amount: Decimal,
|
||||
) -> Result<DeploymentContract, GridError> {
|
||||
match &spec.service_type {
|
||||
ServiceType::VM { os, version } => {
|
||||
self.create_vm_contract(spec, node, os, version, tft_amount).await
|
||||
}
|
||||
ServiceType::Container { image, ports } => {
|
||||
self.create_container_contract(spec, node, image, ports, tft_amount).await
|
||||
}
|
||||
ServiceType::KubernetesCluster { node_count } => {
|
||||
self.create_k8s_contract(spec, node, *node_count, tft_amount).await
|
||||
}
|
||||
ServiceType::Storage { storage_type } => {
|
||||
self.create_storage_contract(spec, node, storage_type, tft_amount).await
|
||||
}
|
||||
ServiceType::Application { app_id } => {
|
||||
self.create_app_contract(spec, node, app_id, tft_amount).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_vm_contract(
|
||||
&self,
|
||||
spec: &ServiceSpec,
|
||||
node: &GridNode,
|
||||
os: &str,
|
||||
version: &str,
|
||||
tft_amount: Decimal,
|
||||
) -> Result<DeploymentContract, GridError> {
|
||||
let vm_config = json!({
|
||||
"type": "vm",
|
||||
"node_id": node.id,
|
||||
"cpu": spec.cpu_cores,
|
||||
"memory": spec.memory_gb,
|
||||
"storage": spec.storage_gb,
|
||||
"os": os,
|
||||
"version": version,
|
||||
"network": spec.network_config,
|
||||
"environment": spec.environment_vars,
|
||||
"payment": {
|
||||
"amount": tft_amount,
|
||||
"currency": "TFT"
|
||||
}
|
||||
});
|
||||
|
||||
let response = self.client
|
||||
.post(&format!("{}/deployments/vm", self.grid_proxy_url))
|
||||
.json(&vm_config)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let contract: DeploymentContract = response.json().await?;
|
||||
Ok(contract)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. TFT Conversion Service
|
||||
|
||||
```rust
|
||||
// src/services/tft_conversion.rs
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
pub struct TFTConversionService {
|
||||
grid_client: ThreeFoldGridService,
|
||||
price_oracle: TFTPriceOracle,
|
||||
}
|
||||
|
||||
impl TFTConversionService {
|
||||
pub async fn convert_usd_to_tft(&self, usd_amount: Decimal) -> Result<TFTConversion, ConversionError> {
|
||||
// Get current TFT/USD rate
|
||||
let tft_rate = self.price_oracle.get_tft_usd_rate().await?;
|
||||
let tft_amount = usd_amount / tft_rate;
|
||||
|
||||
// Add small buffer for price fluctuations (2%)
|
||||
let tft_with_buffer = tft_amount * Decimal::from_str("1.02")?;
|
||||
|
||||
Ok(TFTConversion {
|
||||
usd_amount,
|
||||
tft_rate,
|
||||
tft_amount: tft_with_buffer,
|
||||
conversion_timestamp: Utc::now(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn execute_conversion(&self, conversion: &TFTConversion) -> Result<TFTTransaction, ConversionError> {
|
||||
// Execute the actual TFT purchase/conversion
|
||||
// This would integrate with TFT DEX or direct wallet operations
|
||||
|
||||
let transaction = self.grid_client.tft_wallet.convert_usd_to_tft(
|
||||
conversion.usd_amount,
|
||||
conversion.tft_amount,
|
||||
).await?;
|
||||
|
||||
Ok(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TFTConversion {
|
||||
pub usd_amount: Decimal,
|
||||
pub tft_rate: Decimal,
|
||||
pub tft_amount: Decimal,
|
||||
pub conversion_timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub struct TFTPriceOracle {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl TFTPriceOracle {
|
||||
pub async fn get_tft_usd_rate(&self) -> Result<Decimal, PriceError> {
|
||||
// Get TFT price from multiple sources and average
|
||||
let sources = vec![
|
||||
self.get_rate_from_dex().await,
|
||||
self.get_rate_from_coingecko().await,
|
||||
self.get_rate_from_grid_stats().await,
|
||||
];
|
||||
|
||||
let valid_rates: Vec<Decimal> = sources.into_iter()
|
||||
.filter_map(|r| r.ok())
|
||||
.collect();
|
||||
|
||||
if valid_rates.is_empty() {
|
||||
return Err(PriceError::NoValidSources);
|
||||
}
|
||||
|
||||
// Calculate average rate
|
||||
let sum: Decimal = valid_rates.iter().sum();
|
||||
let average = sum / Decimal::from(valid_rates.len());
|
||||
|
||||
Ok(average)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Deployment Worker
|
||||
|
||||
```rust
|
||||
// src/services/deployment_worker.rs
|
||||
pub struct DeploymentWorker {
|
||||
id: usize,
|
||||
grid_service: Arc<ThreeFoldGridService>,
|
||||
tft_service: Arc<TFTConversionService>,
|
||||
notification_service: Arc<NotificationService>,
|
||||
}
|
||||
|
||||
impl DeploymentWorker {
|
||||
pub async fn process_deployment(&self, job: DeploymentJob) -> Result<(), DeploymentError> {
|
||||
log::info!("Worker {} processing deployment {}", self.id, job.id);
|
||||
|
||||
// Update status to "processing"
|
||||
self.update_deployment_status(&job.id, DeploymentStatus::Processing).await?;
|
||||
|
||||
// 1. Convert USD to TFT
|
||||
let conversion = self.tft_service.convert_usd_to_tft(job.grid_payment_amount).await?;
|
||||
let tft_transaction = self.tft_service.execute_conversion(&conversion).await?;
|
||||
|
||||
// 2. Deploy on ThreeFold Grid
|
||||
let grid_deployment = self.grid_service.deploy_vm(&job.service_spec, conversion.tft_amount).await?;
|
||||
|
||||
// 3. Update deployment record
|
||||
self.update_deployment_with_grid_info(&job.id, &grid_deployment, &tft_transaction).await?;
|
||||
|
||||
// 4. Verify deployment health
|
||||
self.verify_deployment_health(&grid_deployment).await?;
|
||||
|
||||
// 5. Update status to "active"
|
||||
self.update_deployment_status(&job.id, DeploymentStatus::Active).await?;
|
||||
|
||||
// 6. Notify user
|
||||
self.notification_service.send_deployment_success(&job.user_id, &job.id).await?;
|
||||
|
||||
log::info!("Worker {} completed deployment {}", self.id, job.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_deployment_failure(&self, job: &DeploymentJob, error: &DeploymentError) {
|
||||
log::error!("Deployment {} failed: {:?}", job.id, error);
|
||||
|
||||
// Update status to failed
|
||||
self.update_deployment_status(&job.id, DeploymentStatus::Failed).await.ok();
|
||||
|
||||
// Refund TFC credits to user
|
||||
if let Err(refund_error) = self.refund_tfc_credits(job).await {
|
||||
log::error!("Failed to refund TFC for deployment {}: {:?}", job.id, refund_error);
|
||||
}
|
||||
|
||||
// Notify user of failure
|
||||
self.notification_service.send_deployment_failure(&job.user_id, &job.id, error).await.ok();
|
||||
}
|
||||
|
||||
async fn refund_tfc_credits(&self, job: &DeploymentJob) -> Result<(), RefundError> {
|
||||
// Refund the full TFC amount back to user
|
||||
self.tfc_service.add_tfc_credits(
|
||||
&job.user_id,
|
||||
job.tfc_amount,
|
||||
&format!("Refund for failed deployment {}", job.id),
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Service Management Dashboard
|
||||
|
||||
### 1. Real-time Deployment Status
|
||||
|
||||
```html
|
||||
<!-- Deployment Status Component -->
|
||||
<div class="deployment-status-card">
|
||||
<div class="card-header">
|
||||
<h5>Deployment Status</h5>
|
||||
<span class="status-badge status-{{deployment.status}}">{{deployment.status}}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="progress-timeline">
|
||||
<div class="timeline-step {{#if deployment.payment_completed}}completed{{/if}}">
|
||||
<i class="bi bi-credit-card"></i>
|
||||
<span>Payment Processed</span>
|
||||
</div>
|
||||
<div class="timeline-step {{#if deployment.queued}}completed{{/if}}">
|
||||
<i class="bi bi-clock"></i>
|
||||
<span>Queued for Deployment</span>
|
||||
</div>
|
||||
<div class="timeline-step {{#if deployment.converting}}completed{{/if}}">
|
||||
<i class="bi bi-arrow-left-right"></i>
|
||||
<span>Converting TFC → TFT</span>
|
||||
</div>
|
||||
<div class="timeline-step {{#if deployment.deploying}}completed{{/if}}">
|
||||
<i class="bi bi-cloud-upload"></i>
|
||||
<span>Deploying on Grid</span>
|
||||
</div>
|
||||
<div class="timeline-step {{#if deployment.active}}completed{{/if}}">
|
||||
<i class="bi bi-check-circle"></i>
|
||||
<span>Service Active</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if deployment.active}}
|
||||
<div class="connection-details">
|
||||
<h6>Connection Details</h6>
|
||||
<div class="detail-item">
|
||||
<label>IP Address:</label>
|
||||
<span class="copyable">{{deployment.ip_address}}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<label>SSH Access:</label>
|
||||
<code class="copyable">ssh root@{{deployment.ip_address}}</code>
|
||||
</div>
|
||||
{{#if deployment.web_url}}
|
||||
<div class="detail-item">
|
||||
<label>Web Interface:</label>
|
||||
<a href="{{deployment.web_url}}" target="_blank">{{deployment.web_url}}</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="deployment-metrics">
|
||||
<div class="metric">
|
||||
<label>TFC Spent:</label>
|
||||
<span>{{deployment.tfc_amount}} TFC</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<label>TFT Converted:</label>
|
||||
<span>{{deployment.tft_amount}} TFT</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<label>Grid Node:</label>
|
||||
<span>{{deployment.node_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. Service Management Controls
|
||||
|
||||
```javascript
|
||||
// Service Management Functions
|
||||
class ServiceManager {
|
||||
constructor(deploymentId) {
|
||||
this.deploymentId = deploymentId;
|
||||
this.statusPolling = null;
|
||||
}
|
||||
|
||||
startStatusPolling() {
|
||||
this.statusPolling = setInterval(async () => {
|
||||
await this.updateDeploymentStatus();
|
||||
}, 5000); // Poll every 5 seconds
|
||||
}
|
||||
|
||||
async updateDeploymentStatus() {
|
||||
try {
|
||||
const response = await fetch(`/api/deployments/${this.deploymentId}/status`);
|
||||
const deployment = await response.json();
|
||||
|
||||
this.updateStatusDisplay(deployment);
|
||||
|
||||
// Stop polling if deployment is complete or failed
|
||||
if (deployment.status === 'Active' || deployment.status === 'Failed') {
|
||||
this.stopStatusPolling();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update deployment status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
updateStatusDisplay(deployment) {
|
||||
// Update progress timeline
|
||||
document.querySelectorAll('.timeline-step').forEach((step, index) => {
|
||||
const stepStates = ['payment_completed', 'queued', 'converting', 'deploying', 'active'];
|
||||
if (deployment[stepStates[index]]) {
|
||||
step.classList.add('completed');
|
||||
}
|
||||
});
|
||||
|
||||
// Update status badge
|
||||
const statusBadge = document.querySelector('.status-badge');
|
||||
statusBadge.className = `status-badge status-${deployment.status.toLowerCase()}`;
|
||||
statusBadge.textContent = deployment.status;
|
||||
|
||||
// Update connection details if active
|
||||
if (deployment.status === 'Active' && deployment.connection_info) {
|
||||
this.showConnectionDetails(deployment.connection_info);
|
||||
}
|
||||
}
|
||||
|
||||
async restartService() {
|
||||
try {
|
||||
const response = await fetch(`/api/deployments/${this.deploymentId}/restart`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showSuccessToast('Service restart initiated');
|
||||
this.startStatusPolling();
|
||||
} else {
|
||||
showErrorToast('Failed to restart service');
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast('Failed to restart service');
|
||||
}
|
||||
}
|
||||
|
||||
async stopService() {
|
||||
if (confirm('Are you sure you want to stop this service? This action cannot be undone.')) {
|
||||
try {
|
||||
const response = await fetch(`/api/deployments/${this.deploymentId}/stop`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showSuccessToast('Service stopped successfully');
|
||||
window.location.href = '/dashboard/deployments';
|
||||
} else {
|
||||
showErrorToast('Failed to stop service');
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast('Failed to stop service');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration & Environment
|
||||
|
||||
```toml
|
||||
# config/deployment.toml
|
||||
[deployment_queue]
|
||||
worker_count = 4
|
||||
max_queue_size = 1000
|
||||
retry_attempts = 3
|
||||
retry_delay_seconds = 30
|
||||
|
||||
[threefold_grid]
|
||||
grid_proxy_url = "https://gridproxy.grid.tf"
|
||||
substrate_url = "wss://tfchain.grid.tf/ws"
|
||||
relay_url = "wss://relay.grid.tf"
|
||||
|
||||
[tft_conversion]
|
||||
price_sources = ["dex", "coingecko", "grid_stats"]
|
||||
conversion_buffer_percent = 2.0
|
||||
max_slippage_percent = 5.0
|
||||
|
||||
[notifications]
|
||||
email_enabled = true
|
||||
webhook_enabled = true
|
||||
slack_enabled = false
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This deployment automation specification provides the complete technical foundation for seamless TFC-to-Grid deployment automation, ensuring reliable and scalable service provisioning on the ThreeFold Grid.
|
@@ -0,0 +1,550 @@
|
||||
# Dual UX Specification: Modern App + E-commerce Flows
|
||||
|
||||
**Document Purpose**: Comprehensive UX specification supporting both modern app-style instant purchase and traditional e-commerce cart workflows.
|
||||
|
||||
**Last Updated**: 2025-08-04
|
||||
**Status**: Implementation Ready
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The Project Mycelium supports **two distinct user experience patterns** to accommodate different user preferences and use cases:
|
||||
|
||||
1. **Modern App Flow** (OpenRouter-style): Instant purchase with wallet top-up
|
||||
2. **Traditional E-commerce Flow**: Cart-based shopping with checkout
|
||||
|
||||
---
|
||||
|
||||
## UX Flow Comparison
|
||||
|
||||
### **Flow 1: Modern App Style (OpenRouter-inspired)**
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Browse Marketplace] --> B[Register/Login]
|
||||
B --> C[View Service]
|
||||
C --> D[Buy Now Button]
|
||||
D --> E{TFC Balance Check}
|
||||
E -->|Sufficient| F[Instant Deploy]
|
||||
E -->|Insufficient| G[Auto Top-up Modal]
|
||||
G --> H[Stripe Payment]
|
||||
H --> I[TFC Added]
|
||||
I --> F
|
||||
F --> J[Deployment Status]
|
||||
J --> K[Service Active]
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Instant Purchase**: Single-click buying
|
||||
- **Auto Top-up**: Seamless balance management
|
||||
- **Wallet-centric**: Balance always visible
|
||||
- **Minimal Friction**: No cart, no checkout process
|
||||
|
||||
### **Flow 2: Traditional E-commerce Style**
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[Browse Marketplace] --> B[View Service]
|
||||
B --> C[Add to Cart]
|
||||
C --> D[Continue Shopping]
|
||||
D --> E[Review Cart]
|
||||
E --> F[Checkout]
|
||||
F --> G{Payment Method}
|
||||
G -->|TFC Balance| H[Pay with TFC]
|
||||
G -->|Credit Card| I[Stripe Checkout]
|
||||
I --> J[TFC Purchase]
|
||||
J --> H
|
||||
H --> K[Batch Deployment]
|
||||
K --> L[Order Confirmation]
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Bulk Shopping**: Multiple items in cart
|
||||
- **Price Comparison**: Review before purchase
|
||||
- **Batch Deployment**: Deploy multiple services together
|
||||
- **Familiar UX**: Traditional e-commerce experience
|
||||
|
||||
---
|
||||
|
||||
## Detailed UX Specifications
|
||||
|
||||
### **Modern App Flow Implementation**
|
||||
|
||||
#### **1. Wallet-First Interface**
|
||||
|
||||
```html
|
||||
<!-- Wallet Status Component (Always Visible) -->
|
||||
<div class="wallet-status-bar">
|
||||
<div class="balance-display">
|
||||
<span class="balance-amount">{{user.tfc_balance}} TFC</span>
|
||||
<span class="usd-equivalent">${{user.tfc_balance}} USD</span>
|
||||
</div>
|
||||
<div class="wallet-actions">
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="showTopUpModal()">
|
||||
<i class="bi bi-plus-circle"></i> Top Up
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="toggleAutoTopUp()">
|
||||
<i class="bi bi-arrow-repeat"></i> Auto Top-up: {{#if user.auto_topup}}ON{{else}}OFF{{/if}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### **2. Buy Now Button (Primary Action)**
|
||||
|
||||
```html
|
||||
<!-- Service Card with Buy Now -->
|
||||
<div class="service-card modern-style">
|
||||
<div class="service-header">
|
||||
<h4>{{service.name}}</h4>
|
||||
<div class="price-tag">
|
||||
<span class="tfc-price">{{service.price_tfc}} TFC</span>
|
||||
<span class="deployment-time">~2 min deploy</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="service-actions">
|
||||
<button class="btn btn-primary btn-lg buy-now-btn"
|
||||
onclick="buyNowInstant('{{service.id}}')">
|
||||
<i class="bi bi-lightning-fill"></i>
|
||||
Buy Now & Deploy
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary add-to-cart-btn"
|
||||
onclick="addToCart('{{service.id}}')">
|
||||
<i class="bi bi-cart-plus"></i>
|
||||
Add to Cart
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Balance Check Indicator -->
|
||||
<div class="balance-check">
|
||||
{{#if (gte user.tfc_balance service.price_tfc)}}
|
||||
<span class="text-success">
|
||||
<i class="bi bi-check-circle"></i> Ready to deploy
|
||||
</span>
|
||||
{{else}}
|
||||
<span class="text-warning">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
Need {{subtract service.price_tfc user.tfc_balance}} more TFC
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### **3. Auto Top-up Configuration**
|
||||
|
||||
```html
|
||||
<!-- Auto Top-up Settings Modal -->
|
||||
<div class="modal fade" id="autoTopUpModal">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5>Auto Top-up Settings</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>Enable Auto Top-up</label>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="enableAutoTopUp">
|
||||
<label class="form-check-label">Automatically add TFC when balance is low</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Trigger Threshold</label>
|
||||
<select class="form-select" id="topUpThreshold">
|
||||
<option value="10">When balance < 10 TFC</option>
|
||||
<option value="25">When balance < 25 TFC</option>
|
||||
<option value="50" selected>When balance < 50 TFC</option>
|
||||
<option value="100">When balance < 100 TFC</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Top-up Amount</label>
|
||||
<select class="form-select" id="topUpAmount">
|
||||
<option value="50">Add 50 TFC ($50)</option>
|
||||
<option value="100" selected>Add 100 TFC ($100)</option>
|
||||
<option value="200">Add 200 TFC ($200)</option>
|
||||
<option value="500">Add 500 TFC ($500)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle"></i>
|
||||
Auto top-up uses your saved payment method. You'll receive an email confirmation for each transaction.
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveAutoTopUpSettings()">Save Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### **Traditional E-commerce Flow Implementation**
|
||||
|
||||
#### **1. Shopping Cart Component**
|
||||
|
||||
```html
|
||||
<!-- Shopping Cart (Sidebar or Page) -->
|
||||
<div class="shopping-cart">
|
||||
<div class="cart-header">
|
||||
<h4>Shopping Cart</h4>
|
||||
<span class="item-count">{{cart.items.length}} items</span>
|
||||
</div>
|
||||
|
||||
<div class="cart-items">
|
||||
{{#each cart.items}}
|
||||
<div class="cart-item">
|
||||
<div class="item-info">
|
||||
<h6>{{this.service_name}}</h6>
|
||||
<p class="item-specs">{{this.cpu}}CPU • {{this.memory}}GB RAM • {{this.storage}}GB</p>
|
||||
</div>
|
||||
<div class="item-price">
|
||||
<span class="tfc-price">{{this.price_tfc}} TFC</span>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="removeFromCart('{{this.id}}')">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
|
||||
<div class="cart-summary">
|
||||
<div class="summary-line">
|
||||
<span>Subtotal:</span>
|
||||
<span>{{cart.subtotal}} TFC</span>
|
||||
</div>
|
||||
<div class="summary-line">
|
||||
<span>Estimated Deploy Time:</span>
|
||||
<span>~{{cart.estimated_deploy_time}} minutes</span>
|
||||
</div>
|
||||
<div class="summary-line total">
|
||||
<span><strong>Total:</strong></span>
|
||||
<span><strong>{{cart.total}} TFC</strong></span>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary btn-lg w-100" onclick="proceedToCheckout()">
|
||||
<i class="bi bi-credit-card"></i>
|
||||
Proceed to Checkout
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### **2. Checkout Process**
|
||||
|
||||
```html
|
||||
<!-- Checkout Page -->
|
||||
<div class="checkout-container">
|
||||
<div class="checkout-steps">
|
||||
<div class="step active">1. Review Order</div>
|
||||
<div class="step">2. Payment</div>
|
||||
<div class="step">3. Deployment</div>
|
||||
</div>
|
||||
|
||||
<div class="checkout-content">
|
||||
<div class="order-review">
|
||||
<h5>Order Summary</h5>
|
||||
<!-- Cart items review -->
|
||||
|
||||
<div class="deployment-options">
|
||||
<h6>Deployment Options</h6>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="deploymentTiming" value="immediate" checked>
|
||||
<label class="form-check-label">Deploy immediately after payment</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="deploymentTiming" value="scheduled">
|
||||
<label class="form-check-label">Schedule deployment for later</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="payment-section">
|
||||
<h5>Payment Method</h5>
|
||||
<div class="payment-options">
|
||||
<div class="payment-option" onclick="selectPaymentMethod('tfc')">
|
||||
<div class="option-header">
|
||||
<i class="bi bi-wallet2"></i>
|
||||
<span>Pay with TFC Balance</span>
|
||||
<span class="balance-info">{{user.tfc_balance}} TFC available</span>
|
||||
</div>
|
||||
{{#if (lt user.tfc_balance cart.total)}}
|
||||
<div class="insufficient-notice">
|
||||
<span class="text-warning">Insufficient balance. Need {{subtract cart.total user.tfc_balance}} more TFC.</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="payment-option" onclick="selectPaymentMethod('stripe')">
|
||||
<div class="option-header">
|
||||
<i class="bi bi-credit-card"></i>
|
||||
<span>Credit/Debit Card</span>
|
||||
<span class="amount-info">${{cart.total}} USD</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="payment-option" onclick="selectPaymentMethod('mixed')">
|
||||
<div class="option-header">
|
||||
<i class="bi bi-shuffle"></i>
|
||||
<span>Use TFC + Credit Card</span>
|
||||
<span class="mixed-info">{{user.tfc_balance}} TFC + ${{subtract cart.total user.tfc_balance}} USD</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JavaScript Implementation
|
||||
|
||||
### **Modern App Flow Functions**
|
||||
|
||||
```javascript
|
||||
// Modern App Style - Instant Purchase
|
||||
async function buyNowInstant(serviceId) {
|
||||
try {
|
||||
// Check balance first
|
||||
const balance = await getUserTFCBalance();
|
||||
const service = await getServiceDetails(serviceId);
|
||||
|
||||
if (balance >= service.price_tfc) {
|
||||
// Sufficient balance - instant deploy
|
||||
showLoadingToast('Starting deployment...');
|
||||
const result = await initiateDeployment(serviceId, 'tfc');
|
||||
|
||||
if (result.success) {
|
||||
showSuccessToast('Deployment started! Redirecting...');
|
||||
window.location.href = `/dashboard/deployments/${result.deployment_id}`;
|
||||
}
|
||||
} else {
|
||||
// Insufficient balance - auto top-up flow
|
||||
const needed = service.price_tfc - balance;
|
||||
|
||||
if (await isAutoTopUpEnabled()) {
|
||||
showAutoTopUpModal(needed, serviceId);
|
||||
} else {
|
||||
showManualTopUpModal(needed, serviceId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast('Failed to process purchase');
|
||||
}
|
||||
}
|
||||
|
||||
// Auto Top-up Flow
|
||||
async function handleAutoTopUp(amount, serviceId) {
|
||||
try {
|
||||
showLoadingToast('Processing auto top-up...');
|
||||
|
||||
const topUpResult = await processAutoTopUp(amount);
|
||||
if (topUpResult.success) {
|
||||
showSuccessToast('Balance updated! Starting deployment...');
|
||||
|
||||
// Proceed with deployment
|
||||
const deployResult = await initiateDeployment(serviceId, 'tfc');
|
||||
if (deployResult.success) {
|
||||
window.location.href = `/dashboard/deployments/${deployResult.deployment_id}`;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast('Auto top-up failed');
|
||||
}
|
||||
}
|
||||
|
||||
// Real-time Balance Updates
|
||||
function startBalancePolling() {
|
||||
setInterval(async () => {
|
||||
const balance = await getUserTFCBalance();
|
||||
updateBalanceDisplay(balance);
|
||||
}, 10000); // Update every 10 seconds
|
||||
}
|
||||
|
||||
function updateBalanceDisplay(balance) {
|
||||
document.querySelector('.balance-amount').textContent = `${balance} TFC`;
|
||||
document.querySelector('.usd-equivalent').textContent = `$${balance} USD`;
|
||||
|
||||
// Update buy buttons state
|
||||
document.querySelectorAll('.buy-now-btn').forEach(btn => {
|
||||
const servicePrice = parseFloat(btn.dataset.price);
|
||||
if (balance >= servicePrice) {
|
||||
btn.classList.remove('insufficient-balance');
|
||||
btn.disabled = false;
|
||||
} else {
|
||||
btn.classList.add('insufficient-balance');
|
||||
btn.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### **Traditional E-commerce Functions**
|
||||
|
||||
```javascript
|
||||
// Traditional E-commerce - Cart Management
|
||||
class ShoppingCart {
|
||||
constructor() {
|
||||
this.items = JSON.parse(localStorage.getItem('cart_items') || '[]');
|
||||
this.updateCartDisplay();
|
||||
}
|
||||
|
||||
addItem(serviceId, serviceName, price, specs) {
|
||||
const existingItem = this.items.find(item => item.service_id === serviceId);
|
||||
|
||||
if (existingItem) {
|
||||
showInfoToast('Item already in cart');
|
||||
return;
|
||||
}
|
||||
|
||||
this.items.push({
|
||||
id: generateId(),
|
||||
service_id: serviceId,
|
||||
service_name: serviceName,
|
||||
price_tfc: price,
|
||||
specs: specs,
|
||||
added_at: new Date().toISOString()
|
||||
});
|
||||
|
||||
this.saveCart();
|
||||
this.updateCartDisplay();
|
||||
showSuccessToast('Added to cart');
|
||||
}
|
||||
|
||||
removeItem(itemId) {
|
||||
this.items = this.items.filter(item => item.id !== itemId);
|
||||
this.saveCart();
|
||||
this.updateCartDisplay();
|
||||
showSuccessToast('Removed from cart');
|
||||
}
|
||||
|
||||
getTotal() {
|
||||
return this.items.reduce((total, item) => total + item.price_tfc, 0);
|
||||
}
|
||||
|
||||
async proceedToCheckout() {
|
||||
if (this.items.length === 0) {
|
||||
showErrorToast('Cart is empty');
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate to checkout page with cart data
|
||||
const cartData = encodeURIComponent(JSON.stringify(this.items));
|
||||
window.location.href = `/checkout?cart=${cartData}`;
|
||||
}
|
||||
|
||||
saveCart() {
|
||||
localStorage.setItem('cart_items', JSON.stringify(this.items));
|
||||
}
|
||||
|
||||
updateCartDisplay() {
|
||||
const cartCount = document.querySelector('.cart-count');
|
||||
const cartTotal = document.querySelector('.cart-total');
|
||||
|
||||
if (cartCount) cartCount.textContent = this.items.length;
|
||||
if (cartTotal) cartTotal.textContent = `${this.getTotal()} TFC`;
|
||||
}
|
||||
}
|
||||
|
||||
// Checkout Process
|
||||
async function processCheckout(paymentMethod, cartItems) {
|
||||
try {
|
||||
showLoadingToast('Processing order...');
|
||||
|
||||
const orderData = {
|
||||
items: cartItems,
|
||||
payment_method: paymentMethod,
|
||||
total_tfc: cartItems.reduce((sum, item) => sum + item.price_tfc, 0)
|
||||
};
|
||||
|
||||
const result = await fetch('/api/checkout/process', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(orderData)
|
||||
});
|
||||
|
||||
const response = await result.json();
|
||||
|
||||
if (response.success) {
|
||||
// Clear cart
|
||||
localStorage.removeItem('cart_items');
|
||||
|
||||
// Redirect to order confirmation
|
||||
window.location.href = `/orders/${response.order_id}`;
|
||||
} else {
|
||||
showErrorToast(response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast('Checkout failed');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Preference System
|
||||
|
||||
### **UX Mode Selection**
|
||||
|
||||
```html
|
||||
<!-- User Preferences - UX Mode -->
|
||||
<div class="ux-preference-setting">
|
||||
<h6>Shopping Experience</h6>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="uxMode" value="modern" id="modernUX">
|
||||
<label class="form-check-label" for="modernUX">
|
||||
<strong>Modern App Style</strong>
|
||||
<br><small>Instant purchases with wallet top-up (like OpenRouter)</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="uxMode" value="ecommerce" id="ecommerceUX">
|
||||
<label class="form-check-label" for="ecommerceUX">
|
||||
<strong>Traditional E-commerce</strong>
|
||||
<br><small>Shopping cart with checkout process</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="uxMode" value="both" id="bothUX" checked>
|
||||
<label class="form-check-label" for="bothUX">
|
||||
<strong>Both Options</strong>
|
||||
<br><small>Show both "Buy Now" and "Add to Cart" buttons</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits of Dual UX Approach
|
||||
|
||||
### **Modern App Flow Benefits:**
|
||||
- ✅ **Speed**: Instant purchases
|
||||
- ✅ **Simplicity**: Minimal clicks
|
||||
- ✅ **Mobile-friendly**: Touch-optimized
|
||||
- ✅ **Auto-management**: Set-and-forget top-ups
|
||||
|
||||
### **Traditional E-commerce Benefits:**
|
||||
- ✅ **Bulk Shopping**: Multiple services at once
|
||||
- ✅ **Price Comparison**: Review before buying
|
||||
- ✅ **Familiar**: Standard shopping experience
|
||||
- ✅ **Planning**: Schedule deployments
|
||||
|
||||
### **Combined Advantages:**
|
||||
- 🎯 **User Choice**: Accommodate different preferences
|
||||
- 🎯 **Use Case Flexibility**: Quick single purchases OR planned bulk orders
|
||||
- 🎯 **Market Coverage**: Appeal to both app users and traditional shoppers
|
||||
- 🎯 **Conversion Optimization**: Multiple paths to purchase
|
||||
|
||||
---
|
||||
|
||||
This dual UX specification ensures the Project Mycelium appeals to both modern app users (who want instant, frictionless purchases) and traditional e-commerce users (who prefer to review and plan their purchases).
|
@@ -0,0 +1,411 @@
|
||||
# Project Mycelium: Complete Roadmap & UX/UI Analysis
|
||||
|
||||
**Document Purpose**: Comprehensive roadmap for the complete marketplace ecosystem including payment integration, deployment automation, and full user experience flow.
|
||||
|
||||
**Last Updated**: 2025-08-04
|
||||
**Status**: Strategic Planning Phase
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Current State & Next Phase](#current-state--next-phase)
|
||||
2. [Complete UX/UI Flow Analysis](#complete-uxui-flow-analysis)
|
||||
3. [Payment System Integration](#payment-system-integration)
|
||||
4. [ThreeFold Grid Deployment Automation](#threefold-grid-deployment-automation)
|
||||
5. [Revenue Model & Commission Structure](#revenue-model--commission-structure)
|
||||
6. [Technical Architecture](#technical-architecture)
|
||||
7. [Implementation Phases](#implementation-phases)
|
||||
8. [Success Metrics](#success-metrics)
|
||||
|
||||
---
|
||||
|
||||
## Current State & Next Phase
|
||||
|
||||
### ✅ **Phase 1 Complete: Foundation**
|
||||
- **TFC Credits System**: Single currency (1 TFC = 1 USD)
|
||||
- **Database Migration**: Supabase PostgreSQL ready
|
||||
- **HA Architecture**: k3s cluster deployment documented
|
||||
- **Clean Codebase**: All TFP → TFC refactor complete
|
||||
|
||||
### 🎯 **Phase 2: Complete Marketplace Ecosystem**
|
||||
|
||||
The next critical phase involves creating a **complete end-to-end marketplace** that bridges:
|
||||
- **Web2 UX**: Easy fiat payments via Stripe
|
||||
- **Web3 Deployment**: Decentralized ThreeFold Grid infrastructure
|
||||
- **Seamless Integration**: TFC → TFT conversion for grid deployments
|
||||
|
||||
---
|
||||
|
||||
## Complete UX/UI Flow Analysis
|
||||
|
||||
### **User Journey: From Discovery to Deployment**
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[User Browses Marketplace] --> B[Selects App/VM/Service]
|
||||
B --> C[Add to Cart]
|
||||
C --> D[Review Cart]
|
||||
D --> E{Payment Method}
|
||||
E -->|Has TFC Credits| F[Pay with TFC]
|
||||
E -->|Needs Credits| G[Buy TFC with Stripe]
|
||||
G --> H[Stripe Payment Processing]
|
||||
H --> I[TFC Credits Added]
|
||||
I --> F
|
||||
F --> J[TFC Balance Deducted]
|
||||
J --> K[Deployment Queue]
|
||||
K --> L[TFC → TFT Conversion]
|
||||
L --> M[ThreeFold Grid Deployment]
|
||||
M --> N[Service Active]
|
||||
N --> O[User Dashboard Access]
|
||||
```
|
||||
|
||||
### **Critical UX/UI Components**
|
||||
|
||||
#### **1. Shopping Cart & Checkout**
|
||||
- **Add to Cart**: ✅ Working
|
||||
- **Buy Now**: ❌ **MISSING** - Direct purchase flow
|
||||
- **Cart Review**: Price breakdown, TFC cost, estimated deployment time
|
||||
- **Payment Options**: TFC balance vs. Stripe top-up
|
||||
|
||||
#### **2. TFC Balance Management**
|
||||
- **Real-time Balance**: Display current TFC credits
|
||||
- **Deduction Logic**: `1000 TFC - 50 TFC product = 950 TFC`
|
||||
- **Low Balance Alerts**: Prompt for Stripe top-up
|
||||
- **Transaction History**: All TFC purchases and usage
|
||||
|
||||
#### **3. Payment Integration**
|
||||
- **Stripe Integration**: Credit card payments → TFC credits
|
||||
- **Currency Bridge**: USD → TFC (1:1) → TFT (market rate)
|
||||
- **Commission Handling**: Marketplace cut before grid deployment
|
||||
|
||||
#### **4. Deployment Status**
|
||||
- **Queue Position**: Real-time deployment progress
|
||||
- **Grid Status**: ThreeFold node selection and deployment
|
||||
- **Service Access**: Connection details and management
|
||||
|
||||
---
|
||||
|
||||
## Payment System Integration
|
||||
|
||||
### **Stripe Payment Flow**
|
||||
|
||||
```rust
|
||||
// Proposed Stripe Integration Architecture
|
||||
pub struct StripePaymentService {
|
||||
stripe_client: stripe::Client,
|
||||
webhook_secret: String,
|
||||
}
|
||||
|
||||
impl StripePaymentService {
|
||||
pub async fn create_payment_intent(
|
||||
&self,
|
||||
amount_usd: Decimal,
|
||||
user_id: String,
|
||||
) -> Result<PaymentIntent> {
|
||||
// Create Stripe payment intent
|
||||
// Amount in cents (USD)
|
||||
let amount_cents = (amount_usd * 100).to_u64().unwrap();
|
||||
|
||||
stripe::PaymentIntent::create(&self.stripe_client, CreatePaymentIntent {
|
||||
amount: amount_cents,
|
||||
currency: Currency::USD,
|
||||
metadata: [("user_id", user_id)].iter().cloned().collect(),
|
||||
..Default::default()
|
||||
}).await
|
||||
}
|
||||
|
||||
pub async fn handle_webhook(&self, payload: &str, signature: &str) -> Result<()> {
|
||||
// Verify webhook signature
|
||||
// Process payment success
|
||||
// Add TFC credits to user balance
|
||||
// Trigger any pending deployments
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **TFC ↔ Fiat Bridge Logic**
|
||||
|
||||
| User Action | USD Amount | TFC Credits | Marketplace Cut | Grid Deployment |
|
||||
|-------------|------------|-------------|-----------------|-----------------|
|
||||
| Buy 100 TFC | $100.00 | +100 TFC | $0 (top-up) | N/A |
|
||||
| Deploy VM (50 TFC) | N/A | -50 TFC | $5 (10%) | $45 → TFT |
|
||||
| Deploy App (25 TFC) | N/A | -25 TFC | $2.50 (10%) | $22.50 → TFT |
|
||||
|
||||
### **Commission Structure**
|
||||
- **Marketplace Fee**: 10% of each deployment
|
||||
- **Grid Payment**: 90% converted to TFT for actual deployment
|
||||
- **Revenue Split**: Transparent fee structure for users
|
||||
|
||||
---
|
||||
|
||||
## ThreeFold Grid Deployment Automation
|
||||
|
||||
### **Deployment Pipeline Architecture**
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[User Purchases Service] --> B[TFC Deducted]
|
||||
B --> C[Deployment Queue]
|
||||
C --> D[Commission Calculation]
|
||||
D --> E[Remaining Amount → TFT]
|
||||
E --> F[ThreeFold Grid API]
|
||||
F --> G[Node Selection]
|
||||
G --> H[Resource Allocation]
|
||||
H --> I[Service Deployment]
|
||||
I --> J[Connection Details]
|
||||
J --> K[User Notification]
|
||||
```
|
||||
|
||||
### **Grid Integration Service**
|
||||
|
||||
```rust
|
||||
pub struct ThreeFoldGridService {
|
||||
grid_client: TFGridClient,
|
||||
tft_wallet: TFTWallet,
|
||||
}
|
||||
|
||||
impl ThreeFoldGridService {
|
||||
pub async fn deploy_vm(
|
||||
&self,
|
||||
deployment_spec: VMDeploymentSpec,
|
||||
tfc_amount: Decimal,
|
||||
marketplace_cut: Decimal,
|
||||
) -> Result<DeploymentResult> {
|
||||
// Calculate actual deployment cost
|
||||
let deployment_amount = tfc_amount - marketplace_cut;
|
||||
|
||||
// Convert TFC to TFT at current market rate
|
||||
let tft_amount = self.convert_usd_to_tft(deployment_amount).await?;
|
||||
|
||||
// Deploy on ThreeFold Grid
|
||||
let deployment = self.grid_client.deploy_vm(VMDeployment {
|
||||
cpu: deployment_spec.cpu,
|
||||
memory: deployment_spec.memory,
|
||||
storage: deployment_spec.storage,
|
||||
payment: tft_amount,
|
||||
..Default::default()
|
||||
}).await?;
|
||||
|
||||
Ok(DeploymentResult {
|
||||
deployment_id: deployment.id,
|
||||
grid_node: deployment.node_id,
|
||||
access_details: deployment.connection_info,
|
||||
tft_spent: tft_amount,
|
||||
})
|
||||
}
|
||||
|
||||
async fn convert_usd_to_tft(&self, usd_amount: Decimal) -> Result<Decimal> {
|
||||
// Get current TFT/USD rate from ThreeFold Grid
|
||||
let rate = self.grid_client.get_tft_usd_rate().await?;
|
||||
Ok(usd_amount / rate)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### **Deployment Types & Automation**
|
||||
|
||||
| Service Type | TFC Cost | Deployment Method | Grid Resource |
|
||||
|--------------|----------|-------------------|---------------|
|
||||
| **VM Instance** | 50-200 TFC | Automated VM deployment | Compute nodes |
|
||||
| **Kubernetes Cluster** | 100-500 TFC | k3s cluster provisioning | Multiple nodes |
|
||||
| **Storage Service** | 25-100 TFC | Distributed storage | Storage nodes |
|
||||
| **Application** | 10-50 TFC | Container deployment | App-specific nodes |
|
||||
|
||||
---
|
||||
|
||||
## Revenue Model & Commission Structure
|
||||
|
||||
### **Marketplace Economics**
|
||||
|
||||
```
|
||||
User Payment Flow:
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ User Pays │ │ Marketplace │ │ ThreeFold Grid │
|
||||
│ $100 USD │───▶│ Takes 10% │───▶│ Receives 90% │
|
||||
│ via Stripe │ │ ($10 USD) │ │ ($90 → TFT) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
### **Commission Breakdown**
|
||||
- **Platform Fee**: 10% of each deployment
|
||||
- **Payment Processing**: 2.9% + $0.30 (Stripe fees)
|
||||
- **Net Marketplace Revenue**: ~7% per transaction
|
||||
- **Grid Deployment**: 90% converted to TFT for actual resources
|
||||
|
||||
### **Revenue Projections**
|
||||
| Monthly Deployments | Avg. Deployment | Gross Revenue | Net Revenue |
|
||||
|---------------------|-----------------|---------------|-------------|
|
||||
| 1,000 | $75 | $7,500 | $5,250 |
|
||||
| 5,000 | $75 | $37,500 | $26,250 |
|
||||
| 10,000 | $75 | $75,000 | $52,500 |
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture
|
||||
|
||||
### **Complete System Architecture**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Project Mycelium │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Frontend (React/HTML) │ Backend (Rust) │ Database │
|
||||
│ - Shopping Cart │ - Payment API │ - PostgreSQL │
|
||||
│ - TFC Balance │ - Grid API │ - Supabase │
|
||||
│ - Deployment Status │ - Auth Service │ - Real-time │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ External Integrations │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Stripe Payments │ ThreeFold Grid │ TFT Blockchain │
|
||||
│ - Credit Cards │ - VM Deployment │ - Token Conversion│
|
||||
│ - Webhooks │ - K8s Clusters │ - Market Rates │
|
||||
│ - Subscriptions │ - Storage │ - Wallet Mgmt │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### **Data Flow Architecture**
|
||||
|
||||
```rust
|
||||
// Complete marketplace data flow
|
||||
pub struct MarketplaceTransaction {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub service_type: ServiceType,
|
||||
pub tfc_amount: Decimal,
|
||||
pub usd_amount: Decimal,
|
||||
pub marketplace_fee: Decimal,
|
||||
pub grid_payment: Decimal,
|
||||
pub tft_amount: Decimal,
|
||||
pub deployment_id: Option<String>,
|
||||
pub status: TransactionStatus,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub enum TransactionStatus {
|
||||
PaymentPending,
|
||||
PaymentCompleted,
|
||||
DeploymentQueued,
|
||||
DeploymentInProgress,
|
||||
DeploymentCompleted,
|
||||
DeploymentFailed,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### **Phase 2A: Payment Integration (Weeks 1-3)**
|
||||
- [ ] Stripe integration setup
|
||||
- [ ] TFC purchase flow
|
||||
- [ ] Webhook handling
|
||||
- [ ] Balance management
|
||||
- [ ] Commission calculation
|
||||
|
||||
### **Phase 2B: Grid Deployment (Weeks 4-6)**
|
||||
- [ ] ThreeFold Grid API integration
|
||||
- [ ] TFC → TFT conversion service
|
||||
- [ ] Automated deployment pipeline
|
||||
- [ ] Status tracking and notifications
|
||||
- [ ] Error handling and rollback
|
||||
|
||||
### **Phase 2C: Complete UX/UI (Weeks 7-9)**
|
||||
- [ ] Buy Now button implementation
|
||||
- [ ] Real-time balance updates
|
||||
- [ ] Deployment progress tracking
|
||||
- [ ] Service management dashboard
|
||||
- [ ] Mobile-responsive design
|
||||
|
||||
### **Phase 2D: Production & Scaling (Weeks 10-12)**
|
||||
- [ ] Load testing
|
||||
- [ ] Security audit
|
||||
- [ ] Performance optimization
|
||||
- [ ] Monitoring and alerting
|
||||
- [ ] Documentation completion
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### **Technical Metrics**
|
||||
- **Payment Success Rate**: >99.5%
|
||||
- **Deployment Success Rate**: >95%
|
||||
- **Average Deployment Time**: <5 minutes
|
||||
- **System Uptime**: >99.9%
|
||||
- **API Response Time**: <200ms
|
||||
|
||||
### **Business Metrics**
|
||||
- **Monthly Active Users**: Target 1,000+
|
||||
- **Average Revenue Per User**: $50/month
|
||||
- **Customer Acquisition Cost**: <$25
|
||||
- **Customer Lifetime Value**: >$500
|
||||
- **Marketplace Commission**: 10% of deployments
|
||||
|
||||
### **User Experience Metrics**
|
||||
- **Cart Abandonment Rate**: <20%
|
||||
- **Payment Completion Rate**: >90%
|
||||
- **User Satisfaction Score**: >4.5/5
|
||||
- **Support Ticket Volume**: <5% of transactions
|
||||
- **Feature Adoption Rate**: >70%
|
||||
|
||||
---
|
||||
|
||||
## Critical Questions & Analysis
|
||||
|
||||
### **Is This a Complete UX/UI for the Marketplace?**
|
||||
|
||||
**Current State Analysis**:
|
||||
✅ **Strengths**:
|
||||
- Clean TFC credits system (1 TFC = 1 USD)
|
||||
- Add to cart functionality working
|
||||
- Clear pricing and service catalog
|
||||
- User authentication and dashboard
|
||||
|
||||
❌ **Missing Critical Components**:
|
||||
- **Buy Now button** - Direct purchase flow
|
||||
- **Real-time TFC deduction** - Balance updates on purchase
|
||||
- **Stripe payment integration** - Fiat → TFC conversion
|
||||
- **Grid deployment automation** - TFC → TFT → Deployment
|
||||
- **Deployment status tracking** - Real-time progress
|
||||
- **Service management** - Post-deployment controls
|
||||
|
||||
### **Recommended UX/UI Enhancements**
|
||||
|
||||
1. **Immediate Purchase Flow**
|
||||
```
|
||||
[Service Page] → [Buy Now] → [Payment Method] → [Stripe/TFC] → [Deployment]
|
||||
```
|
||||
|
||||
2. **Balance Management**
|
||||
```
|
||||
TFC Balance: 1000 → Purchase 50 TFC service → New Balance: 950
|
||||
```
|
||||
|
||||
3. **Payment Options**
|
||||
```
|
||||
┌─ Pay with TFC Credits (if sufficient balance)
|
||||
└─ Buy TFC with Credit Card (Stripe integration)
|
||||
```
|
||||
|
||||
4. **Deployment Pipeline**
|
||||
```
|
||||
Payment → Queue → TFC→TFT → Grid API → Service Active → User Access
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The proposed marketplace architecture creates a **seamless Web2-to-Web3 bridge** that:
|
||||
|
||||
- **Simplifies User Experience**: Familiar credit card payments
|
||||
- **Maintains Decentralization**: ThreeFold Grid deployment
|
||||
- **Generates Revenue**: 10% marketplace commission
|
||||
- **Scales Efficiently**: Cloud-native architecture
|
||||
- **Ensures Reliability**: HA k3s deployment ready
|
||||
|
||||
The next implementation phase will transform this from a catalog into a **complete e-commerce platform** with automated deployment capabilities.
|
||||
|
||||
---
|
||||
|
||||
**Next Steps**: Begin Phase 2A implementation with Stripe integration and TFC purchase flow.
|
@@ -0,0 +1,582 @@
|
||||
# Payment Integration Technical Specification
|
||||
|
||||
**Document Purpose**: Detailed technical specification for Stripe payment integration and TFC credits system.
|
||||
|
||||
**Last Updated**: 2025-08-04
|
||||
**Status**: Implementation Ready
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the technical implementation for integrating Stripe payments with the Project Mycelium TFC credits system, enabling seamless fiat-to-credits conversion and automated grid deployments.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Components
|
||||
|
||||
### 1. Stripe Payment Service
|
||||
|
||||
```rust
|
||||
// src/services/stripe_service.rs
|
||||
use stripe::{Client, PaymentIntent, CreatePaymentIntent, Currency, Webhook, EventType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StripeService {
|
||||
client: Client,
|
||||
webhook_secret: String,
|
||||
publishable_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct TFCPurchaseRequest {
|
||||
pub amount_usd: Decimal,
|
||||
pub user_id: String,
|
||||
pub return_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PaymentIntentResponse {
|
||||
pub client_secret: String,
|
||||
pub payment_intent_id: String,
|
||||
pub amount_cents: u64,
|
||||
pub tfc_amount: Decimal,
|
||||
}
|
||||
|
||||
impl StripeService {
|
||||
pub fn new(secret_key: String, webhook_secret: String, publishable_key: String) -> Self {
|
||||
Self {
|
||||
client: Client::new(secret_key),
|
||||
webhook_secret,
|
||||
publishable_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_tfc_purchase_intent(
|
||||
&self,
|
||||
request: TFCPurchaseRequest,
|
||||
) -> Result<PaymentIntentResponse, StripeError> {
|
||||
let amount_cents = (request.amount_usd * Decimal::from(100)).to_u64().unwrap();
|
||||
|
||||
let payment_intent = PaymentIntent::create(&self.client, CreatePaymentIntent {
|
||||
amount: amount_cents,
|
||||
currency: Currency::USD,
|
||||
metadata: [
|
||||
("user_id".to_string(), request.user_id.clone()),
|
||||
("tfc_amount".to_string(), request.amount_usd.to_string()),
|
||||
("purpose".to_string(), "tfc_purchase".to_string()),
|
||||
].iter().cloned().collect(),
|
||||
..Default::default()
|
||||
}).await?;
|
||||
|
||||
Ok(PaymentIntentResponse {
|
||||
client_secret: payment_intent.client_secret.unwrap(),
|
||||
payment_intent_id: payment_intent.id.to_string(),
|
||||
amount_cents,
|
||||
tfc_amount: request.amount_usd,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn handle_webhook(
|
||||
&self,
|
||||
payload: &str,
|
||||
signature: &str,
|
||||
) -> Result<WebhookResult, StripeError> {
|
||||
let event = Webhook::construct_event(payload, signature, &self.webhook_secret)?;
|
||||
|
||||
match event.type_ {
|
||||
EventType::PaymentIntentSucceeded => {
|
||||
if let Ok(payment_intent) = serde_json::from_value::<PaymentIntent>(event.data.object) {
|
||||
return Ok(WebhookResult::PaymentSucceeded {
|
||||
user_id: payment_intent.metadata.get("user_id").cloned().unwrap_or_default(),
|
||||
tfc_amount: payment_intent.metadata.get("tfc_amount")
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_default(),
|
||||
payment_intent_id: payment_intent.id.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
EventType::PaymentIntentPaymentFailed => {
|
||||
// Handle failed payments
|
||||
return Ok(WebhookResult::PaymentFailed);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(WebhookResult::Ignored)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WebhookResult {
|
||||
PaymentSucceeded {
|
||||
user_id: String,
|
||||
tfc_amount: Decimal,
|
||||
payment_intent_id: String,
|
||||
},
|
||||
PaymentFailed,
|
||||
Ignored,
|
||||
}
|
||||
```
|
||||
|
||||
### 2. TFC Credits Management
|
||||
|
||||
```rust
|
||||
// src/services/tfc_service.rs
|
||||
use rust_decimal::Decimal;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TFCTransaction {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub transaction_type: TFCTransactionType,
|
||||
pub amount: Decimal,
|
||||
pub balance_before: Decimal,
|
||||
pub balance_after: Decimal,
|
||||
pub description: String,
|
||||
pub metadata: serde_json::Value,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum TFCTransactionType {
|
||||
Purchase, // Stripe payment → TFC
|
||||
Deployment, // TFC → Grid deployment
|
||||
Refund, // Failed deployment refund
|
||||
Transfer, // User-to-user transfer
|
||||
AdminAdjustment, // Manual balance adjustment
|
||||
}
|
||||
|
||||
pub struct TFCService {
|
||||
db: Arc<dyn Database>,
|
||||
}
|
||||
|
||||
impl TFCService {
|
||||
pub async fn add_tfc_credits(
|
||||
&self,
|
||||
user_id: &str,
|
||||
amount: Decimal,
|
||||
payment_intent_id: &str,
|
||||
) -> Result<TFCTransaction, TFCError> {
|
||||
let current_balance = self.get_user_balance(user_id).await?;
|
||||
let new_balance = current_balance + amount;
|
||||
|
||||
let transaction = TFCTransaction {
|
||||
id: generate_transaction_id(),
|
||||
user_id: user_id.to_string(),
|
||||
transaction_type: TFCTransactionType::Purchase,
|
||||
amount,
|
||||
balance_before: current_balance,
|
||||
balance_after: new_balance,
|
||||
description: format!("TFC purchase via Stripe ({})", payment_intent_id),
|
||||
metadata: json!({
|
||||
"payment_intent_id": payment_intent_id,
|
||||
"stripe_amount_cents": (amount * Decimal::from(100)).to_u64(),
|
||||
}),
|
||||
created_at: Utc::now(),
|
||||
};
|
||||
|
||||
// Atomic transaction: update balance + record transaction
|
||||
self.db.execute_transaction(|tx| {
|
||||
tx.update_user_balance(user_id, new_balance)?;
|
||||
tx.insert_tfc_transaction(&transaction)?;
|
||||
Ok(())
|
||||
}).await?;
|
||||
|
||||
Ok(transaction)
|
||||
}
|
||||
|
||||
pub async fn deduct_tfc_for_deployment(
|
||||
&self,
|
||||
user_id: &str,
|
||||
amount: Decimal,
|
||||
deployment_id: &str,
|
||||
service_name: &str,
|
||||
) -> Result<TFCTransaction, TFCError> {
|
||||
let current_balance = self.get_user_balance(user_id).await?;
|
||||
|
||||
if current_balance < amount {
|
||||
return Err(TFCError::InsufficientBalance {
|
||||
required: amount,
|
||||
available: current_balance,
|
||||
});
|
||||
}
|
||||
|
||||
let new_balance = current_balance - amount;
|
||||
|
||||
let transaction = TFCTransaction {
|
||||
id: generate_transaction_id(),
|
||||
user_id: user_id.to_string(),
|
||||
transaction_type: TFCTransactionType::Deployment,
|
||||
amount: -amount, // Negative for deduction
|
||||
balance_before: current_balance,
|
||||
balance_after: new_balance,
|
||||
description: format!("Deployment: {} ({})", service_name, deployment_id),
|
||||
metadata: json!({
|
||||
"deployment_id": deployment_id,
|
||||
"service_name": service_name,
|
||||
}),
|
||||
created_at: Utc::now(),
|
||||
};
|
||||
|
||||
self.db.execute_transaction(|tx| {
|
||||
tx.update_user_balance(user_id, new_balance)?;
|
||||
tx.insert_tfc_transaction(&transaction)?;
|
||||
Ok(())
|
||||
}).await?;
|
||||
|
||||
Ok(transaction)
|
||||
}
|
||||
|
||||
pub async fn get_user_balance(&self, user_id: &str) -> Result<Decimal, TFCError> {
|
||||
self.db.get_user_tfc_balance(user_id).await
|
||||
}
|
||||
|
||||
pub async fn get_transaction_history(
|
||||
&self,
|
||||
user_id: &str,
|
||||
limit: Option<u32>,
|
||||
) -> Result<Vec<TFCTransaction>, TFCError> {
|
||||
self.db.get_user_tfc_transactions(user_id, limit.unwrap_or(50)).await
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Buy Now Implementation
|
||||
|
||||
```rust
|
||||
// src/controllers/marketplace.rs
|
||||
use actix_web::{web, HttpResponse, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct BuyNowRequest {
|
||||
pub service_id: String,
|
||||
pub payment_method: PaymentMethod,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub enum PaymentMethod {
|
||||
TFC, // Use existing TFC balance
|
||||
Stripe, // Purchase TFC with credit card
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct BuyNowResponse {
|
||||
pub success: bool,
|
||||
pub action: BuyNowAction,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub enum BuyNowAction {
|
||||
DeploymentStarted { deployment_id: String },
|
||||
PaymentRequired { payment_intent: PaymentIntentResponse },
|
||||
InsufficientFunds { required: Decimal, available: Decimal },
|
||||
}
|
||||
|
||||
impl MarketplaceController {
|
||||
pub async fn buy_now(
|
||||
&self,
|
||||
request: web::Json<BuyNowRequest>,
|
||||
session: Session,
|
||||
) -> Result<HttpResponse> {
|
||||
let user_id = self.get_user_id_from_session(&session)?;
|
||||
let service = self.get_service(&request.service_id).await?;
|
||||
|
||||
match request.payment_method {
|
||||
PaymentMethod::TFC => {
|
||||
// Check TFC balance and proceed with deployment
|
||||
let balance = self.tfc_service.get_user_balance(&user_id).await?;
|
||||
|
||||
if balance >= service.price_tfc {
|
||||
// Sufficient balance - start deployment
|
||||
let deployment_id = self.start_deployment(&user_id, &service).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(BuyNowResponse {
|
||||
success: true,
|
||||
action: BuyNowAction::DeploymentStarted { deployment_id },
|
||||
message: "Deployment started successfully".to_string(),
|
||||
}))
|
||||
} else {
|
||||
// Insufficient balance
|
||||
Ok(HttpResponse::Ok().json(BuyNowResponse {
|
||||
success: false,
|
||||
action: BuyNowAction::InsufficientFunds {
|
||||
required: service.price_tfc,
|
||||
available: balance,
|
||||
},
|
||||
message: "Insufficient TFC balance".to_string(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
PaymentMethod::Stripe => {
|
||||
// Create Stripe payment intent for TFC purchase
|
||||
let payment_intent = self.stripe_service.create_tfc_purchase_intent(
|
||||
TFCPurchaseRequest {
|
||||
amount_usd: service.price_tfc, // 1 TFC = 1 USD
|
||||
user_id: user_id.clone(),
|
||||
return_url: format!("/marketplace/service/{}/deploy", service.id),
|
||||
}
|
||||
).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(BuyNowResponse {
|
||||
success: true,
|
||||
action: BuyNowAction::PaymentRequired { payment_intent },
|
||||
message: "Payment required to complete purchase".to_string(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_deployment(
|
||||
&self,
|
||||
user_id: &str,
|
||||
service: &MarketplaceService,
|
||||
) -> Result<String, MarketplaceError> {
|
||||
// 1. Deduct TFC from user balance
|
||||
let transaction = self.tfc_service.deduct_tfc_for_deployment(
|
||||
user_id,
|
||||
service.price_tfc,
|
||||
&generate_deployment_id(),
|
||||
&service.name,
|
||||
).await?;
|
||||
|
||||
// 2. Calculate marketplace commission
|
||||
let commission = service.price_tfc * Decimal::from_str("0.10")?; // 10%
|
||||
let grid_payment = service.price_tfc - commission;
|
||||
|
||||
// 3. Queue deployment
|
||||
let deployment = Deployment {
|
||||
id: transaction.metadata["deployment_id"].as_str().unwrap().to_string(),
|
||||
user_id: user_id.to_string(),
|
||||
service_id: service.id.clone(),
|
||||
tfc_amount: service.price_tfc,
|
||||
commission_amount: commission,
|
||||
grid_payment_amount: grid_payment,
|
||||
status: DeploymentStatus::Queued,
|
||||
created_at: Utc::now(),
|
||||
};
|
||||
|
||||
self.deployment_service.queue_deployment(deployment).await?;
|
||||
|
||||
Ok(transaction.metadata["deployment_id"].as_str().unwrap().to_string())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend Integration
|
||||
|
||||
### 1. Buy Now Button Component
|
||||
|
||||
```html
|
||||
<!-- Buy Now Button with Payment Options -->
|
||||
<div class="buy-now-section">
|
||||
<div class="price-display">
|
||||
<span class="price">{{service.price_tfc}} TFC</span>
|
||||
<span class="usd-equivalent">${{service.price_tfc}} USD</span>
|
||||
</div>
|
||||
|
||||
<div class="payment-options">
|
||||
<button id="buyNowTFC" class="btn btn-primary btn-lg"
|
||||
onclick="buyNowWithTFC('{{service.id}}')">
|
||||
<i class="bi bi-lightning-fill"></i>
|
||||
Buy Now with TFC
|
||||
</button>
|
||||
|
||||
<button id="buyNowStripe" class="btn btn-outline-primary btn-lg"
|
||||
onclick="buyNowWithStripe('{{service.id}}')">
|
||||
<i class="bi bi-credit-card"></i>
|
||||
Buy with Credit Card
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="balance-info">
|
||||
<small>Your TFC Balance: <span id="userTFCBalance">{{user.tfc_balance}}</span> TFC</small>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. JavaScript Payment Handling
|
||||
|
||||
```javascript
|
||||
// Buy Now with TFC Credits
|
||||
async function buyNowWithTFC(serviceId) {
|
||||
try {
|
||||
const response = await fetch('/api/marketplace/buy-now', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
service_id: serviceId,
|
||||
payment_method: 'TFC'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
switch (result.action.type) {
|
||||
case 'DeploymentStarted':
|
||||
showSuccessToast('Deployment started! Redirecting to dashboard...');
|
||||
setTimeout(() => {
|
||||
window.location.href = `/dashboard/deployments/${result.action.deployment_id}`;
|
||||
}, 2000);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (result.action.type) {
|
||||
case 'InsufficientFunds':
|
||||
showInsufficientFundsModal(result.action.required, result.action.available);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast('Failed to process purchase');
|
||||
}
|
||||
}
|
||||
|
||||
// Buy Now with Stripe
|
||||
async function buyNowWithStripe(serviceId) {
|
||||
try {
|
||||
const response = await fetch('/api/marketplace/buy-now', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
service_id: serviceId,
|
||||
payment_method: 'Stripe'
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.action.type === 'PaymentRequired') {
|
||||
// Initialize Stripe Elements
|
||||
const stripe = Stripe('pk_test_your_publishable_key');
|
||||
const elements = stripe.elements();
|
||||
|
||||
// Show Stripe payment modal
|
||||
showStripePaymentModal(stripe, result.action.payment_intent.client_secret);
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast('Failed to initialize payment');
|
||||
}
|
||||
}
|
||||
|
||||
// Stripe Payment Modal
|
||||
function showStripePaymentModal(stripe, clientSecret) {
|
||||
const modal = document.getElementById('stripePaymentModal');
|
||||
const elements = stripe.elements();
|
||||
|
||||
const cardElement = elements.create('card');
|
||||
cardElement.mount('#card-element');
|
||||
|
||||
document.getElementById('confirmPayment').onclick = async () => {
|
||||
const {error, paymentIntent} = await stripe.confirmCardPayment(clientSecret, {
|
||||
payment_method: {
|
||||
card: cardElement,
|
||||
}
|
||||
});
|
||||
|
||||
if (error) {
|
||||
showErrorToast(error.message);
|
||||
} else {
|
||||
showSuccessToast('Payment successful! TFC credits added to your account.');
|
||||
// Refresh balance and redirect
|
||||
updateTFCBalance();
|
||||
modal.hide();
|
||||
}
|
||||
};
|
||||
|
||||
new bootstrap.Modal(modal).show();
|
||||
}
|
||||
|
||||
// Real-time TFC Balance Updates
|
||||
function updateTFCBalance() {
|
||||
fetch('/api/user/tfc-balance')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('userTFCBalance').textContent = data.balance;
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema Updates
|
||||
|
||||
```sql
|
||||
-- TFC Transactions Table
|
||||
CREATE TABLE tfc_transactions (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
transaction_type VARCHAR(50) NOT NULL,
|
||||
amount DECIMAL(15,2) NOT NULL,
|
||||
balance_before DECIMAL(15,2) NOT NULL,
|
||||
balance_after DECIMAL(15,2) NOT NULL,
|
||||
description TEXT,
|
||||
metadata JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_created_at (created_at),
|
||||
INDEX idx_transaction_type (transaction_type)
|
||||
);
|
||||
|
||||
-- Deployments Table
|
||||
CREATE TABLE deployments (
|
||||
id VARCHAR(255) PRIMARY KEY,
|
||||
user_id VARCHAR(255) NOT NULL,
|
||||
service_id VARCHAR(255) NOT NULL,
|
||||
tfc_amount DECIMAL(15,2) NOT NULL,
|
||||
commission_amount DECIMAL(15,2) NOT NULL,
|
||||
grid_payment_amount DECIMAL(15,2) NOT NULL,
|
||||
tft_amount DECIMAL(15,8),
|
||||
grid_deployment_id VARCHAR(255),
|
||||
status VARCHAR(50) NOT NULL,
|
||||
error_message TEXT,
|
||||
connection_details JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
|
||||
INDEX idx_user_id (user_id),
|
||||
INDEX idx_status (status),
|
||||
INDEX idx_created_at (created_at)
|
||||
);
|
||||
|
||||
-- Update Users Table
|
||||
ALTER TABLE users ADD COLUMN tfc_balance DECIMAL(15,2) DEFAULT 0.00;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
```toml
|
||||
# config/marketplace.toml
|
||||
[stripe]
|
||||
secret_key = "sk_test_..."
|
||||
publishable_key = "pk_test_..."
|
||||
webhook_secret = "whsec_..."
|
||||
|
||||
[marketplace]
|
||||
commission_rate = 0.10 # 10%
|
||||
tfc_usd_rate = 1.00 # 1 TFC = 1 USD
|
||||
|
||||
[threefold_grid]
|
||||
api_endpoint = "https://gridproxy.grid.tf"
|
||||
tft_wallet_mnemonic = "your wallet mnemonic"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
This technical specification provides the complete implementation details for integrating Stripe payments with the TFC credits system, enabling seamless fiat-to-deployment automation in the Project Mycelium.
|
@@ -0,0 +1,231 @@
|
||||
# ServiceFactory Architecture Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The ServiceFactory is a centralized service instantiation system that implements the single source of truth pattern for service creation throughout the Project Mycelium codebase. This architecture eliminates scattered service instantiations and provides consistent, persistent data-backed services.
|
||||
|
||||
## Architecture Principles
|
||||
|
||||
### 1. Single Source of Truth
|
||||
- All service instantiations go through the ServiceFactory
|
||||
- Eliminates duplicate service creation logic
|
||||
- Ensures consistent service configuration across the application
|
||||
|
||||
### 2. Persistent Data Focus
|
||||
- **No Mock Data**: All services use real persistent data from `user_data/` directory
|
||||
- **Industry Standard**: Aligns with production-ready architecture patterns
|
||||
- **Authentic Experience**: Users interact with real data, not mock data
|
||||
|
||||
### 3. Singleton Pattern Implementation
|
||||
- Uses `std::sync::OnceLock` for thread-safe singleton instances
|
||||
- Lazy initialization ensures services are created only when needed
|
||||
- Memory efficient with shared service instances
|
||||
|
||||
## ServiceFactory Implementation
|
||||
|
||||
### Core Structure
|
||||
|
||||
```rust
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use crate::services::{
|
||||
currency::CurrencyService,
|
||||
user_persistence::UserPersistence,
|
||||
};
|
||||
|
||||
/// Service factory for single source of truth service instantiation
|
||||
pub struct ServiceFactory;
|
||||
|
||||
impl ServiceFactory {
|
||||
/// Creates a new CurrencyService instance using persistent data
|
||||
pub fn currency_service() -> Arc<CurrencyService> {
|
||||
static CURRENCY_SERVICE: OnceLock<Arc<CurrencyService>> = OnceLock::new();
|
||||
CURRENCY_SERVICE.get_or_init(|| {
|
||||
Arc::new(CurrencyService::builder().build().unwrap())
|
||||
}).clone()
|
||||
}
|
||||
|
||||
/// Provides access to UserPersistence for persistent data operations
|
||||
pub fn user_persistence() -> Arc<UserPersistence> {
|
||||
static USER_PERSISTENCE: OnceLock<Arc<UserPersistence>> = OnceLock::new();
|
||||
USER_PERSISTENCE.get_or_init(|| {
|
||||
Arc::new(UserPersistence::new())
|
||||
}).clone()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Features
|
||||
|
||||
1. **Thread-Safe Singletons**: Uses `OnceLock` for safe concurrent access
|
||||
2. **Lazy Initialization**: Services created only when first accessed
|
||||
3. **Arc Wrapping**: Enables efficient sharing across threads
|
||||
4. **Builder Pattern Integration**: Leverages existing service builders
|
||||
|
||||
## Migration Results
|
||||
|
||||
### Quantitative Improvements
|
||||
|
||||
- **Files Migrated**: 7 files consolidated to use ServiceFactory
|
||||
- **Code Reduction**: ~100+ lines of duplicate service instantiation code eliminated
|
||||
- **Compilation Errors**: Reduced from 43 to 0 during migration
|
||||
- **Mock Data Elimination**: 100% removal of MockDataService usage
|
||||
|
||||
### Files Successfully Migrated
|
||||
|
||||
1. `src/controllers/order.rs` - Order processing service instantiation
|
||||
2. `src/controllers/product.rs` - Product management service instantiation
|
||||
3. `src/controllers/currency.rs` - Currency conversion service instantiation
|
||||
4. `src/controllers/marketplace.rs` - Marketplace service instantiation
|
||||
5. `src/services/instant_purchase.rs` - Purchase service instantiation
|
||||
6. `src/services/factory.rs` - ServiceFactory implementation
|
||||
7. `src/services/navbar.rs` - Navigation service instantiation
|
||||
|
||||
### Before vs After Comparison
|
||||
|
||||
#### Before (Scattered Instantiation)
|
||||
```rust
|
||||
// In multiple files throughout codebase
|
||||
let currency_service = CurrencyService::builder()
|
||||
.build()
|
||||
.unwrap();
|
||||
```
|
||||
|
||||
#### After (Centralized ServiceFactory)
|
||||
```rust
|
||||
// Single line in any file
|
||||
let currency_service = ServiceFactory::currency_service();
|
||||
```
|
||||
|
||||
## Mock Data Elimination
|
||||
|
||||
### Complete Removal Strategy
|
||||
|
||||
The ServiceFactory migration included a comprehensive elimination of all mock data usage:
|
||||
|
||||
#### MockDataService Removal
|
||||
- **Struct Field Removal**: Removed `mock_data: MockDataService` from CurrencyService
|
||||
- **Method Updates**: Replaced all `self.mock_data.*` calls with persistent data logic
|
||||
- **Import Cleanup**: Removed unused MockDataService imports
|
||||
|
||||
#### Persistent Data Replacement
|
||||
- **Currency Data**: Replaced mock currencies with standard USD, EUR, TFT currencies
|
||||
- **Exchange Rates**: Implemented real exchange rate logic instead of mock volatility
|
||||
- **Configuration**: Hardcoded sensible defaults instead of mock configuration
|
||||
|
||||
### Currency Service Modernization
|
||||
|
||||
#### Standard Currency Implementation
|
||||
```rust
|
||||
pub fn get_supported_currencies(&self) -> Vec<Currency> {
|
||||
vec![
|
||||
Currency {
|
||||
code: "USD".to_string(),
|
||||
name: "US Dollar".to_string(),
|
||||
symbol: "$".to_string(),
|
||||
currency_type: CurrencyType::Fiat,
|
||||
exchange_rate_to_base: dec!(1.0),
|
||||
is_base_currency: true,
|
||||
decimal_places: 2,
|
||||
is_active: true,
|
||||
provider_config: None,
|
||||
last_updated: chrono::Utc::now(),
|
||||
},
|
||||
// EUR and TFT currencies...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Methodology
|
||||
|
||||
### Systematic Manual Approach
|
||||
|
||||
The migration was completed using a systematic, manual approach that proved more effective than automated scripts:
|
||||
|
||||
#### 1. Identification Phase
|
||||
- Used `grep_search` to find all scattered service instantiations
|
||||
- Catalogued `CurrencyService::builder()` and `MockDataService::new()` usage
|
||||
- Identified 27+ CurrencyService instantiations and 3 MockDataService usages
|
||||
|
||||
#### 2. Implementation Phase
|
||||
- Created ServiceFactory with singleton pattern
|
||||
- Implemented thread-safe service access methods
|
||||
- Added proper error handling and initialization
|
||||
|
||||
#### 3. Migration Phase
|
||||
- **Script-Based Initial Migration**: Created Python scripts for bulk replacements
|
||||
- **Manual Refinement**: Line-by-line fixes for complex cases
|
||||
- **Compilation-Driven**: Used compiler errors to guide systematic fixes
|
||||
|
||||
#### 4. Validation Phase
|
||||
- **Compilation Testing**: Ensured zero compilation errors
|
||||
- **Type Safety**: Fixed all type mismatches and borrowing issues
|
||||
- **Import Cleanup**: Removed unused imports and dependencies
|
||||
|
||||
### Lessons Learned
|
||||
|
||||
#### Manual vs Automated Approach
|
||||
- **Scripts Useful For**: Bulk pattern replacements and initial migration
|
||||
- **Manual Required For**: Complex logic changes, type fixes, and edge cases
|
||||
- **Hybrid Approach**: Best results from script-assisted initial migration followed by manual refinement
|
||||
|
||||
#### Compilation-Driven Development
|
||||
- **Error Reduction**: Systematic approach reduced errors from 43 to 0
|
||||
- **Focused Fixes**: Each compilation cycle provided clear next steps
|
||||
- **Quality Assurance**: Compiler ensured type safety and correctness
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
### For Developers
|
||||
|
||||
#### Service Access Pattern
|
||||
```rust
|
||||
use crate::services::ServiceFactory;
|
||||
|
||||
// Get currency service instance
|
||||
let currency_service = ServiceFactory::currency_service();
|
||||
|
||||
// Use service methods
|
||||
let supported_currencies = currency_service.get_supported_currencies();
|
||||
```
|
||||
|
||||
#### Adding New Services
|
||||
1. Implement service with builder pattern
|
||||
2. Add singleton method to ServiceFactory
|
||||
3. Update documentation and usage examples
|
||||
4. Migrate existing instantiations to use ServiceFactory
|
||||
|
||||
### For AI Assistants
|
||||
|
||||
#### ServiceFactory Usage Rules
|
||||
1. **Always use ServiceFactory**: Never instantiate services directly
|
||||
2. **No Mock Data**: Only use persistent data from `user_data/` directory
|
||||
3. **Follow Patterns**: Use established singleton patterns for new services
|
||||
4. **Document Changes**: Update this documentation when adding services
|
||||
|
||||
#### Migration Best Practices
|
||||
1. **Systematic Approach**: Use compilation errors to guide fixes
|
||||
2. **Manual Refinement**: Don't rely solely on automated scripts
|
||||
3. **Test Incrementally**: Verify compilation after each major change
|
||||
4. **Clean Up**: Remove unused imports and dead code
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Planned Improvements
|
||||
|
||||
1. **Service Discovery**: Automatic service registration and discovery
|
||||
2. **Configuration Management**: Centralized service configuration
|
||||
3. **Health Monitoring**: Service health checks and monitoring
|
||||
4. **Dependency Injection**: Full dependency injection container
|
||||
|
||||
### Integration Opportunities
|
||||
|
||||
1. **ConfigurationBuilder**: Integrate with centralized configuration
|
||||
2. **ResponseBuilder**: Coordinate with API response patterns
|
||||
3. **SessionDataBuilder**: Align with user data management
|
||||
4. **Error Handling**: Standardized error handling across services
|
||||
|
||||
## Conclusion
|
||||
|
||||
The ServiceFactory architecture successfully consolidates service instantiation throughout the Project Mycelium, eliminating mock data usage and establishing industry-standard patterns. This foundation enables scalable, maintainable service management and sets the stage for further architectural improvements.
|
||||
|
||||
The migration demonstrates the effectiveness of systematic, compilation-driven development combined with manual refinement for complex architectural changes. This approach should be used as a template for future consolidation efforts.
|
65
docs/dev/design/archive/vision/ux/current_personas.md
Normal file
65
docs/dev/design/archive/vision/ux/current_personas.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Current Personas and Fixture-backed Marketplace State
|
||||
|
||||
Last Updated: 2025-08-09
|
||||
|
||||
This document captures the canonical demo/test state of the Project Mycelium when running with fixtures.
|
||||
It enables developers and reviewers to reproduce the exact marketplace state and verify the intended UX.
|
||||
|
||||
- Data Source: fixtures (filesystem)
|
||||
- How to run:
|
||||
- APP_DATA_SOURCE=fixtures
|
||||
- APP_FIXTURES_PATH=./user_data
|
||||
- APP_DEMO_MODE=true (optional banner and safe guards)
|
||||
- APP_ENABLE_MOCKS=false (we do not rely on MockDataService)
|
||||
|
||||
## Snapshot
|
||||
|
||||
- Users: 10 (user1..user10)
|
||||
- Providers: user1..user3
|
||||
- Mixed (provider+buyer): user4..user6
|
||||
- Buyers: user7..user10
|
||||
- Products: 3 (seeded; will expand)
|
||||
- WireGuard VPN (category: applications) — provider: user1@example.com — featured
|
||||
- Public Gateway Bundle (category: gateways) — provider: user2@example.com
|
||||
- Object Storage (100GB) (category: applications) — provider: user3@example.com
|
||||
- Services: TBD
|
||||
- Orders: TBD
|
||||
|
||||
## Personas (credentials for demo)
|
||||
|
||||
These credentials are for demo/test environments only. Do not ship to production.
|
||||
|
||||
| Persona | Email | Password | Roles |
|
||||
|---------|---------------------|----------|---------------------|
|
||||
| user1 | user1@example.com | password | ["provider"] |
|
||||
| user2 | user2@example.com | password | ["provider"] |
|
||||
| user3 | user3@example.com | password | ["provider"] |
|
||||
| user4 | user4@example.com | password | ["provider","buyer"] |
|
||||
| user5 | user5@example.com | password | ["provider","buyer"] |
|
||||
| user6 | user6@example.com | password | ["provider","buyer"] |
|
||||
| user7 | user7@example.com | password | ["buyer"] |
|
||||
| user8 | user8@example.com | password | ["buyer"] |
|
||||
| user9 | user9@example.com | password | ["buyer"] |
|
||||
| user10 | user10@example.com | password | ["buyer"] |
|
||||
|
||||
## Ownership and Data Rules
|
||||
|
||||
- No orphan data: every product/service has an owner_user_id.
|
||||
- Orders link buyer_user_id -> items -> product_id/service_id.
|
||||
- Future payments will be linked via payment records.
|
||||
|
||||
## Known Scenarios to Verify
|
||||
|
||||
- Buyer journey (add to cart, buy-now, view invoice).
|
||||
- Provider publishing a product/service and seeing it live.
|
||||
- Insufficient balance UX contract where applicable.
|
||||
|
||||
## Reset and Updates
|
||||
|
||||
- Reset to seed (planned): cargo run -- seed --reset
|
||||
- Fixtures path: ./user_data
|
||||
- Versioning: see ./user_data/version.json
|
||||
|
||||
## Change Log
|
||||
|
||||
- 2025-08-09: Initial scaffold with 10 demo users, pending product/service/order seeds.
|
@@ -0,0 +1,75 @@
|
||||
# Manual Test Checklist — Fixtures Mode
|
||||
|
||||
Last Updated: 2025-08-10
|
||||
|
||||
This checklist validates the Project Mycelium when running with fixture data in `user_data/`.
|
||||
|
||||
## Prereqs
|
||||
- APP_DATA_SOURCE=fixtures
|
||||
- APP_FIXTURES_PATH=./user_data
|
||||
- APP_ENABLE_MOCKS=0
|
||||
- APP_CATALOG_CACHE=true
|
||||
- APP_CATALOG_CACHE_TTL_SECS=5
|
||||
- Optional: APP_DEMO_MODE=true
|
||||
- Command: `make fixtures-run` (or `make fixtures-check` to compile only)
|
||||
|
||||
## Personas
|
||||
- Users: `user1@example.com` … `user10@example.com`
|
||||
- Password: `password`
|
||||
- Roles: see `docs/dev/design/current/ux/current_personas.md`
|
||||
|
||||
## Sanity: Products and Categories
|
||||
- [ ] Home/Marketplace page loads without error.
|
||||
- [ ] Exactly 3 seeded products are visible:
|
||||
- WireGuard VPN (applications) — featured
|
||||
- Public Gateway Bundle (gateways)
|
||||
- Object Storage (100GB) (applications)
|
||||
- [ ] Categories auto-derived include `applications` and `gateways`.
|
||||
- [ ] Featured section shows WireGuard VPN.
|
||||
- [ ] Product detail page renders for each seeded product (name, description, price, provider).
|
||||
|
||||
## Dev Cache Behavior (Phase 0)
|
||||
- [ ] With `APP_CATALOG_CACHE=true` and `APP_CATALOG_CACHE_TTL_SECS=5`, refresh the marketplace twice within 5s; second load should be faster locally and show identical results.
|
||||
- [ ] Edit a product field in `user_data/products.json` temporarily; refresh before TTL → old value; after TTL (or restart) → new value. Revert the edit after the check.
|
||||
- [ ] No duplicate products appear across sections due to catalog deduplication.
|
||||
- [ ] Category pages reflect normalized singular IDs (e.g., `applications` → `application`).
|
||||
|
||||
## Search and Filters
|
||||
- [ ] Search "vpn" returns WireGuard VPN.
|
||||
- [ ] Search "storage" returns Object Storage (100GB).
|
||||
- [ ] Filter by category `applications` shows 2 products; `gateways` shows 1 product.
|
||||
|
||||
## Cart Flow (user7@example.com - buyer)
|
||||
- [ ] Login as user7 → navigate back to marketplace.
|
||||
- [ ] Add WireGuard VPN to cart.
|
||||
- [ ] Cart badge updates immediately (global `emitCartUpdated` behavior) and persists on refresh.
|
||||
- [ ] Proceed to cart → item, price, currency are correct.
|
||||
- [ ] Checkout succeeds and creates an order entry.
|
||||
|
||||
## Buy-Now Flow (user8@example.com - buyer)
|
||||
- [ ] From product detail, click Buy Now for Public Gateway Bundle.
|
||||
- [ ] Order completes without error.
|
||||
- [ ] Orders dashboard shows the new order.
|
||||
|
||||
## Invoice View (both cart and buy-now)
|
||||
- [ ] From Orders dashboard, click "View Invoice" for each order.
|
||||
- [ ] Invoice page renders with correct order details.
|
||||
- [ ] Print dialog works (browser print-to-PDF acceptable).
|
||||
- [ ] Buy-now invoice is accessible (regression: endpoint falls back to persistent storage).
|
||||
|
||||
## Insufficient Balance UX
|
||||
- [ ] Attempt a purchase that should exceed balance (if needed, temporarily adjust product price or reduce user wallet balance in user JSON for testing).
|
||||
- [ ] Error follows the unified contract documented in architecture/prompt files (wording and structure consistent across flows).
|
||||
|
||||
## Provider Perspective (user1@example.com - provider)
|
||||
- [ ] Login as user1 and verify WireGuard VPN appears under provider listings (if such view exists) or can be filtered by provider.
|
||||
- [ ] Ensure provider name/email appears correctly on product detail.
|
||||
|
||||
## General
|
||||
- [ ] No references to MockDataService are used at runtime in fixtures mode (logs free of mock warnings).
|
||||
- [ ] Currency display matches configuration; prices render with expected currency code.
|
||||
- [ ] Static assets and templates load correctly; no console errors in browser.
|
||||
|
||||
## Notes / Findings
|
||||
- Record any discrepancies, missing data, or UX issues.
|
||||
- If changes are made to fixtures, re-run `make fixtures-check` and test again.
|
Reference in New Issue
Block a user