Merge branch 'migrate-dexie-to-sqlite'
This commit is contained in:
75
src/assets/icons.json
Normal file
75
src/assets/icons.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"warning": {
|
||||
"fillRule": "evenodd",
|
||||
"d": "M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z",
|
||||
"clipRule": "evenodd"
|
||||
},
|
||||
"spinner": {
|
||||
"d": "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
},
|
||||
"chart": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||
},
|
||||
"plus": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M12 4v16m8-8H4"
|
||||
},
|
||||
"settings": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
||||
},
|
||||
"settingsDot": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
},
|
||||
"lock": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
|
||||
},
|
||||
"download": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||
},
|
||||
"check": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
},
|
||||
"edit": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
},
|
||||
"trash": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
},
|
||||
"plusCircle": {
|
||||
"strokeLinecap": "round",
|
||||
"strokeLinejoin": "round",
|
||||
"strokeWidth": "2",
|
||||
"d": "M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||
},
|
||||
"info": {
|
||||
"fillRule": "evenodd",
|
||||
"d": "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z",
|
||||
"clipRule": "evenodd"
|
||||
}
|
||||
}
|
||||
90
src/components/IconRenderer.vue
Normal file
90
src/components/IconRenderer.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<svg
|
||||
v-if="iconData"
|
||||
:class="svgClass"
|
||||
:fill="fill"
|
||||
:stroke="stroke"
|
||||
:viewBox="viewBox"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path v-for="(path, index) in iconData.paths" :key="index" v-bind="path" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from "vue-facing-decorator";
|
||||
import icons from "../assets/icons.json";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* Icon path interface
|
||||
*/
|
||||
interface IconPath {
|
||||
d: string;
|
||||
fillRule?: string;
|
||||
clipRule?: string;
|
||||
strokeLinecap?: string;
|
||||
strokeLinejoin?: string;
|
||||
strokeWidth?: string | number;
|
||||
fill?: string;
|
||||
stroke?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon data interface
|
||||
*/
|
||||
interface IconData {
|
||||
paths: IconPath[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Icons JSON structure
|
||||
*/
|
||||
interface IconsJson {
|
||||
[key: string]: IconPath | IconData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon Renderer Component
|
||||
*
|
||||
* This component loads SVG icon definitions from a JSON file and renders them
|
||||
* as SVG elements. It provides a clean way to use icons without cluttering
|
||||
* templates with long SVG path definitions.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 1.0.0
|
||||
* @since 2024
|
||||
*/
|
||||
@Component({
|
||||
name: "IconRenderer",
|
||||
})
|
||||
export default class IconRenderer extends Vue {
|
||||
@Prop({ required: true }) readonly iconName!: string;
|
||||
@Prop({ default: "h-5 w-5" }) readonly svgClass!: string;
|
||||
@Prop({ default: "none" }) readonly fill!: string;
|
||||
@Prop({ default: "currentColor" }) readonly stroke!: string;
|
||||
@Prop({ default: "0 0 24 24" }) readonly viewBox!: string;
|
||||
|
||||
/**
|
||||
* Get the icon data for the specified icon name
|
||||
*
|
||||
* @returns {IconData | null} The icon data object or null if not found
|
||||
*/
|
||||
get iconData(): IconData | null {
|
||||
const icon = (icons as IconsJson)[this.iconName];
|
||||
if (!icon) {
|
||||
logger.warn(`Icon "${this.iconName}" not found in icons.json`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert single path to array format for consistency
|
||||
if ("d" in icon) {
|
||||
return {
|
||||
paths: [icon as IconPath],
|
||||
};
|
||||
}
|
||||
|
||||
return icon as IconData;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,4 +1,7 @@
|
||||
import migrationService from "../services/migrationService";
|
||||
import {
|
||||
registerMigration,
|
||||
runMigrations as runMigrationsService,
|
||||
} from "../services/migrationService";
|
||||
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
||||
import { arrayBufferToBase64 } from "@/libs/crypto";
|
||||
|
||||
@@ -123,16 +126,12 @@ const MIGRATIONS = [
|
||||
* @param extractMigrationNames - A function that extracts the names (string array) from "select name from migrations"
|
||||
*/
|
||||
export async function runMigrations<T>(
|
||||
sqlExec: (sql: string) => Promise<unknown>,
|
||||
sqlQuery: (sql: string) => Promise<T>,
|
||||
sqlExec: (sql: string, params?: unknown[]) => Promise<unknown>,
|
||||
sqlQuery: (sql: string, params?: unknown[]) => Promise<T>,
|
||||
extractMigrationNames: (result: T) => Set<string>,
|
||||
): Promise<void> {
|
||||
for (const migration of MIGRATIONS) {
|
||||
migrationService.registerMigration(migration);
|
||||
registerMigration(migration);
|
||||
}
|
||||
await migrationService.runMigrations(
|
||||
sqlExec,
|
||||
sqlQuery,
|
||||
extractMigrationNames,
|
||||
);
|
||||
await runMigrationsService(sqlExec, sqlQuery, extractMigrationNames);
|
||||
}
|
||||
|
||||
@@ -227,10 +227,28 @@ export async function logConsoleAndDb(
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an SQL INSERT statement and parameters from a model object.
|
||||
* @param model The model object containing fields to update
|
||||
* @param tableName The name of the table to update
|
||||
* @returns Object containing the SQL statement and parameters array
|
||||
* Generates SQL INSERT statement and parameters from a model object
|
||||
*
|
||||
* This helper function creates a parameterized SQL INSERT statement
|
||||
* from a JavaScript object. It filters out undefined values and
|
||||
* creates the appropriate SQL syntax with placeholders.
|
||||
*
|
||||
* The function is used internally by the migration functions to
|
||||
* safely insert data into the SQLite database.
|
||||
*
|
||||
* @function generateInsertStatement
|
||||
* @param {Record<string, unknown>} model - The model object containing fields to insert
|
||||
* @param {string} tableName - The name of the table to insert into
|
||||
* @returns {Object} Object containing the SQL statement and parameters array
|
||||
* @returns {string} returns.sql - The SQL INSERT statement
|
||||
* @returns {unknown[]} returns.params - Array of parameter values
|
||||
* @example
|
||||
* ```typescript
|
||||
* const contact = { did: 'did:example:123', name: 'John Doe' };
|
||||
* const { sql, params } = generateInsertStatement(contact, 'contacts');
|
||||
* // sql: "INSERT INTO contacts (did, name) VALUES (?, ?)"
|
||||
* // params: ['did:example:123', 'John Doe']
|
||||
* ```
|
||||
*/
|
||||
export function generateInsertStatement(
|
||||
model: Record<string, unknown>,
|
||||
@@ -248,12 +266,30 @@ export function generateInsertStatement(
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an SQL UPDATE statement and parameters from a model object.
|
||||
* @param model The model object containing fields to update
|
||||
* @param tableName The name of the table to update
|
||||
* @param whereClause The WHERE clause for the update (e.g. "id = ?")
|
||||
* @param whereParams Parameters for the WHERE clause
|
||||
* @returns Object containing the SQL statement and parameters array
|
||||
* Generates SQL UPDATE statement and parameters from a model object
|
||||
*
|
||||
* This helper function creates a parameterized SQL UPDATE statement
|
||||
* from a JavaScript object. It filters out undefined values and
|
||||
* creates the appropriate SQL syntax with placeholders.
|
||||
*
|
||||
* The function is used internally by the migration functions to
|
||||
* safely update data in the SQLite database.
|
||||
*
|
||||
* @function generateUpdateStatement
|
||||
* @param {Record<string, unknown>} model - The model object containing fields to update
|
||||
* @param {string} tableName - The name of the table to update
|
||||
* @param {string} whereClause - The WHERE clause for the update (e.g. "id = ?")
|
||||
* @param {unknown[]} [whereParams=[]] - Parameters for the WHERE clause
|
||||
* @returns {Object} Object containing the SQL statement and parameters array
|
||||
* @returns {string} returns.sql - The SQL UPDATE statement
|
||||
* @returns {unknown[]} returns.params - Array of parameter values
|
||||
* @example
|
||||
* ```typescript
|
||||
* const contact = { name: 'Jane Doe' };
|
||||
* const { sql, params } = generateUpdateStatement(contact, 'contacts', 'did = ?', ['did:example:123']);
|
||||
* // sql: "UPDATE contacts SET name = ? WHERE did = ?"
|
||||
* // params: ['Jane Doe', 'did:example:123']
|
||||
* ```
|
||||
*/
|
||||
export function generateUpdateStatement(
|
||||
model: Record<string, unknown>,
|
||||
|
||||
@@ -1 +1 @@
|
||||
Check the contact & settings export to see whether you want your new table to be included in it.
|
||||
# Check the contact & settings export to see whether you want your new table to be included in it
|
||||
|
||||
@@ -19,6 +19,10 @@ export interface Contact {
|
||||
registered?: boolean; // cached value of the server setting
|
||||
}
|
||||
|
||||
export type ContactWithJsonStrings = Contact & {
|
||||
contactMethods?: string;
|
||||
};
|
||||
|
||||
export const ContactSchema = {
|
||||
contacts: "&did, name", // no need to key by other things
|
||||
};
|
||||
|
||||
@@ -64,6 +64,11 @@ export type Settings = {
|
||||
webPushServer?: string; // Web Push server URL
|
||||
};
|
||||
|
||||
// type of settings where the searchBoxes are JSON strings instead of objects
|
||||
export type SettingsWithJsonStrings = Settings & {
|
||||
searchBoxes: string;
|
||||
};
|
||||
|
||||
export function checkIsAnyFeedFilterOn(settings: Settings): boolean {
|
||||
return !!(settings?.filterFeedByNearby || settings?.filterFeedByVisible);
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import { sha256 } from "ethereum-cryptography/sha256";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
import { insertDidSpecificSettings, parseJsonField } from "../db/databaseUtil";
|
||||
import { DEFAULT_ROOT_DERIVATION_PATH } from "./crypto";
|
||||
|
||||
export interface GiverReceiverInputInfo {
|
||||
did?: string;
|
||||
@@ -998,3 +999,38 @@ export const contactsToExportJson = (contacts: Contact[]): DatabaseExport => {
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Imports an account from a mnemonic phrase
|
||||
* @param mnemonic - The seed phrase to import from
|
||||
* @param derivationPath - The derivation path to use (defaults to DEFAULT_ROOT_DERIVATION_PATH)
|
||||
* @param shouldErase - Whether to erase existing accounts before importing
|
||||
* @returns Promise that resolves when import is complete
|
||||
* @throws Error if mnemonic is invalid or import fails
|
||||
*/
|
||||
export async function importFromMnemonic(
|
||||
mnemonic: string,
|
||||
derivationPath: string = DEFAULT_ROOT_DERIVATION_PATH,
|
||||
shouldErase: boolean = false,
|
||||
): Promise<void> {
|
||||
const mne: string = mnemonic.trim().toLowerCase();
|
||||
|
||||
// Derive address and keys from mnemonic
|
||||
const [address, privateHex, publicHex] = deriveAddress(mne, derivationPath);
|
||||
|
||||
// Create new identifier
|
||||
const newId = newIdentifier(address, publicHex, privateHex, derivationPath);
|
||||
|
||||
// Handle erasures
|
||||
if (shouldErase) {
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
await platformService.dbExec("DELETE FROM accounts");
|
||||
if (USE_DEXIE_DB) {
|
||||
const accountsDB = await accountsDBPromise;
|
||||
await accountsDB.accounts.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Save the new identity
|
||||
await saveNewIdentity(newId, mne, derivationPath);
|
||||
}
|
||||
|
||||
@@ -148,6 +148,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: "logs",
|
||||
component: () => import("../views/LogView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/database-migration",
|
||||
name: "database-migration",
|
||||
component: () => import("../views/DatabaseMigration.vue"),
|
||||
},
|
||||
{
|
||||
path: "/new-activity",
|
||||
name: "new-activity",
|
||||
|
||||
1373
src/services/indexedDBMigrationService.ts
Normal file
1373
src/services/indexedDBMigrationService.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,60 +1,150 @@
|
||||
/**
|
||||
* Manage database migrations as people upgrade their app over time
|
||||
*/
|
||||
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
/**
|
||||
* Migration interface for database schema migrations
|
||||
*/
|
||||
interface Migration {
|
||||
name: string;
|
||||
sql: string;
|
||||
}
|
||||
|
||||
export class MigrationService {
|
||||
private static instance: MigrationService;
|
||||
/**
|
||||
* Migration registry to store and manage database migrations
|
||||
*/
|
||||
class MigrationRegistry {
|
||||
private migrations: Migration[] = [];
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): MigrationService {
|
||||
if (!MigrationService.instance) {
|
||||
MigrationService.instance = new MigrationService();
|
||||
}
|
||||
return MigrationService.instance;
|
||||
}
|
||||
|
||||
registerMigration(migration: Migration) {
|
||||
/**
|
||||
* Register a migration with the registry
|
||||
*
|
||||
* @param migration - The migration to register
|
||||
*/
|
||||
registerMigration(migration: Migration): void {
|
||||
this.migrations.push(migration);
|
||||
logger.info(`[MigrationService] Registered migration: ${migration.name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sqlExec - A function that executes a SQL statement and returns some update result
|
||||
* @param sqlQuery - A function that executes a SQL query and returns the result in some format
|
||||
* @param extractMigrationNames - A function that extracts the names (string array) from a "select name from migrations" query
|
||||
* Get all registered migrations
|
||||
*
|
||||
* @returns Array of registered migrations
|
||||
*/
|
||||
async runMigrations<T>(
|
||||
// note that this does not take parameters because the Capacitor SQLite 'execute' is different
|
||||
sqlExec: (sql: string) => Promise<unknown>,
|
||||
sqlQuery: (sql: string) => Promise<T>,
|
||||
extractMigrationNames: (result: T) => Set<string>,
|
||||
): Promise<void> {
|
||||
// Create migrations table if it doesn't exist
|
||||
await sqlExec(`
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
getMigrations(): Migration[] {
|
||||
return this.migrations;
|
||||
}
|
||||
|
||||
// Get list of executed migrations
|
||||
const result1: T = await sqlQuery("SELECT name FROM migrations;");
|
||||
const executedMigrations = extractMigrationNames(result1);
|
||||
|
||||
// Run pending migrations in order
|
||||
for (const migration of this.migrations) {
|
||||
if (!executedMigrations.has(migration.name)) {
|
||||
await sqlExec(migration.sql);
|
||||
|
||||
await sqlExec(
|
||||
`INSERT INTO migrations (name) VALUES ('${migration.name}')`,
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clear all registered migrations
|
||||
*/
|
||||
clearMigrations(): void {
|
||||
this.migrations = [];
|
||||
logger.info("[MigrationService] Cleared all registered migrations");
|
||||
}
|
||||
}
|
||||
|
||||
export default MigrationService.getInstance();
|
||||
// Create a singleton instance of the migration registry
|
||||
const migrationRegistry = new MigrationRegistry();
|
||||
|
||||
/**
|
||||
* Register a migration with the migration service
|
||||
*
|
||||
* This function is used by the migration system to register database
|
||||
* schema migrations that need to be applied to the database.
|
||||
*
|
||||
* @param migration - The migration to register
|
||||
*/
|
||||
export function registerMigration(migration: Migration): void {
|
||||
migrationRegistry.registerMigration(migration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run all registered migrations against the database
|
||||
*
|
||||
* This function executes all registered migrations in order, checking
|
||||
* which ones have already been applied to avoid duplicate execution.
|
||||
* It creates a migrations table if it doesn't exist to track applied
|
||||
* migrations.
|
||||
*
|
||||
* @param sqlExec - Function to execute SQL statements
|
||||
* @param sqlQuery - Function to query SQL data
|
||||
* @param extractMigrationNames - Function to extract migration names from query results
|
||||
* @returns Promise that resolves when all migrations are complete
|
||||
*/
|
||||
export async function runMigrations<T>(
|
||||
sqlExec: (sql: string, params?: unknown[]) => Promise<unknown>,
|
||||
sqlQuery: (sql: string, params?: unknown[]) => Promise<T>,
|
||||
extractMigrationNames: (result: T) => Set<string>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Create migrations table if it doesn't exist
|
||||
await sqlExec(`
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
name TEXT PRIMARY KEY,
|
||||
applied_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// Get list of already applied migrations
|
||||
const appliedMigrationsResult = await sqlQuery(
|
||||
"SELECT name FROM migrations",
|
||||
);
|
||||
const appliedMigrations = extractMigrationNames(appliedMigrationsResult);
|
||||
|
||||
logger.info(
|
||||
`[MigrationService] Found ${appliedMigrations.size} applied migrations`,
|
||||
);
|
||||
|
||||
// Get all registered migrations
|
||||
const migrations = migrationRegistry.getMigrations();
|
||||
|
||||
if (migrations.length === 0) {
|
||||
logger.warn("[MigrationService] No migrations registered");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[MigrationService] Running ${migrations.length} registered migrations`,
|
||||
);
|
||||
|
||||
// Run each migration that hasn't been applied yet
|
||||
for (const migration of migrations) {
|
||||
if (appliedMigrations.has(migration.name)) {
|
||||
logger.info(
|
||||
`[MigrationService] Skipping already applied migration: ${migration.name}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.info(`[MigrationService] Applying migration: ${migration.name}`);
|
||||
|
||||
try {
|
||||
// Execute the migration SQL
|
||||
await sqlExec(migration.sql);
|
||||
|
||||
// Record that the migration was applied
|
||||
await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
|
||||
migration.name,
|
||||
]);
|
||||
|
||||
logger.info(
|
||||
`[MigrationService] Successfully applied migration: ${migration.name}`,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[MigrationService] Failed to apply migration ${migration.name}:`,
|
||||
error,
|
||||
);
|
||||
throw new Error(`Migration ${migration.name} failed: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("[MigrationService] All migrations completed successfully");
|
||||
} catch (error) {
|
||||
logger.error("[MigrationService] Migration process failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,7 +566,7 @@ export default class ContactImportView extends Vue {
|
||||
this.checkingImports = true;
|
||||
|
||||
try {
|
||||
const jwt: string = getContactJwtFromJwtUrl(jwtInput);
|
||||
const jwt: string = getContactJwtFromJwtUrl(jwtInput) || "";
|
||||
const payload = decodeEndorserJwt(jwt).payload;
|
||||
|
||||
if (Array.isArray(payload.contacts)) {
|
||||
|
||||
1488
src/views/DatabaseMigration.vue
Normal file
1488
src/views/DatabaseMigration.vue
Normal file
File diff suppressed because it is too large
Load Diff
@@ -106,12 +106,12 @@ Raymer * @version 1.0.0 */
|
||||
</button>
|
||||
</div>
|
||||
<UserNameDialog ref="userNameDialog" />
|
||||
<div v-if="PASSKEYS_ENABLED" class="flex justify-end w-full">
|
||||
<div class="flex justify-end w-full">
|
||||
<router-link
|
||||
:to="{ name: 'start' }"
|
||||
class="block text-right text-md font-bold bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mt-2 px-2 py-3 rounded-md"
|
||||
>
|
||||
See all your options first
|
||||
See advanced options
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<div v-if="numAccounts == 1" class="mt-4">
|
||||
<input v-model="shouldErase" type="checkbox" class="mr-2" />
|
||||
<label>Erase the previous identifier.</label>
|
||||
<label>Erase previous identifiers.</label>
|
||||
</div>
|
||||
|
||||
<div v-if="isNotProdServer()" class="mt-4 text-blue-500">
|
||||
@@ -88,17 +88,9 @@ import { Router } from "vue-router";
|
||||
|
||||
import { AppString, NotificationIface, USE_DEXIE_DB } from "../constants/app";
|
||||
import * as databaseUtil from "../db/databaseUtil";
|
||||
import {
|
||||
accountsDBPromise,
|
||||
retrieveSettingsForActiveAccount,
|
||||
} from "../db/index";
|
||||
import {
|
||||
DEFAULT_ROOT_DERIVATION_PATH,
|
||||
deriveAddress,
|
||||
newIdentifier,
|
||||
} from "../libs/crypto";
|
||||
import { retrieveAccountCount, saveNewIdentity } from "../libs/util";
|
||||
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
|
||||
import { retrieveSettingsForActiveAccount } from "../db/index";
|
||||
import { DEFAULT_ROOT_DERIVATION_PATH } from "../libs/crypto";
|
||||
import { retrieveAccountCount, importFromMnemonic } from "../libs/util";
|
||||
import { logger } from "../utils/logger";
|
||||
|
||||
@Component({
|
||||
@@ -115,12 +107,9 @@ export default class ImportAccountView extends Vue {
|
||||
$router!: Router;
|
||||
|
||||
apiServer = "";
|
||||
address = "";
|
||||
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
|
||||
mnemonic = "";
|
||||
numAccounts = 0;
|
||||
privateHex = "";
|
||||
publicHex = "";
|
||||
showAdvanced = false;
|
||||
shouldErase = false;
|
||||
|
||||
@@ -143,33 +132,16 @@ export default class ImportAccountView extends Vue {
|
||||
}
|
||||
|
||||
public async fromMnemonic() {
|
||||
const mne: string = this.mnemonic.trim().toLowerCase();
|
||||
try {
|
||||
[this.address, this.privateHex, this.publicHex] = deriveAddress(
|
||||
mne,
|
||||
await importFromMnemonic(
|
||||
this.mnemonic,
|
||||
this.derivationPath,
|
||||
this.shouldErase,
|
||||
);
|
||||
|
||||
const newId = newIdentifier(
|
||||
this.address,
|
||||
this.publicHex,
|
||||
this.privateHex,
|
||||
this.derivationPath,
|
||||
);
|
||||
|
||||
const accountsDB = await accountsDBPromise;
|
||||
if (this.shouldErase) {
|
||||
const platformService = PlatformServiceFactory.getInstance();
|
||||
await platformService.dbExec("DELETE FROM accounts");
|
||||
if (USE_DEXIE_DB) {
|
||||
await accountsDB.accounts.clear();
|
||||
}
|
||||
}
|
||||
await saveNewIdentity(newId, mne, this.derivationPath);
|
||||
this.$router.push({ name: "account" });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (err: any) {
|
||||
logger.error("Error saving mnemonic & updating settings:", err);
|
||||
logger.error("Error importing from mnemonic:", err);
|
||||
if (err == "Error: invalid mnemonic") {
|
||||
this.$notify(
|
||||
{
|
||||
|
||||
@@ -82,6 +82,18 @@
|
||||
Derive new address from existing seed
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Database Migration Section -->
|
||||
<div class="mt-8 pt-6 border-t border-gray-200">
|
||||
<div class="flex justify-center">
|
||||
<router-link
|
||||
:to="{ name: 'database-migration' }"
|
||||
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||
>
|
||||
Migrate My Old Data
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -182,6 +182,15 @@
|
||||
>
|
||||
Accounts
|
||||
</button>
|
||||
<button
|
||||
class="text-sm text-blue-600 hover:text-blue-800 underline"
|
||||
@click="
|
||||
sqlQuery = 'SELECT * FROM contacts;';
|
||||
executeSql();
|
||||
"
|
||||
>
|
||||
Contacts
|
||||
</button>
|
||||
<button
|
||||
class="text-sm text-blue-600 hover:text-blue-800 underline"
|
||||
@click="
|
||||
|
||||
Reference in New Issue
Block a user