forked from jsnbuchanan/crowd-funder-for-time-pwa
chore: update plan for handling MASTER_SETTINGS_KEY
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# ActiveDid Migration Plan - Separate Table Architecture
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: 2025-08-29T07:24Z
|
||||
**Date**: 2025-08-29T08:03Z
|
||||
**Status**: 🎯 **PLANNING** - Active migration planning phase
|
||||
|
||||
## Objective
|
||||
@@ -85,29 +85,35 @@ flowchart TD
|
||||
|
||||
## Repro: End-to-End Procedure
|
||||
|
||||
### Phase 1: Enhanced Schema Creation
|
||||
### Phase 1: Enhanced Schema Creation via migration.ts
|
||||
|
||||
```sql
|
||||
-- Create new active_identity table with proper constraints
|
||||
CREATE TABLE 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
|
||||
);
|
||||
```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);
|
||||
-- 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 INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now'));
|
||||
-- Insert default record (will be updated during migration)
|
||||
INSERT OR IGNORE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now'));
|
||||
`,
|
||||
},
|
||||
```
|
||||
|
||||
### Phase 2: Enhanced Data Migration with Validation
|
||||
|
||||
```typescript
|
||||
// Enhanced migration script with comprehensive validation
|
||||
// Enhanced migration function with comprehensive validation
|
||||
async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
|
||||
const result: MigrationResult = {
|
||||
success: false,
|
||||
@@ -117,7 +123,7 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
|
||||
};
|
||||
|
||||
try {
|
||||
// 1. Get current activeDid from settings
|
||||
// 1. Get current activeDid from settings (legacy approach)
|
||||
const currentSettings = await retrieveSettingsForDefaultAccount();
|
||||
const activeDid = currentSettings.activeDid;
|
||||
|
||||
@@ -137,27 +143,20 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 3. Check if active_identity table already has data
|
||||
const existingActiveIdentity = await dbQuery(
|
||||
"SELECT activeDid FROM active_identity WHERE id = 1"
|
||||
// 3. Update active_identity table (new system)
|
||||
await dbExec(
|
||||
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||
[activeDid]
|
||||
);
|
||||
|
||||
if (existingActiveIdentity?.values?.length) {
|
||||
// Update existing record
|
||||
await dbExec(
|
||||
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||
[activeDid]
|
||||
);
|
||||
} else {
|
||||
// Insert new record
|
||||
await dbExec(
|
||||
"INSERT INTO active_identity (id, activeDid, lastUpdated) VALUES (1, ?, datetime('now'))",
|
||||
[activeDid]
|
||||
);
|
||||
}
|
||||
// 4. Ensure legacy settings.activeDid stays in sync (backward compatibility)
|
||||
// This maintains compatibility with IndexedDB migration service
|
||||
await dbExec(
|
||||
"UPDATE settings SET activeDid = ? WHERE id = ?",
|
||||
[activeDid, MASTER_SETTINGS_KEY]
|
||||
);
|
||||
|
||||
result.success = true;
|
||||
result.dataMigrated = 1;
|
||||
dataMigrated = 1;
|
||||
result.warnings.push(`Successfully migrated activeDid: ${activeDid}`);
|
||||
|
||||
} catch (error) {
|
||||
@@ -167,17 +166,9 @@ async function migrateActiveDidToSeparateTable(): Promise<MigrationResult> {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Migration result interface
|
||||
interface MigrationResult {
|
||||
success: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
dataMigrated: number;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Focused API Updates
|
||||
### Phase 3: Focused API Updates with Dual-Write Pattern
|
||||
|
||||
```typescript
|
||||
// Updated PlatformServiceMixin method - maintains backward compatibility
|
||||
@@ -201,93 +192,20 @@ async $accountSettings(did?: string, defaults: Settings = {}): Promise<Settings>
|
||||
}
|
||||
}
|
||||
|
||||
// New method for active identity management
|
||||
async $getActiveIdentity(): Promise<{ activeDid: string | null }> {
|
||||
try {
|
||||
const result = await this.$dbQuery(
|
||||
"SELECT activeDid FROM active_identity WHERE id = 1"
|
||||
);
|
||||
|
||||
if (!result?.values?.length) {
|
||||
return { activeDid: null };
|
||||
}
|
||||
|
||||
return { activeDid: result.values[0][0] as string };
|
||||
} catch (error) {
|
||||
logger.error("[Settings Trace] ❌ Failed to get active identity:", error);
|
||||
return { activeDid: null };
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced method to get settings without activeDid
|
||||
async _getSettingsWithoutActiveDid(): Promise<Settings> {
|
||||
const result = await this.$dbQuery(
|
||||
"SELECT id, accountDid, apiServer, filterFeedByNearby, filterFeedByVisible, " +
|
||||
"finishedOnboarding, firstName, hideRegisterPromptOnNewContact, isRegistered, " +
|
||||
"lastName, lastAckedOfferToUserJwtId, lastAckedOfferToUserProjectsJwtId, " +
|
||||
"lastNotifiedClaimId, lastViewedClaimId, notifyingNewActivityTime, " +
|
||||
"notifyingReminderMessage, notifyingReminderTime, partnerApiServer, " +
|
||||
"passkeyExpirationMinutes, profileImageUrl, searchBoxes, showContactGivesInline, " +
|
||||
"showGeneralAdvanced, showShortcutBvc, vapid, warnIfProdServer, warnIfTestServer, " +
|
||||
"webPushServer FROM settings WHERE id = ?",
|
||||
[MASTER_SETTINGS_KEY]
|
||||
);
|
||||
|
||||
if (!result?.values?.length) {
|
||||
return DEFAULT_SETTINGS;
|
||||
}
|
||||
|
||||
return this._mapColumnsToValues(result.columns, result.values)[0] as Settings;
|
||||
}
|
||||
|
||||
// Enhanced save settings method
|
||||
async $saveSettings(changes: Partial<Settings>): Promise<boolean> {
|
||||
try {
|
||||
// Remove fields that shouldn't be updated
|
||||
const { accountDid, id, activeDid, ...safeChanges } = changes;
|
||||
|
||||
if (Object.keys(safeChanges).length > 0) {
|
||||
// Convert settings for database storage
|
||||
const convertedChanges = this._convertSettingsForStorage(safeChanges);
|
||||
const setParts: string[] = [];
|
||||
const params: unknown[] = [];
|
||||
|
||||
Object.entries(convertedChanges).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
setParts.push(`${key} = ?`);
|
||||
params.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
if (setParts.length > 0) {
|
||||
params.push(MASTER_SETTINGS_KEY);
|
||||
await this.$dbExec(
|
||||
`UPDATE settings SET ${setParts.join(", ")} WHERE id = ?`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle activeDid separately in new table
|
||||
if (changes.activeDid !== undefined) {
|
||||
await this.$updateActiveDid(changes.activeDid);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error("[PlatformServiceMixin] Error saving settings:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced update activeDid method
|
||||
// Enhanced update activeDid method with dual-write pattern
|
||||
async $updateActiveDid(newDid: string | null): Promise<boolean> {
|
||||
try {
|
||||
if (newDid === null) {
|
||||
// Clear active identity
|
||||
// Clear active identity in both tables
|
||||
await this.$dbExec(
|
||||
"UPDATE active_identity SET activeDid = '', lastUpdated = datetime('now') WHERE id = 1"
|
||||
);
|
||||
|
||||
// Keep legacy field in sync (backward compatibility)
|
||||
await this.$dbExec(
|
||||
"UPDATE settings SET activeDid = '' WHERE id = ?",
|
||||
[MASTER_SETTINGS_KEY]
|
||||
);
|
||||
} else {
|
||||
// Validate DID exists before setting
|
||||
const accountExists = await this.$dbQuery(
|
||||
@@ -300,11 +218,17 @@ async $updateActiveDid(newDid: string | null): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update active identity
|
||||
// Update active identity in new table
|
||||
await this.$dbExec(
|
||||
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
|
||||
[newDid]
|
||||
);
|
||||
|
||||
// Keep legacy field in sync (backward compatibility)
|
||||
await this.$dbExec(
|
||||
"UPDATE settings SET activeDid = ? WHERE id = ?",
|
||||
[newDid, MASTER_SETTINGS_KEY]
|
||||
);
|
||||
}
|
||||
|
||||
// Update internal tracking
|
||||
@@ -431,30 +355,30 @@ async $getMergedSettings(defaultKey: string, accountDid?: string, defaultFallbac
|
||||
## What Works (Evidence)
|
||||
|
||||
- ✅ **Current activeDid storage** in settings table
|
||||
- **Time**: 2025-08-29T07:24Z
|
||||
- **Evidence**: `src/db/tables/settings.ts:25` - activeDid field exists
|
||||
- **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-29T07:24Z
|
||||
- **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-29T07:24Z
|
||||
- **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-29T07:24Z
|
||||
- **Time**: 2025-08-29T08:03Z
|
||||
- **Evidence**: Database schema only shows settings table
|
||||
- **Hypothesis**: Table needs to be created as part of migration
|
||||
- **Next probe**: Create migration script for new table
|
||||
- **Next probe**: Add migration to existing MIGRATIONS array
|
||||
|
||||
- ❌ **Data corruption issues** with orphaned activeDid references
|
||||
- **Time**: 2025-08-29T07:24Z
|
||||
- **Time**: 2025-08-29T08:03Z
|
||||
- **Evidence**: `IdentitySwitcherView.vue:175` - `hasCorruptedIdentity` detection
|
||||
- **Hypothesis**: Current schema allows activeDid to point to non-existent accounts
|
||||
- **Next probe**: Implement foreign key constraints in new table
|
||||
@@ -522,7 +446,7 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
|
||||
|
||||
| Owner | Task | Exit Criteria | Target Date (UTC) |
|
||||
|-------|------|---------------|-------------------|
|
||||
| Development Team | Create enhanced migration script | Migration script with validation and rollback | 2025-08-30 |
|
||||
| Development Team | Add migration to existing MIGRATIONS array | Migration script integrated with existing system | 2025-08-30 |
|
||||
| Development Team | Update type definitions | Settings type updated, ActiveIdentity type created | 2025-08-30 |
|
||||
| Development Team | Update PlatformServiceMixin | Core methods updated and tested | 2025-08-31 |
|
||||
| Development Team | Implement foreign key constraints | Schema validation prevents corruption | 2025-08-31 |
|
||||
@@ -552,7 +476,7 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
|
||||
## Collaboration Hooks
|
||||
|
||||
- **Sign-off checklist**:
|
||||
- [ ] Migration script tested on development database
|
||||
- [ ] Migration script integrated with existing MIGRATIONS array
|
||||
- [ ] Foreign key constraints implemented and tested
|
||||
- [ ] PlatformServiceMixin updated and tested
|
||||
- [ ] Rollback procedures validated
|
||||
@@ -571,31 +495,33 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
|
||||
|
||||
## What Needs to Change
|
||||
|
||||
### **1. Database Schema**
|
||||
### **1. Database Schema via migration.ts**
|
||||
- Add migration to existing MIGRATIONS array in `src/db-sql/migration.ts`
|
||||
- Create `active_identity` table with foreign key constraints
|
||||
- Add performance indexes
|
||||
- Remove `activeDid` field from `settings` table
|
||||
- **Keep `activeDid` field in `settings` table temporarily** for backward compatibility
|
||||
- **Preserve `MASTER_SETTINGS_KEY = "1"`** for legacy migration support
|
||||
|
||||
### **2. PlatformServiceMixin Methods**
|
||||
- `$accountSettings()` - integrate with new table
|
||||
- `$saveSettings()` - handle activeDid in new table
|
||||
- `$updateActiveDid()` - validate and update new table
|
||||
- `$accountSettings()` - integrate with new table while maintaining backward compatibility
|
||||
- `$saveSettings()` - handle activeDid in new table, sync with legacy field
|
||||
- `$updateActiveDid()` - validate and update new table, sync with legacy field
|
||||
- `$getActiveIdentity()` - new method for identity management
|
||||
|
||||
### **3. Master Settings Functions**
|
||||
- `retrieveSettingsForDefaultAccount()` - integrate with new table
|
||||
- `$getMergedSettings()` - integrate with new table
|
||||
- `retrieveSettingsForDefaultAccount()` - integrate with new table while preserving legacy support
|
||||
- `$getMergedSettings()` - integrate with new table while preserving legacy support
|
||||
|
||||
### **4. Migration Scripts**
|
||||
- Create migration script with validation
|
||||
- Implement rollback procedures
|
||||
- Add data corruption detection
|
||||
|
||||
### **5. Type Definitions**
|
||||
- Update Settings type to remove activeDid
|
||||
### **4. Type Definitions**
|
||||
- **Keep `activeDid` in Settings type temporarily** for backward compatibility
|
||||
- Create ActiveIdentity type for new table
|
||||
- Update related interfaces
|
||||
|
||||
### **5. Legacy Compatibility**
|
||||
- **Preserve `MASTER_SETTINGS_KEY = "1"`** for IndexedDB migration service
|
||||
- **Maintain dual-write pattern** during transition period
|
||||
- **Ensure legacy clients can still migrate** from Dexie to SQLite
|
||||
|
||||
## What Doesn't Need to Change
|
||||
|
||||
- **All Vue components** - API layer handles migration transparently
|
||||
@@ -603,3 +529,48 @@ async function rollbackActiveDidMigration(): Promise<boolean> {
|
||||
- **User interface** - No changes to identity selection UI
|
||||
- **Authentication flow** - Existing system unchanged
|
||||
- **Component logic** - All activeDid handling through API methods
|
||||
- **Migration system** - Use existing migration.ts approach, not separate files
|
||||
- **IndexedDB migration service** - Must continue working for legacy clients
|
||||
|
||||
## Enhanced Architecture: Dual-Write Pattern
|
||||
|
||||
### **Phase 1: Add New Table (Current)**
|
||||
```typescript
|
||||
// Create active_identity table
|
||||
// Keep existing settings.activeDid for backward compatibility
|
||||
// Use dual-write pattern during transition
|
||||
```
|
||||
|
||||
### **Phase 2: Dual-Write Pattern**
|
||||
```typescript
|
||||
// When updating activeDid:
|
||||
// 1. Update active_identity table (new system)
|
||||
// 2. Update settings.activeDid (legacy compatibility)
|
||||
// 3. Ensure both stay in sync
|
||||
```
|
||||
|
||||
### **Phase 3: Future Cleanup (Not in Current Scope)**
|
||||
```typescript
|
||||
// Eventually:
|
||||
// 1. Remove activeDid from settings table
|
||||
// 2. Deprecate MASTER_SETTINGS_KEY
|
||||
// 3. Use pure accountDid IS NULL pattern
|
||||
// 4. Update IndexedDB migration service
|
||||
```
|
||||
|
||||
## Backward Compatibility Requirements
|
||||
|
||||
### **Critical: IndexedDB Migration Service**
|
||||
- **Must continue working** for users migrating from Dexie
|
||||
- **Must recognize `id = "1"`** as master settings
|
||||
- **Must preserve existing migration paths**
|
||||
|
||||
### **Important: Legacy Database Operations**
|
||||
- **Must continue working** for existing SQLite databases
|
||||
- **Must handle both old and new patterns**
|
||||
- **Must not break existing queries**
|
||||
|
||||
### **Desired: Cleaner Architecture**
|
||||
- **New operations** use `accountDid IS NULL` pattern
|
||||
- **Legacy operations** continue using `MASTER_SETTINGS_KEY`
|
||||
- **Gradual migration** toward cleaner patterns
|
||||
Reference in New Issue
Block a user