allow blocking another person's content from this user (with iViewContent contact field)
This commit is contained in:
@@ -34,7 +34,6 @@ const secretBase64 = arrayBufferToBase64(randomBytes);
|
|||||||
const MIGRATIONS = [
|
const MIGRATIONS = [
|
||||||
{
|
{
|
||||||
name: "001_initial",
|
name: "001_initial",
|
||||||
// see ../db/tables files for explanations of the fields
|
|
||||||
sql: `
|
sql: `
|
||||||
CREATE TABLE IF NOT EXISTS accounts (
|
CREATE TABLE IF NOT EXISTS accounts (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -119,6 +118,12 @@ const MIGRATIONS = [
|
|||||||
);
|
);
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "002_add_iViewContent_to_contacts",
|
||||||
|
sql: `
|
||||||
|
ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE;
|
||||||
|
`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
export interface ContactMethod {
|
export type ContactMethod = {
|
||||||
label: string;
|
label: string;
|
||||||
type: string; // eg. "EMAIL", "SMS", "WHATSAPP", maybe someday "GOOGLE-CONTACT-API"
|
type: string; // eg. "EMAIL", "SMS", "WHATSAPP", maybe someday "GOOGLE-CONTACT-API"
|
||||||
value: string;
|
value: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface Contact {
|
export type Contact = {
|
||||||
//
|
//
|
||||||
// When adding a property, consider whether it should be added when exporting & sharing contacts.
|
// When adding a property, consider whether it should be added when exporting & sharing contacts, eg. DataExportSection
|
||||||
|
|
||||||
did: string;
|
did: string;
|
||||||
contactMethods?: Array<ContactMethod>;
|
contactMethods?: Array<ContactMethod>;
|
||||||
|
iViewContent?: boolean;
|
||||||
name?: string;
|
name?: string;
|
||||||
nextPubKeyHashB64?: string; // base64-encoded SHA256 hash of next public key
|
nextPubKeyHashB64?: string; // base64-encoded SHA256 hash of next public key
|
||||||
notes?: string;
|
notes?: string;
|
||||||
@@ -17,9 +18,15 @@ export interface Contact {
|
|||||||
publicKeyBase64?: string;
|
publicKeyBase64?: string;
|
||||||
seesMe?: boolean; // cached value of the server setting
|
seesMe?: boolean; // cached value of the server setting
|
||||||
registered?: boolean; // cached value of the server setting
|
registered?: boolean; // cached value of the server setting
|
||||||
}
|
};
|
||||||
|
|
||||||
export type ContactWithJsonStrings = Contact & {
|
/**
|
||||||
|
* This is for those cases (eg. with a DB) where every field is a primitive (and not an object).
|
||||||
|
*
|
||||||
|
* This is so that we can reuse most of the type and don't have to maintain another copy.
|
||||||
|
* Another approach uses typescript conditionals: https://chatgpt.com/share/6855cdc3-ab5c-8007-8525-726612016eb2
|
||||||
|
*/
|
||||||
|
export type ContactWithJsonStrings = Omit<Contact, "contactMethods"> & {
|
||||||
contactMethods?: string;
|
contactMethods?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import {
|
|||||||
faArrowLeft,
|
faArrowLeft,
|
||||||
faArrowRight,
|
faArrowRight,
|
||||||
faArrowRotateBackward,
|
faArrowRotateBackward,
|
||||||
faArrowUpRightFromSquare,
|
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
faBan,
|
faBan,
|
||||||
faBitcoinSign,
|
faBitcoinSign,
|
||||||
faBurst,
|
faBurst,
|
||||||
@@ -92,8 +92,8 @@ library.add(
|
|||||||
faArrowLeft,
|
faArrowLeft,
|
||||||
faArrowRight,
|
faArrowRight,
|
||||||
faArrowRotateBackward,
|
faArrowRotateBackward,
|
||||||
faArrowUpRightFromSquare,
|
|
||||||
faArrowUp,
|
faArrowUp,
|
||||||
|
faArrowUpRightFromSquare,
|
||||||
faBan,
|
faBan,
|
||||||
faBitcoinSign,
|
faBitcoinSign,
|
||||||
faBurst,
|
faBurst,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
updateDefaultSettings,
|
updateDefaultSettings,
|
||||||
} from "../db/index";
|
} from "../db/index";
|
||||||
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
||||||
import { Contact } from "../db/tables/contacts";
|
import { Contact, ContactWithJsonStrings } from "../db/tables/contacts";
|
||||||
import * as databaseUtil from "../db/databaseUtil";
|
import * as databaseUtil from "../db/databaseUtil";
|
||||||
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "../db/tables/settings";
|
import { DEFAULT_PASSKEY_EXPIRATION_MINUTES } from "../db/tables/settings";
|
||||||
import {
|
import {
|
||||||
@@ -974,19 +974,16 @@ export interface DatabaseExport {
|
|||||||
*/
|
*/
|
||||||
export const contactsToExportJson = (contacts: Contact[]): DatabaseExport => {
|
export const contactsToExportJson = (contacts: Contact[]): DatabaseExport => {
|
||||||
// Convert each contact to a plain object and ensure all fields are included
|
// Convert each contact to a plain object and ensure all fields are included
|
||||||
const rows = contacts.map((contact) => ({
|
const rows = contacts.map((contact) => {
|
||||||
did: contact.did,
|
const exContact: ContactWithJsonStrings = R.omit(
|
||||||
name: contact.name || null,
|
["contactMethods"],
|
||||||
contactMethods: contact.contactMethods
|
contact,
|
||||||
? JSON.stringify(parseJsonField(contact.contactMethods, []))
|
);
|
||||||
: null,
|
exContact.contactMethods = contact.contactMethods
|
||||||
nextPubKeyHashB64: contact.nextPubKeyHashB64 || null,
|
? JSON.stringify(contact.contactMethods, [])
|
||||||
notes: contact.notes || null,
|
: undefined;
|
||||||
profileImageUrl: contact.profileImageUrl || null,
|
return exContact;
|
||||||
publicKeyBase64: contact.publicKeyBase64 || null,
|
});
|
||||||
seesMe: contact.seesMe || false,
|
|
||||||
registered: contact.registered || false,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import {
|
|||||||
generateUpdateStatement,
|
generateUpdateStatement,
|
||||||
generateInsertStatement,
|
generateInsertStatement,
|
||||||
} from "../db/databaseUtil";
|
} from "../db/databaseUtil";
|
||||||
import { updateDefaultSettings } from "../db/databaseUtil";
|
|
||||||
import { importFromMnemonic } from "../libs/util";
|
import { importFromMnemonic } from "../libs/util";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,11 +155,14 @@ export async function getDexieContacts(): Promise<Contact[]> {
|
|||||||
await db.open();
|
await db.open();
|
||||||
const contacts = await db.contacts.toArray();
|
const contacts = await db.contacts.toArray();
|
||||||
logger.info(
|
logger.info(
|
||||||
`[MigrationService] Retrieved ${contacts.length} contacts from Dexie`,
|
`[IndexedDBMigrationService] Retrieved ${contacts.length} contacts from Dexie`,
|
||||||
);
|
);
|
||||||
return contacts;
|
return contacts;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[MigrationService] Error retrieving Dexie contacts:", error);
|
logger.error(
|
||||||
|
"[IndexedDBMigrationService] Error retrieving Dexie contacts:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw new Error(`Failed to retrieve Dexie contacts: ${error}`);
|
throw new Error(`Failed to retrieve Dexie contacts: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,11 +216,14 @@ export async function getSqliteContacts(): Promise<Contact[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`[MigrationService] Retrieved ${contacts.length} contacts from SQLite`,
|
`[IndexedDBMigrationService] Retrieved ${contacts.length} contacts from SQLite`,
|
||||||
);
|
);
|
||||||
return contacts;
|
return contacts;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[MigrationService] Error retrieving SQLite contacts:", error);
|
logger.error(
|
||||||
|
"[IndexedDBMigrationService] Error retrieving SQLite contacts:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw new Error(`Failed to retrieve SQLite contacts: ${error}`);
|
throw new Error(`Failed to retrieve SQLite contacts: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,11 +256,14 @@ export async function getDexieSettings(): Promise<Settings[]> {
|
|||||||
await db.open();
|
await db.open();
|
||||||
const settings = await db.settings.toArray();
|
const settings = await db.settings.toArray();
|
||||||
logger.info(
|
logger.info(
|
||||||
`[MigrationService] Retrieved ${settings.length} settings from Dexie`,
|
`[IndexedDBMigrationService] Retrieved ${settings.length} settings from Dexie`,
|
||||||
);
|
);
|
||||||
return settings;
|
return settings;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[MigrationService] Error retrieving Dexie settings:", error);
|
logger.error(
|
||||||
|
"[IndexedDBMigrationService] Error retrieving Dexie settings:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw new Error(`Failed to retrieve Dexie settings: ${error}`);
|
throw new Error(`Failed to retrieve Dexie settings: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,11 +317,14 @@ export async function getSqliteSettings(): Promise<Settings[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`[MigrationService] Retrieved ${settings.length} settings from SQLite`,
|
`[IndexedDBMigrationService] Retrieved ${settings.length} settings from SQLite`,
|
||||||
);
|
);
|
||||||
return settings;
|
return settings;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[MigrationService] Error retrieving SQLite settings:", error);
|
logger.error(
|
||||||
|
"[IndexedDBMigrationService] Error retrieving SQLite settings:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw new Error(`Failed to retrieve SQLite settings: ${error}`);
|
throw new Error(`Failed to retrieve SQLite settings: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,11 +364,14 @@ export async function getSqliteAccounts(): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`[MigrationService] Retrieved ${dids.length} accounts from SQLite`,
|
`[IndexedDBMigrationService] Retrieved ${dids.length} accounts from SQLite`,
|
||||||
);
|
);
|
||||||
return dids;
|
return dids;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[MigrationService] Error retrieving SQLite accounts:", error);
|
logger.error(
|
||||||
|
"[IndexedDBMigrationService] Error retrieving SQLite accounts:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw new Error(`Failed to retrieve SQLite accounts: ${error}`);
|
throw new Error(`Failed to retrieve SQLite accounts: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,11 +405,14 @@ export async function getDexieAccounts(): Promise<Account[]> {
|
|||||||
await accountsDB.open();
|
await accountsDB.open();
|
||||||
const accounts = await accountsDB.accounts.toArray();
|
const accounts = await accountsDB.accounts.toArray();
|
||||||
logger.info(
|
logger.info(
|
||||||
`[MigrationService] Retrieved ${accounts.length} accounts from Dexie`,
|
`[IndexedDBMigrationService] Retrieved ${accounts.length} accounts from Dexie`,
|
||||||
);
|
);
|
||||||
return accounts;
|
return accounts;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[MigrationService] Error retrieving Dexie accounts:", error);
|
logger.error(
|
||||||
|
"[IndexedDBMigrationService] Error retrieving Dexie accounts:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw new Error(`Failed to retrieve Dexie accounts: ${error}`);
|
throw new Error(`Failed to retrieve Dexie accounts: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -429,7 +446,7 @@ export async function getDexieAccounts(): Promise<Account[]> {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export async function compareDatabases(): Promise<DataComparison> {
|
export async function compareDatabases(): Promise<DataComparison> {
|
||||||
logger.info("[MigrationService] Starting database comparison");
|
logger.info("[IndexedDBMigrationService] Starting database comparison");
|
||||||
|
|
||||||
const [
|
const [
|
||||||
dexieContacts,
|
dexieContacts,
|
||||||
@@ -470,7 +487,7 @@ export async function compareDatabases(): Promise<DataComparison> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.info("[MigrationService] Database comparison completed", {
|
logger.info("[IndexedDBMigrationService] Database comparison completed", {
|
||||||
dexieContacts: dexieContacts.length,
|
dexieContacts: dexieContacts.length,
|
||||||
sqliteContacts: sqliteContacts.length,
|
sqliteContacts: sqliteContacts.length,
|
||||||
dexieSettings: dexieSettings.length,
|
dexieSettings: dexieSettings.length,
|
||||||
@@ -679,6 +696,7 @@ function compareAccounts(dexieAccounts: Account[], sqliteDids: string[]) {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
function contactsEqual(contact1: Contact, contact2: Contact): boolean {
|
function contactsEqual(contact1: Contact, contact2: Contact): boolean {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const ifEmpty = (arg: any, def: any) => (arg ? arg : def);
|
const ifEmpty = (arg: any, def: any) => (arg ? arg : def);
|
||||||
const contact1Methods =
|
const contact1Methods =
|
||||||
contact1.contactMethods &&
|
contact1.contactMethods &&
|
||||||
@@ -954,7 +972,7 @@ export function generateComparisonYaml(comparison: DataComparison): string {
|
|||||||
export async function migrateContacts(
|
export async function migrateContacts(
|
||||||
overwriteExisting: boolean = false,
|
overwriteExisting: boolean = false,
|
||||||
): Promise<MigrationResult> {
|
): Promise<MigrationResult> {
|
||||||
logger.info("[MigrationService] Starting contact migration", {
|
logger.info("[IndexedDBMigrationService] Starting contact migration", {
|
||||||
overwriteExisting,
|
overwriteExisting,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -990,7 +1008,7 @@ export async function migrateContacts(
|
|||||||
);
|
);
|
||||||
await platformService.dbExec(sql, params);
|
await platformService.dbExec(sql, params);
|
||||||
result.contactsMigrated++;
|
result.contactsMigrated++;
|
||||||
logger.info(`[MigrationService] Updated contact: ${contact.did}`);
|
logger.info(`[IndexedDBMigrationService] Updated contact: ${contact.did}`);
|
||||||
} else {
|
} else {
|
||||||
result.warnings.push(
|
result.warnings.push(
|
||||||
`Contact ${contact.did} already exists, skipping`,
|
`Contact ${contact.did} already exists, skipping`,
|
||||||
@@ -1004,17 +1022,17 @@ export async function migrateContacts(
|
|||||||
);
|
);
|
||||||
await platformService.dbExec(sql, params);
|
await platformService.dbExec(sql, params);
|
||||||
result.contactsMigrated++;
|
result.contactsMigrated++;
|
||||||
logger.info(`[MigrationService] Added contact: ${contact.did}`);
|
logger.info(`[IndexedDBMigrationService] Added contact: ${contact.did}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = `Failed to migrate contact ${contact.did}: ${error}`;
|
const errorMsg = `Failed to migrate contact ${contact.did}: ${error}`;
|
||||||
logger.error("[MigrationService]", errorMsg);
|
logger.error("[IndexedDBMigrationService]", errorMsg);
|
||||||
result.errors.push(errorMsg);
|
result.errors.push(errorMsg);
|
||||||
result.success = false;
|
result.success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("[MigrationService] Contact migration completed", {
|
logger.info("[IndexedDBMigrationService] Contact migration completed", {
|
||||||
contactsMigrated: result.contactsMigrated,
|
contactsMigrated: result.contactsMigrated,
|
||||||
errors: result.errors.length,
|
errors: result.errors.length,
|
||||||
warnings: result.warnings.length,
|
warnings: result.warnings.length,
|
||||||
@@ -1023,7 +1041,7 @@ export async function migrateContacts(
|
|||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = `Contact migration failed: ${error}`;
|
const errorMsg = `Contact migration failed: ${error}`;
|
||||||
logger.error("[MigrationService]", errorMsg);
|
logger.error("[IndexedDBMigrationService]", errorMsg);
|
||||||
result.errors.push(errorMsg);
|
result.errors.push(errorMsg);
|
||||||
result.success = false;
|
result.success = false;
|
||||||
return result;
|
return result;
|
||||||
@@ -1063,7 +1081,7 @@ export async function migrateContacts(
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export async function migrateSettings(): Promise<MigrationResult> {
|
export async function migrateSettings(): Promise<MigrationResult> {
|
||||||
logger.info("[MigrationService] Starting settings migration");
|
logger.info("[IndexedDBMigrationService] Starting settings migration");
|
||||||
|
|
||||||
const result: MigrationResult = {
|
const result: MigrationResult = {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -1076,17 +1094,17 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const dexieSettings = await getDexieSettings();
|
const dexieSettings = await getDexieSettings();
|
||||||
logger.info("[MigrationService] Migrating settings", {
|
logger.info("[IndexedDBMigrationService] Migrating settings", {
|
||||||
dexieSettings: dexieSettings.length,
|
dexieSettings: dexieSettings.length,
|
||||||
});
|
});
|
||||||
const platformService = PlatformServiceFactory.getInstance();
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
|
||||||
// Create an array of promises for all settings migrations
|
// Create an array of promises for all settings migrations
|
||||||
const migrationPromises = dexieSettings.map(async (setting) => {
|
const migrationPromises = dexieSettings.map(async (setting) => {
|
||||||
logger.info("[MigrationService] Starting to migrate settings", setting);
|
logger.info(
|
||||||
let sqliteSettingRaw:
|
"[IndexedDBMigrationService] Starting to migrate settings",
|
||||||
| { columns: string[]; values: unknown[][] }
|
setting,
|
||||||
| undefined;
|
);
|
||||||
|
|
||||||
// adjust SQL based on the accountDid key, maybe null
|
// adjust SQL based on the accountDid key, maybe null
|
||||||
let conditional: string;
|
let conditional: string;
|
||||||
@@ -1098,15 +1116,18 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
|||||||
conditional = "accountDid = ?";
|
conditional = "accountDid = ?";
|
||||||
preparams = [setting.accountDid];
|
preparams = [setting.accountDid];
|
||||||
}
|
}
|
||||||
sqliteSettingRaw = await platformService.dbQuery(
|
const sqliteSettingRaw = await platformService.dbQuery(
|
||||||
"SELECT * FROM settings WHERE " + conditional,
|
"SELECT * FROM settings WHERE " + conditional,
|
||||||
preparams,
|
preparams,
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info("[MigrationService] Migrating one set of settings:", {
|
logger.info(
|
||||||
setting,
|
"[IndexedDBMigrationService] Migrating one set of settings:",
|
||||||
sqliteSettingRaw,
|
{
|
||||||
});
|
setting,
|
||||||
|
sqliteSettingRaw,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (sqliteSettingRaw?.values?.length) {
|
if (sqliteSettingRaw?.values?.length) {
|
||||||
// should cover the master settings, where accountDid is null
|
// should cover the master settings, where accountDid is null
|
||||||
delete setting.id; // don't conflict with the id in the sqlite database
|
delete setting.id; // don't conflict with the id in the sqlite database
|
||||||
@@ -1117,7 +1138,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
|||||||
conditional,
|
conditional,
|
||||||
preparams,
|
preparams,
|
||||||
);
|
);
|
||||||
logger.info("[MigrationService] Updating settings", {
|
logger.info("[IndexedDBMigrationService] Updating settings", {
|
||||||
sql,
|
sql,
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
@@ -1127,10 +1148,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
|||||||
// insert new setting
|
// insert new setting
|
||||||
delete setting.id; // don't conflict with the id in the sqlite database
|
delete setting.id; // don't conflict with the id in the sqlite database
|
||||||
delete setting.activeDid; // ensure we don't set the activeDid (since master settings are an update and don't hit this case)
|
delete setting.activeDid; // ensure we don't set the activeDid (since master settings are an update and don't hit this case)
|
||||||
const { sql, params } = generateInsertStatement(
|
const { sql, params } = generateInsertStatement(setting, "settings");
|
||||||
setting,
|
|
||||||
"settings",
|
|
||||||
);
|
|
||||||
await platformService.dbExec(sql, params);
|
await platformService.dbExec(sql, params);
|
||||||
result.settingsMigrated++;
|
result.settingsMigrated++;
|
||||||
}
|
}
|
||||||
@@ -1140,7 +1158,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
|||||||
const updatedSettings = await Promise.all(migrationPromises);
|
const updatedSettings = await Promise.all(migrationPromises);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"[MigrationService] Finished migrating settings",
|
"[IndexedDBMigrationService] Finished migrating settings",
|
||||||
updatedSettings,
|
updatedSettings,
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
@@ -1148,7 +1166,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
|||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"[MigrationService] Complete settings migration failed:",
|
"[IndexedDBMigrationService] Complete settings migration failed:",
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
const errorMessage = `Settings migration failed: ${error}`;
|
const errorMessage = `Settings migration failed: ${error}`;
|
||||||
@@ -1192,7 +1210,7 @@ export async function migrateSettings(): Promise<MigrationResult> {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export async function migrateAccounts(): Promise<MigrationResult> {
|
export async function migrateAccounts(): Promise<MigrationResult> {
|
||||||
logger.info("[MigrationService] Starting account migration");
|
logger.info("[IndexedDBMigrationService] Starting account migration");
|
||||||
|
|
||||||
const result: MigrationResult = {
|
const result: MigrationResult = {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -1248,14 +1266,17 @@ export async function migrateAccounts(): Promise<MigrationResult> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("[MigrationService] Successfully migrated account", {
|
logger.info(
|
||||||
did,
|
"[IndexedDBMigrationService] Successfully migrated account",
|
||||||
dateCreated: account.dateCreated,
|
{
|
||||||
});
|
did,
|
||||||
|
dateCreated: account.dateCreated,
|
||||||
|
},
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = `Failed to migrate account ${did}: ${error}`;
|
const errorMessage = `Failed to migrate account ${did}: ${error}`;
|
||||||
result.errors.push(errorMessage);
|
result.errors.push(errorMessage);
|
||||||
logger.error("[MigrationService] Account migration failed:", {
|
logger.error("[IndexedDBMigrationService] Account migration failed:", {
|
||||||
error,
|
error,
|
||||||
did,
|
did,
|
||||||
});
|
});
|
||||||
@@ -1272,7 +1293,7 @@ export async function migrateAccounts(): Promise<MigrationResult> {
|
|||||||
result.errors.push(errorMessage);
|
result.errors.push(errorMessage);
|
||||||
result.success = false;
|
result.success = false;
|
||||||
logger.error(
|
logger.error(
|
||||||
"[MigrationService] Complete account migration failed:",
|
"[IndexedDBMigrationService] Complete account migration failed:",
|
||||||
error,
|
error,
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
@@ -1306,11 +1327,11 @@ export async function migrateAll(): Promise<MigrationResult> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info(
|
logger.info(
|
||||||
"[MigrationService] Starting complete migration from Dexie to SQLite",
|
"[IndexedDBMigrationService] Starting complete migration from Dexie to SQLite",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Step 1: Migrate Accounts (foundational)
|
// Step 1: Migrate Accounts (foundational)
|
||||||
logger.info("[MigrationService] Step 1: Migrating accounts...");
|
logger.info("[IndexedDBMigrationService] Step 1: Migrating accounts...");
|
||||||
const accountsResult = await migrateAccounts();
|
const accountsResult = await migrateAccounts();
|
||||||
if (!accountsResult.success) {
|
if (!accountsResult.success) {
|
||||||
result.errors.push(
|
result.errors.push(
|
||||||
@@ -1322,7 +1343,7 @@ export async function migrateAll(): Promise<MigrationResult> {
|
|||||||
result.warnings.push(...accountsResult.warnings);
|
result.warnings.push(...accountsResult.warnings);
|
||||||
|
|
||||||
// Step 2: Migrate Settings (depends on accounts)
|
// Step 2: Migrate Settings (depends on accounts)
|
||||||
logger.info("[MigrationService] Step 2: Migrating settings...");
|
logger.info("[IndexedDBMigrationService] Step 2: Migrating settings...");
|
||||||
const settingsResult = await migrateSettings();
|
const settingsResult = await migrateSettings();
|
||||||
if (!settingsResult.success) {
|
if (!settingsResult.success) {
|
||||||
result.errors.push(
|
result.errors.push(
|
||||||
@@ -1335,7 +1356,7 @@ export async function migrateAll(): Promise<MigrationResult> {
|
|||||||
|
|
||||||
// Step 4: Migrate Contacts (independent, but after accounts for consistency)
|
// Step 4: Migrate Contacts (independent, but after accounts for consistency)
|
||||||
// ... but which is better done through the contact import view
|
// ... but which is better done through the contact import view
|
||||||
// logger.info("[MigrationService] Step 4: Migrating contacts...");
|
// logger.info("[IndexedDBMigrationService] Step 4: Migrating contacts...");
|
||||||
// const contactsResult = await migrateContacts();
|
// const contactsResult = await migrateContacts();
|
||||||
// if (!contactsResult.success) {
|
// if (!contactsResult.success) {
|
||||||
// result.errors.push(
|
// result.errors.push(
|
||||||
@@ -1354,7 +1375,7 @@ export async function migrateAll(): Promise<MigrationResult> {
|
|||||||
result.contactsMigrated;
|
result.contactsMigrated;
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`[MigrationService] Complete migration successful: ${totalMigrated} total records migrated`,
|
`[IndexedDBMigrationService] Complete migration successful: ${totalMigrated} total records migrated`,
|
||||||
{
|
{
|
||||||
accounts: result.accountsMigrated,
|
accounts: result.accountsMigrated,
|
||||||
settings: result.settingsMigrated,
|
settings: result.settingsMigrated,
|
||||||
@@ -1367,7 +1388,10 @@ export async function migrateAll(): Promise<MigrationResult> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = `Complete migration failed: ${error}`;
|
const errorMessage = `Complete migration failed: ${error}`;
|
||||||
result.errors.push(errorMessage);
|
result.errors.push(errorMessage);
|
||||||
logger.error("[MigrationService] Complete migration failed:", error);
|
logger.error(
|
||||||
|
"[IndexedDBMigrationService] Complete migration failed:",
|
||||||
|
error,
|
||||||
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,8 +349,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
|
<div v-if="includeUserProfileLocation" class="mb-4 aspect-video">
|
||||||
<p class="text-sm mb-2 text-slate-500">
|
<p class="text-sm mb-2 text-slate-500">
|
||||||
The location you choose will be shared with the world until you remove this checkbox.
|
The location you choose will be shared with the world until you remove
|
||||||
For your security, choose a location nearby but not exactly at your true location, like at your town center.
|
this checkbox. For your security, choose a location nearby but not
|
||||||
|
exactly at your true location, like at your town center.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<l-map
|
<l-map
|
||||||
|
|||||||
@@ -77,6 +77,7 @@
|
|||||||
@click="confirmSetVisibility(contactFromDid, false)"
|
@click="confirmSetVisibility(contactFromDid, false)"
|
||||||
>
|
>
|
||||||
<font-awesome icon="eye" class="fa-fw" />
|
<font-awesome icon="eye" class="fa-fw" />
|
||||||
|
<font-awesome icon="arrow-up" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else-if="
|
v-else-if="
|
||||||
@@ -87,6 +88,32 @@
|
|||||||
@click="confirmSetVisibility(contactFromDid, true)"
|
@click="confirmSetVisibility(contactFromDid, true)"
|
||||||
>
|
>
|
||||||
<font-awesome icon="eye-slash" class="fa-fw" />
|
<font-awesome icon="eye-slash" class="fa-fw" />
|
||||||
|
<font-awesome icon="arrow-up" class="fa-fw" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-if="
|
||||||
|
contactFromDid?.iViewContent &&
|
||||||
|
contactFromDid.did !== activeDid
|
||||||
|
"
|
||||||
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
|
title="I view their content"
|
||||||
|
@click="confirmViewContent(contactFromDid, false)"
|
||||||
|
>
|
||||||
|
<font-awesome icon="eye" class="fa-fw" />
|
||||||
|
<font-awesome icon="arrow-down" class="fa-fw" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else-if="
|
||||||
|
!contactFromDid?.iViewContent &&
|
||||||
|
contactFromDid?.did !== activeDid
|
||||||
|
"
|
||||||
|
class="text-sm uppercase bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white mx-0.5 my-0.5 px-2 py-1.5 rounded-md"
|
||||||
|
title="I do not view their content"
|
||||||
|
@click="confirmViewContent(contactFromDid, true)"
|
||||||
|
>
|
||||||
|
<font-awesome icon="eye-slash" class="fa-fw" />
|
||||||
|
<font-awesome icon="arrow-down" class="fa-fw" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -825,9 +852,9 @@ export default class DIDView extends Vue {
|
|||||||
title: "Visibility Refreshed",
|
title: "Visibility Refreshed",
|
||||||
text:
|
text:
|
||||||
libsUtil.nameForContact(contact, true) +
|
libsUtil.nameForContact(contact, true) +
|
||||||
" can " +
|
" can" +
|
||||||
(visibility ? "" : "not ") +
|
(visibility ? "" : " not") +
|
||||||
"see your activity.",
|
" see your activity.",
|
||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
);
|
);
|
||||||
@@ -857,6 +884,64 @@ export default class DIDView extends Vue {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm whether the user want to see/hide the other's content, then execute it
|
||||||
|
*
|
||||||
|
* @param contact Contact content to show/hide from user
|
||||||
|
* @param view whether user wants to view this contact
|
||||||
|
*/
|
||||||
|
async confirmViewContent(contact: Contact, view: boolean) {
|
||||||
|
const contentVisibilityPrompt = view
|
||||||
|
? "Are you sure you want to see their content?"
|
||||||
|
: "Are you sure you want to hide their content from you?";
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "modal",
|
||||||
|
type: "confirm",
|
||||||
|
title: "Set Content Visibility",
|
||||||
|
text: contentVisibilityPrompt,
|
||||||
|
onYes: async () => {
|
||||||
|
const success = await this.setViewContent(contact, view);
|
||||||
|
if (success) {
|
||||||
|
contact.iViewContent = view; // see visibility note about not working inside setVisibility
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates contact content visibility for this device
|
||||||
|
*
|
||||||
|
* @param contact - Contact to update content visibility for
|
||||||
|
* @param visibility - New content visibility state
|
||||||
|
* @returns Boolean indicating success
|
||||||
|
*/
|
||||||
|
async setViewContent(contact: Contact, visibility: boolean) {
|
||||||
|
const platformService = PlatformServiceFactory.getInstance();
|
||||||
|
await platformService.dbExec(
|
||||||
|
"UPDATE contacts SET iViewContent = ? WHERE did = ?",
|
||||||
|
[visibility, contact.did],
|
||||||
|
);
|
||||||
|
if (USE_DEXIE_DB) {
|
||||||
|
db.contacts.update(contact.did, { iViewContent: visibility });
|
||||||
|
}
|
||||||
|
this.$notify(
|
||||||
|
{
|
||||||
|
group: "alert",
|
||||||
|
type: "success",
|
||||||
|
title: "Visibility Set",
|
||||||
|
text:
|
||||||
|
"You will" +
|
||||||
|
(visibility ? "" : " not") +
|
||||||
|
` see ${contact.name}'s activity.`,
|
||||||
|
},
|
||||||
|
3000,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1122,6 +1122,7 @@ export default class DatabaseMigration extends Vue {
|
|||||||
private loadingMessage = "";
|
private loadingMessage = "";
|
||||||
private error = "";
|
private error = "";
|
||||||
private warning = "";
|
private warning = "";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private exportedData: Record<string, any> | null = null;
|
private exportedData: Record<string, any> | null = null;
|
||||||
private successMessage = "";
|
private successMessage = "";
|
||||||
|
|
||||||
@@ -1134,6 +1135,7 @@ export default class DatabaseMigration extends Vue {
|
|||||||
* @param {any} setting - The setting object
|
* @param {any} setting - The setting object
|
||||||
* @returns {string} The display name for the setting
|
* @returns {string} The display name for the setting
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
getSettingDisplayName(setting: any): string {
|
getSettingDisplayName(setting: any): string {
|
||||||
// Handle exported JSON format (has 'type' and 'did' fields)
|
// Handle exported JSON format (has 'type' and 'did' fields)
|
||||||
if (setting.type && setting.did) {
|
if (setting.type && setting.did) {
|
||||||
@@ -1153,6 +1155,7 @@ export default class DatabaseMigration extends Vue {
|
|||||||
* @param {any} account - The account object
|
* @param {any} account - The account object
|
||||||
* @returns {boolean} True if account has identity
|
* @returns {boolean} True if account has identity
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
getAccountHasIdentity(account: any): boolean {
|
getAccountHasIdentity(account: any): boolean {
|
||||||
// Handle exported JSON format (has 'hasIdentity' field)
|
// Handle exported JSON format (has 'hasIdentity' field)
|
||||||
if (account.hasIdentity !== undefined) {
|
if (account.hasIdentity !== undefined) {
|
||||||
@@ -1170,6 +1173,7 @@ export default class DatabaseMigration extends Vue {
|
|||||||
* @param {any} account - The account object
|
* @param {any} account - The account object
|
||||||
* @returns {boolean} True if account has mnemonic
|
* @returns {boolean} True if account has mnemonic
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
getAccountHasMnemonic(account: any): boolean {
|
getAccountHasMnemonic(account: any): boolean {
|
||||||
// Handle exported JSON format (has 'hasMnemonic' field)
|
// Handle exported JSON format (has 'hasMnemonic' field)
|
||||||
if (account.hasMnemonic !== undefined) {
|
if (account.hasMnemonic !== undefined) {
|
||||||
|
|||||||
@@ -448,6 +448,7 @@ export default class HomeView extends Vue {
|
|||||||
allContacts: Array<Contact> = [];
|
allContacts: Array<Contact> = [];
|
||||||
allMyDids: Array<string> = [];
|
allMyDids: Array<string> = [];
|
||||||
apiServer = "";
|
apiServer = "";
|
||||||
|
blockedContactDids: Array<string> = [];
|
||||||
feedData: GiveRecordWithContactInfo[] = [];
|
feedData: GiveRecordWithContactInfo[] = [];
|
||||||
feedPreviousOldestId?: string;
|
feedPreviousOldestId?: string;
|
||||||
feedLastViewedClaimId?: string;
|
feedLastViewedClaimId?: string;
|
||||||
@@ -567,22 +568,14 @@ export default class HomeView extends Vue {
|
|||||||
|
|
||||||
// Load contacts with graceful fallback
|
// Load contacts with graceful fallback
|
||||||
try {
|
try {
|
||||||
const platformService = PlatformServiceFactory.getInstance();
|
this.loadContacts();
|
||||||
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();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logConsoleAndDb(
|
logConsoleAndDb(
|
||||||
`[HomeView] Failed to retrieve contacts: ${error}`,
|
`[HomeView] Failed to retrieve contacts: ${error}`,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
this.allContacts = []; // Ensure we have a valid empty array
|
this.allContacts = []; // Ensure we have a valid empty array
|
||||||
|
this.blockedContactDids = [];
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
@@ -746,6 +739,9 @@ export default class HomeView extends Vue {
|
|||||||
if (USE_DEXIE_DB) {
|
if (USE_DEXIE_DB) {
|
||||||
this.allContacts = await db.contacts.toArray();
|
this.allContacts = await db.contacts.toArray();
|
||||||
}
|
}
|
||||||
|
this.blockedContactDids = this.allContacts
|
||||||
|
.filter((c) => !c.iViewContent)
|
||||||
|
.map((c) => c.did);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1013,6 +1009,7 @@ export default class HomeView extends Vue {
|
|||||||
);
|
);
|
||||||
if (results.data.length > 0) {
|
if (results.data.length > 0) {
|
||||||
endOfResults = false;
|
endOfResults = false;
|
||||||
|
// gather any contacts that user has blocked from view
|
||||||
await this.processFeedResults(results.data);
|
await this.processFeedResults(results.data);
|
||||||
await this.updateFeedLastViewedId(results.data);
|
await this.updateFeedLastViewedId(results.data);
|
||||||
}
|
}
|
||||||
@@ -1200,7 +1197,7 @@ export default class HomeView extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if record should be included based on filters
|
* Checks if record should be included based on filters & preferences
|
||||||
*
|
*
|
||||||
* @internal
|
* @internal
|
||||||
* @callGraph
|
* @callGraph
|
||||||
@@ -1226,6 +1223,10 @@ export default class HomeView extends Vue {
|
|||||||
record: GiveSummaryRecord,
|
record: GiveSummaryRecord,
|
||||||
fulfillsPlan?: FulfillsPlan,
|
fulfillsPlan?: FulfillsPlan,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
if (this.blockedContactDids.includes(record.issuerDid)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isAnyFeedFilterOn) {
|
if (!this.isAnyFeedFilterOn) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user