forked from jsnbuchanan/crowd-funder-for-time-pwa
424 lines
14 KiB
Markdown
424 lines
14 KiB
Markdown
# ActiveDid Migration Plan - Implementation Guide
|
|
|
|
**Author**: Matthew Raymer
|
|
**Date**: 2025-08-29T08:03Z
|
|
**Status**: 🎯 **IMPLEMENTATION** - Ready for development
|
|
|
|
## Objective
|
|
|
|
Move the `activeDid` field from the `settings` table to a dedicated `active_identity` table to improve database architecture, prevent data corruption, and separate identity selection from user preferences.
|
|
|
|
## Result
|
|
|
|
This document provides the specific implementation steps required to complete the ActiveDid migration with all necessary code changes.
|
|
|
|
## Use/Run
|
|
|
|
Follow this implementation checklist step-by-step to complete the migration.
|
|
|
|
## Context & Scope
|
|
|
|
- **In scope**: Database migration, API updates, component updates, testing
|
|
- **Out of scope**: UI changes, authentication flow changes, MASTER_SETTINGS_KEY elimination (future improvement)
|
|
|
|
## Implementation Checklist
|
|
|
|
### Phase 1: Database Migration ✅
|
|
- [x] Add migration to MIGRATIONS array
|
|
- [x] Create active_identity table with constraints
|
|
|
|
### Phase 2: API Layer Updates ❌
|
|
- [ ] Implement `$getActiveIdentity()` method
|
|
- [ ] Update `$accountSettings()` to use new table
|
|
- [ ] Update `$updateActiveDid()` with dual-write pattern
|
|
|
|
### Phase 3: Component Updates ❌
|
|
- [ ] Update 35+ components to use `$getActiveIdentity()`
|
|
- [ ] Replace `this.activeDid = settings.activeDid` pattern
|
|
- [ ] Test each component individually
|
|
|
|
### Phase 4: Testing ❌
|
|
- [ ] Test all platforms (Web, Electron, iOS, Android)
|
|
- [ ] Test migration rollback scenarios
|
|
- [ ] Test data corruption recovery
|
|
|
|
## Required Code Changes
|
|
|
|
### 1. Database Migration
|
|
|
|
```typescript
|
|
// Add to MIGRATIONS array in src/db-sql/migration.ts
|
|
{
|
|
name: "003_active_did_separate_table",
|
|
sql: `
|
|
-- Create new active_identity table with proper constraints
|
|
CREATE TABLE IF NOT EXISTS active_identity (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
activeDid TEXT NOT NULL,
|
|
lastUpdated TEXT NOT NULL DEFAULT (datetime('now')),
|
|
FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE CASCADE
|
|
);
|
|
|
|
-- Add performance indexes
|
|
CREATE INDEX IF NOT EXISTS idx_active_identity_activeDid ON active_identity(activeDid);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id);
|
|
|
|
-- Insert default record (will be updated during migration)
|
|
INSERT OR IGNORE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now'));
|
|
|
|
-- MIGRATE EXISTING DATA: Copy activeDid from settings to active_identity
|
|
-- This prevents data loss when migration runs on existing databases
|
|
UPDATE active_identity
|
|
SET activeDid = (SELECT activeDid FROM settings WHERE id = 1),
|
|
lastUpdated = datetime('now')
|
|
WHERE id = 1
|
|
AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != '');
|
|
`,
|
|
},
|
|
```
|
|
|
|
### 2. Missing API Method Implementation
|
|
|
|
```typescript
|
|
// Add to PlatformServiceMixin.ts
|
|
async $getActiveIdentity(): Promise<{ activeDid: string }> {
|
|
try {
|
|
const result = await this.$dbQuery(
|
|
"SELECT activeDid FROM active_identity WHERE id = 1"
|
|
);
|
|
|
|
if (result?.values?.length) {
|
|
const activeDid = result.values[0][0] as string;
|
|
|
|
// Validate activeDid exists in accounts
|
|
if (activeDid) {
|
|
const accountExists = await this.$dbQuery(
|
|
"SELECT did FROM accounts WHERE did = ?",
|
|
[activeDid]
|
|
);
|
|
|
|
if (accountExists?.values?.length) {
|
|
return { activeDid };
|
|
} else {
|
|
// Clear corrupted activeDid
|
|
await this.$dbExec(
|
|
"UPDATE active_identity SET activeDid = '', lastUpdated = datetime('now') WHERE id = 1"
|
|
);
|
|
return { activeDid: "" };
|
|
}
|
|
}
|
|
}
|
|
|
|
return { activeDid: "" };
|
|
} catch (error) {
|
|
logger.error("[PlatformServiceMixin] Error getting active identity:", error);
|
|
return { activeDid: "" };
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Updated $accountSettings Method
|
|
|
|
```typescript
|
|
// Update in PlatformServiceMixin.ts
|
|
async $accountSettings(did?: string, defaults: Settings = {}): Promise<Settings> {
|
|
try {
|
|
// Get settings without activeDid (unchanged logic)
|
|
const settings = await this._getSettingsWithoutActiveDid();
|
|
|
|
if (!settings) {
|
|
return defaults;
|
|
}
|
|
|
|
// Get activeDid from new table (new logic)
|
|
const activeIdentity = await this.$getActiveIdentity();
|
|
|
|
// Return combined result (maintains backward compatibility)
|
|
return { ...settings, activeDid: activeIdentity.activeDid };
|
|
} catch (error) {
|
|
logger.error("[Settings Trace] ❌ Error in $accountSettings:", error);
|
|
return defaults;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Component Updates Required
|
|
|
|
**35+ components need this pattern change:**
|
|
|
|
```typescript
|
|
// CURRENT PATTERN (replace in all components):
|
|
this.activeDid = settings.activeDid || "";
|
|
|
|
// NEW PATTERN (use in all components):
|
|
const activeIdentity = await this.$getActiveIdentity();
|
|
this.activeDid = activeIdentity.activeDid || "";
|
|
```
|
|
|
|
**Components requiring updates:**
|
|
|
|
#### Views (25 components)
|
|
- `src/views/DIDView.vue` (line 378)
|
|
- `src/views/TestView.vue` (line 654)
|
|
- `src/views/ContactAmountsView.vue` (line 226)
|
|
- `src/views/HomeView.vue` (line 517)
|
|
- `src/views/UserProfileView.vue` (line 185)
|
|
- `src/views/ClaimView.vue` (line 730)
|
|
- `src/views/OfferDetailsView.vue` (line 435)
|
|
- `src/views/QuickActionBvcEndView.vue` (line 229)
|
|
- `src/views/SharedPhotoView.vue` (line 178)
|
|
- `src/views/ClaimReportCertificateView.vue` (line 56)
|
|
- `src/views/ProjectsView.vue` (line 393)
|
|
- `src/views/ClaimAddRawView.vue` (line 114)
|
|
- `src/views/ContactQRScanShowView.vue` (line 288)
|
|
- `src/views/InviteOneAcceptView.vue` (line 122)
|
|
- `src/views/RecentOffersToUserView.vue` (line 118)
|
|
- `src/views/NewEditProjectView.vue` (line 380)
|
|
- `src/views/GiftedDetailsView.vue` (line 443)
|
|
- `src/views/ProjectViewView.vue` (line 782)
|
|
- `src/views/ContactsView.vue` (line 296)
|
|
- `src/views/ContactQRScanFullView.vue` (line 267)
|
|
- `src/views/NewActivityView.vue` (line 204)
|
|
- `src/views/ClaimCertificateView.vue` (line 42)
|
|
- `src/views/ContactGiftingView.vue` (line 166)
|
|
- `src/views/RecentOffersToUserProjectsView.vue` (line 126)
|
|
- `src/views/InviteOneView.vue` (line 285)
|
|
- `src/views/IdentitySwitcherView.vue` (line 202)
|
|
- `src/views/AccountViewView.vue` (line 1052)
|
|
- `src/views/ConfirmGiftView.vue` (line 549)
|
|
- `src/views/ContactImportView.vue` (line 342)
|
|
|
|
#### Components (10 components)
|
|
- `src/components/OfferDialog.vue` (line 177)
|
|
- `src/components/PhotoDialog.vue` (line 270)
|
|
- `src/components/GiftedDialog.vue` (line 223)
|
|
- `src/components/MembersList.vue` (line 234)
|
|
- `src/components/OnboardingDialog.vue` (line 272)
|
|
- `src/components/ImageMethodDialog.vue` (line 502)
|
|
- `src/components/FeedFilters.vue` (line 89)
|
|
|
|
**Implementation Strategy:**
|
|
|
|
1. **Systematic Replacement**: Use grep search to find all instances
|
|
2. **Pattern Matching**: Replace `this.activeDid = settings.activeDid` with new pattern
|
|
3. **Error Handling**: Ensure proper error handling in each component
|
|
4. **Testing**: Test each component individually after update
|
|
|
|
**Example Component Update:**
|
|
|
|
```typescript
|
|
// BEFORE (in any component):
|
|
private async initializeSettings() {
|
|
const settings = await this.$accountSettings();
|
|
this.activeDid = settings.activeDid || "";
|
|
this.apiServer = settings.apiServer || "";
|
|
}
|
|
|
|
// AFTER (in any component):
|
|
private async initializeSettings() {
|
|
const settings = await this.$accountSettings();
|
|
const activeIdentity = await this.$getActiveIdentity();
|
|
this.activeDid = activeIdentity.activeDid || "";
|
|
this.apiServer = settings.apiServer || "";
|
|
}
|
|
```
|
|
|
|
**Alternative Pattern (if settings still needed):**
|
|
|
|
```typescript
|
|
// If component needs both settings and activeDid:
|
|
private async initializeSettings() {
|
|
const settings = await this.$accountSettings();
|
|
const activeIdentity = await this.$getActiveIdentity();
|
|
|
|
// Use activeDid from new table
|
|
this.activeDid = activeIdentity.activeDid || "";
|
|
|
|
// Use other settings from settings table
|
|
this.apiServer = settings.apiServer || "";
|
|
this.partnerApiServer = settings.partnerApiServer || "";
|
|
// ... other settings
|
|
}
|
|
```
|
|
|
|
### 5. Data Migration Function
|
|
|
|
```typescript
|
|
// Add to migration.ts
|
|
async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
|
|
const result: MigrationResult = {
|
|
success: false,
|
|
errors: [],
|
|
warnings: [],
|
|
dataMigrated: 0
|
|
};
|
|
|
|
try {
|
|
// 1. Get current activeDid from settings (legacy approach)
|
|
const currentSettings = await retrieveSettingsForDefaultAccount();
|
|
const activeDid = currentSettings.activeDid;
|
|
|
|
if (!activeDid) {
|
|
result.warnings.push("No activeDid found in current settings");
|
|
return result;
|
|
}
|
|
|
|
// 2. Validate activeDid exists in accounts table
|
|
const accountExists = await dbQuery(
|
|
"SELECT did FROM accounts WHERE did = ?",
|
|
[activeDid]
|
|
);
|
|
|
|
if (!accountExists?.values?.length) {
|
|
result.errors.push(`ActiveDid ${activeDid} not found in accounts table - data corruption detected`);
|
|
return result;
|
|
}
|
|
|
|
// 3. Update active_identity table (new system)
|
|
await dbExec(
|
|
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
|
[activeDid]
|
|
);
|
|
|
|
// 4. Ensure legacy settings.activeDid stays in sync (backward compatibility)
|
|
await dbExec(
|
|
"UPDATE settings SET activeDid = ? WHERE id = ?",
|
|
[activeDid, MASTER_SETTINGS_KEY]
|
|
);
|
|
|
|
result.dataMigrated = 1;
|
|
result.warnings.push(`Successfully migrated activeDid: ${activeDid}`);
|
|
|
|
} catch (error) {
|
|
logger.error("[PlatformServiceMixin] Error getting active identity:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
## What Works (Evidence)
|
|
|
|
- ✅ **Current activeDid storage** in settings table
|
|
- **Time**: 2025-08-29T08:03Z
|
|
- **Evidence**: `src/db-sql/migration.ts:67` - activeDid field exists in initial migration
|
|
- **Verify at**: Current database schema and Settings type definition
|
|
|
|
- ✅ **PlatformServiceMixin integration** with activeDid
|
|
- **Time**: 2025-08-29T08:03Z
|
|
- **Evidence**: `src/utils/PlatformServiceMixin.ts:108` - activeDid tracking
|
|
- **Verify at**: Component usage across all platforms
|
|
|
|
- ✅ **Database migration infrastructure** exists
|
|
- **Time**: 2025-08-29T08:03Z
|
|
- **Evidence**: `src/db-sql/migration.ts:31` - migration system in place
|
|
- **Verify at**: Existing migration scripts and database versioning
|
|
|
|
## What Doesn't (Evidence & Hypotheses)
|
|
|
|
- ❌ **No separate active_identity table** exists
|
|
- **Time**: 2025-08-29T08:03Z
|
|
- **Evidence**: Database schema only shows settings table
|
|
- **Hypothesis**: Table needs to be created as part of migration
|
|
- **Next probe**: Add migration to existing MIGRATIONS array
|
|
|
|
- ❌ **Missing $getActiveIdentity() method** in PlatformServiceMixin
|
|
- **Time**: 2025-08-29T08:03Z
|
|
- **Evidence**: Method referenced in plan but not implemented
|
|
- **Hypothesis**: Method needs to be added to PlatformServiceMixin
|
|
- **Next probe**: Implement method with proper error handling
|
|
|
|
- ❌ **35+ components need updates** to use new API
|
|
- **Time**: 2025-08-29T08:03Z
|
|
- **Evidence**: Grep search found 35+ instances of `this.activeDid = settings.activeDid`
|
|
- **Hypothesis**: All components need to be updated to use `$getActiveIdentity()`
|
|
- **Next probe**: Update each component individually and test
|
|
|
|
## Risks, Limits, Assumptions
|
|
|
|
- **Data Loss Risk**: Migration failure could lose activeDid values
|
|
- **Breaking Changes**: API updates required in PlatformServiceMixin
|
|
- **Testing Overhead**: All platforms must be tested with new structure
|
|
- **Component Updates**: 35+ components need individual updates and testing
|
|
|
|
## Rollback Strategy
|
|
|
|
### Schema Rollback
|
|
```sql
|
|
-- If migration fails, restore original schema
|
|
DROP TABLE IF EXISTS active_identity;
|
|
```
|
|
|
|
### Data Rollback
|
|
```typescript
|
|
// Rollback function to restore activeDid to settings table
|
|
async function rollbackActiveDidMigration(): Promise<boolean> {
|
|
try {
|
|
const activeIdentityResult = await dbQuery(
|
|
"SELECT activeDid FROM active_identity WHERE id = 1"
|
|
);
|
|
|
|
if (activeIdentityResult?.values?.length) {
|
|
const activeDid = activeIdentityResult.values[0][0] as string;
|
|
|
|
await dbExec(
|
|
"UPDATE settings SET activeDid = ? WHERE id = ?",
|
|
[activeDid, MASTER_SETTINGS_KEY]
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
} catch (error) {
|
|
logger.error("[Rollback] Failed to restore activeDid:", error);
|
|
return false;
|
|
}
|
|
}
|
|
```
|
|
|
|
## Next Steps
|
|
|
|
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
|
|-------|------|---------------|-------------------|
|
|
| Development Team | Add migration to MIGRATIONS array | Migration script integrated | 2025-08-30 |
|
|
| Development Team | Implement $getActiveIdentity() method | Method added to PlatformServiceMixin | 2025-08-30 |
|
|
| Development Team | Update $accountSettings method | Method updated and tested | 2025-08-30 |
|
|
| Development Team | Update 35+ components | All components use new API | 2025-08-31 |
|
|
| QA Team | Platform testing | All platforms tested and verified | 2025-09-01 |
|
|
| Development Team | Deploy migration | Production deployment successful | 2025-09-02 |
|
|
|
|
## Future Improvement: MASTER_SETTINGS_KEY Elimination
|
|
|
|
**Not critical for this task** but logged for future improvement:
|
|
|
|
```typescript
|
|
// Current: WHERE id = "1"
|
|
// Future: WHERE accountDid IS NULL
|
|
|
|
// This eliminates the confusing concept of "master" settings
|
|
// and uses a cleaner pattern for default settings
|
|
```
|
|
|
|
## References
|
|
|
|
- [Database Migration Guide](./database-migration-guide.md)
|
|
- [Dexie to SQLite Mapping](./dexie-to-sqlite-mapping.md)
|
|
- [PlatformServiceMixin Documentation](./component-communication-guide.md)
|
|
|
|
## Competence Hooks
|
|
|
|
- *Why this works*: Separates concerns between identity selection and user preferences, prevents data corruption with foreign key constraints
|
|
- *Common pitfalls*: Forgetting to update all 35+ components, not implementing $getActiveIdentity() method, missing data validation during migration
|
|
- *Next skill unlock*: Systematic component updates with grep search and testing
|
|
- *Teach-back*: Explain why all components need updates and how to systematically find and replace the pattern
|
|
|
|
## Collaboration Hooks
|
|
|
|
- **Reviewers**: Database team, PlatformServiceMixin maintainers, QA team
|
|
- **Sign-off checklist**:
|
|
- [ ] Migration script integrated with existing MIGRATIONS array
|
|
- [ ] $getActiveIdentity() method implemented and tested
|
|
- [ ] All 35+ components updated to use new API
|
|
- [ ] Rollback procedures validated
|
|
- [ ] All platforms tested
|
|
- [ ] All stakeholders approve deployment timeline |