forked from jsnbuchanan/crowd-funder-for-time-pwa
feat(activeDid): implement migration to separate active_identity table
- Add migration 003 with data migration logic to prevent data loss - Create dedicated ActiveIdentity interface in separate file for better architecture - Implement $getActiveIdentity method in PlatformServiceMixin - Enhance $updateActiveDid with dual-write pattern for backward compatibility - Maintain separation of concerns between settings and active identity types - Follow project architectural pattern with dedicated type definition files The migration creates active_identity table alongside existing settings, automatically copying existing activeDid data to prevent user data loss. Dual-write pattern ensures backward compatibility during transition. Migration includes: - Schema creation with proper constraints and indexes - Automatic data transfer from settings.activeDid to active_identity.activeDid - Validation to ensure data exists before migration - Atomic operation: schema and data migration happen together
This commit is contained in:
@@ -117,10 +117,10 @@ runs on existing databases.
|
||||
|
||||
### **2. Type Definitions**
|
||||
|
||||
Create ActiveIdentity interface in `src/db/tables/settings.ts`:
|
||||
Create ActiveIdentity interface in `src/db/tables/activeIdentity.ts`:
|
||||
|
||||
```typescript
|
||||
// Add to src/db/tables/settings.ts
|
||||
// Create new file: src/db/tables/activeIdentity.ts
|
||||
export interface ActiveIdentity {
|
||||
id: number;
|
||||
activeDid: string;
|
||||
@@ -128,6 +128,9 @@ export interface ActiveIdentity {
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: This maintains separation of concerns by keeping active identity types
|
||||
separate from settings types, following the project's architectural pattern.
|
||||
|
||||
### **3. PlatformServiceMixin Methods**
|
||||
|
||||
Implement required methods in `src/utils/PlatformServiceMixin.ts`:
|
||||
|
||||
@@ -124,6 +124,33 @@ const MIGRATIONS = [
|
||||
ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE;
|
||||
`,
|
||||
},
|
||||
{
|
||||
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 != '');
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
14
src/db/tables/activeIdentity.ts
Normal file
14
src/db/tables/activeIdentity.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* ActiveIdentity type describes the active identity selection.
|
||||
* This replaces the activeDid field in the settings table for better
|
||||
* database architecture and data integrity.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @since 2025-08-29
|
||||
*/
|
||||
|
||||
export interface ActiveIdentity {
|
||||
id: number;
|
||||
activeDid: string;
|
||||
lastUpdated: string;
|
||||
}
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
generateInsertStatement,
|
||||
generateUpdateStatement,
|
||||
} from "@/utils/sqlHelpers";
|
||||
import { ActiveIdentity } from "@/db/tables/activeIdentity";
|
||||
|
||||
// =================================================
|
||||
// TYPESCRIPT INTERFACES
|
||||
@@ -548,6 +549,40 @@ export const PlatformServiceMixin = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get active identity from the new active_identity table
|
||||
* This replaces the activeDid field in settings for better architecture
|
||||
*/
|
||||
async $getActiveIdentity(): Promise<ActiveIdentity> {
|
||||
try {
|
||||
const result = await this.$dbQuery(
|
||||
"SELECT id, activeDid, lastUpdated FROM active_identity WHERE id = 1",
|
||||
);
|
||||
|
||||
if (result?.values?.length) {
|
||||
const [id, activeDid, lastUpdated] = result.values[0];
|
||||
return {
|
||||
id: id as number,
|
||||
activeDid: activeDid as string,
|
||||
lastUpdated: lastUpdated as string,
|
||||
};
|
||||
}
|
||||
|
||||
// Return default if no record exists
|
||||
return {
|
||||
id: 1,
|
||||
activeDid: "",
|
||||
lastUpdated: new Date().toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
"[PlatformServiceMixin] Error getting active identity:",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Transaction wrapper with automatic rollback on error
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user