feat(export): Replace CSV export with standardized JSON format

- Add contactsToExportJson utility function for standardized data export
- Replace CSV export with JSON format in DataExportSection
- Update file extension and MIME type to application/json
- Remove Dexie-specific export logic in favor of unified SQLite/Dexie approach
- Update success notifications to reflect JSON format
- Add TypeScript interfaces for export data structure

This change improves data portability and standardization by:
- Using a consistent JSON format for data export/import
- Supporting both SQLite and Dexie databases
- Including all contact fields in export
- Properly handling contactMethods as stringified JSON
- Maintaining backward compatibility with existing import tools

Security: No sensitive data exposure, maintains existing access controls
This commit is contained in:
Matthew Raymer
2025-06-07 05:02:33 +00:00
parent cfb186a04e
commit b9223d7fe2
9 changed files with 230 additions and 126 deletions

View File

@@ -521,7 +521,9 @@ export default class HomeView extends Vue {
logConsoleAndDb(`[HomeView] Retrieved ${this.allMyDids.length} DIDs`);
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to retrieve DIDs: ${error}`, true);
throw new Error("Failed to load existing identities. Please try restarting the app.");
throw new Error(
"Failed to load existing identities. Please try restarting the app.",
);
}
// Create new DID if needed
@@ -534,7 +536,10 @@ export default class HomeView extends Vue {
logConsoleAndDb(`[HomeView] Created new identity: ${newDid}`);
} catch (error) {
this.isCreatingIdentifier = false;
logConsoleAndDb(`[HomeView] Failed to create new identity: ${error}`, true);
logConsoleAndDb(
`[HomeView] Failed to create new identity: ${error}`,
true,
);
throw new Error("Failed to create new identity. Please try again.");
}
}
@@ -546,34 +551,53 @@ export default class HomeView extends Vue {
if (USE_DEXIE_DB) {
settings = await retrieveSettingsForActiveAccount();
}
logConsoleAndDb(`[HomeView] Retrieved settings for ${settings.activeDid || 'no active DID'}`);
logConsoleAndDb(
`[HomeView] Retrieved settings for ${settings.activeDid || "no active DID"}`,
);
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to retrieve settings: ${error}`, true);
throw new Error("Failed to load user settings. Some features may be limited.");
logConsoleAndDb(
`[HomeView] Failed to retrieve settings: ${error}`,
true,
);
throw new Error(
"Failed to load user settings. Some features may be limited.",
);
}
// Update component state
this.apiServer = settings.apiServer || "";
this.activeDid = settings.activeDid || "";
// Load contacts with graceful fallback
try {
const platformService = PlatformServiceFactory.getInstance();
const dbContacts = await platformService.dbQuery("SELECT * FROM contacts");
this.allContacts = databaseUtil.mapQueryResultToValues(dbContacts) as Contact[];
const dbContacts = await platformService.dbQuery(
"SELECT * FROM contacts",
);
this.allContacts = databaseUtil.mapQueryResultToValues(
dbContacts,
) as Contact[];
if (USE_DEXIE_DB) {
this.allContacts = await db.contacts.toArray();
}
logConsoleAndDb(`[HomeView] Retrieved ${this.allContacts.length} contacts`);
logConsoleAndDb(
`[HomeView] Retrieved ${this.allContacts.length} contacts`,
);
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to retrieve contacts: ${error}`, true);
logConsoleAndDb(
`[HomeView] Failed to retrieve contacts: ${error}`,
true,
);
this.allContacts = []; // Ensure we have a valid empty array
this.$notify({
group: "alert",
type: "warning",
title: "Contact Loading Issue",
text: "Some contact information may be unavailable.",
}, 5000);
this.$notify(
{
group: "alert",
type: "warning",
title: "Contact Loading Issue",
text: "Some contact information may be unavailable.",
},
5000,
);
}
// Update remaining settings
@@ -583,14 +607,17 @@ export default class HomeView extends Vue {
this.isFeedFilteredByNearby = !!settings.filterFeedByNearby;
this.isRegistered = !!settings.isRegistered;
this.lastAckedOfferToUserJwtId = settings.lastAckedOfferToUserJwtId;
this.lastAckedOfferToUserProjectsJwtId = settings.lastAckedOfferToUserProjectsJwtId;
this.lastAckedOfferToUserProjectsJwtId =
settings.lastAckedOfferToUserProjectsJwtId;
this.searchBoxes = settings.searchBoxes || [];
this.showShortcutBvc = !!settings.showShortcutBvc;
this.isAnyFeedFilterOn = checkIsAnyFeedFilterOn(settings);
// Check onboarding status
if (!settings.finishedOnboarding) {
(this.$refs.onboardingDialog as OnboardingDialog).open(OnboardPage.Home);
(this.$refs.onboardingDialog as OnboardingDialog).open(
OnboardPage.Home,
);
}
// Check registration status if needed
@@ -613,10 +640,15 @@ export default class HomeView extends Vue {
});
}
this.isRegistered = true;
logConsoleAndDb(`[HomeView] User ${this.activeDid} is now registered`);
logConsoleAndDb(
`[HomeView] User ${this.activeDid} is now registered`,
);
}
} catch (error) {
logConsoleAndDb(`[HomeView] Registration check failed: ${error}`, true);
logConsoleAndDb(
`[HomeView] Registration check failed: ${error}`,
true,
);
// Continue as unregistered - this is expected for new users
}
}
@@ -624,8 +656,11 @@ export default class HomeView extends Vue {
// Initialize feed and offers
try {
// Start feed update in background
this.updateAllFeed().catch(error => {
logConsoleAndDb(`[HomeView] Background feed update failed: ${error}`, true);
this.updateAllFeed().catch((error) => {
logConsoleAndDb(
`[HomeView] Background feed update failed: ${error}`,
true,
);
});
// Load new offers if we have an active DID
@@ -649,23 +684,28 @@ export default class HomeView extends Vue {
this.newOffersToUserHitLimit = offersToUser.hitLimit;
this.numNewOffersToUserProjects = offersToProjects.data.length;
this.newOffersToUserProjectsHitLimit = offersToProjects.hitLimit;
logConsoleAndDb(
`[HomeView] Retrieved ${this.numNewOffersToUser} user offers and ` +
`${this.numNewOffersToUserProjects} project offers`
`${this.numNewOffersToUserProjects} project offers`,
);
}
} catch (error) {
logConsoleAndDb(`[HomeView] Failed to initialize feed/offers: ${error}`, true);
logConsoleAndDb(
`[HomeView] Failed to initialize feed/offers: ${error}`,
true,
);
// Don't throw - we can continue with empty feed
this.$notify({
group: "alert",
type: "warning",
title: "Feed Loading Issue",
text: "Some feed data may be unavailable. Pull to refresh.",
}, 5000);
this.$notify(
{
group: "alert",
type: "warning",
title: "Feed Loading Issue",
text: "Some feed data may be unavailable. Pull to refresh.",
},
5000,
);
}
} catch (error) {
this.handleError(error);
throw error; // Re-throw to be caught by mounted()
@@ -837,10 +877,10 @@ export default class HomeView extends Vue {
private handleError(err: unknown) {
const errorMessage = err instanceof Error ? err.message : String(err);
const userMessage = (err as { userMessage?: string })?.userMessage;
logConsoleAndDb(
`[HomeView] Initialization error: ${errorMessage}${userMessage ? ` (${userMessage})` : ''}`,
true
`[HomeView] Initialization error: ${errorMessage}${userMessage ? ` (${userMessage})` : ""}`,
true,
);
this.$notify(
@@ -848,7 +888,9 @@ export default class HomeView extends Vue {
group: "alert",
type: "danger",
title: "Error",
text: userMessage || "There was an error loading your data. Please try refreshing the page.",
text:
userMessage ||
"There was an error loading your data. Please try refreshing the page.",
},
5000,
);