import labels from an export
This commit is contained in:
@@ -14,25 +14,10 @@
|
||||
* - Mixin pattern for easy integration with existing class components
|
||||
* - Enhanced utility methods for common patterns
|
||||
* - Robust error handling and logging
|
||||
* - Ultra-concise database interaction methods
|
||||
* - Smart caching layer with TTL for performance optimization
|
||||
* - Settings shortcuts for ultra-frequent update patterns
|
||||
* - High-level entity operations (insertContact, updateContact, etc.)
|
||||
* - Result mapping helpers to eliminate verbose row processing
|
||||
*
|
||||
* Benefits:
|
||||
* - Eliminates repeated PlatformServiceFactory.getInstance() calls
|
||||
* - Provides consistent error handling across components
|
||||
* - Reduces boilerplate database code by up to 80%
|
||||
* - Maintains type safety with TypeScript
|
||||
* - Includes common database utility patterns
|
||||
* - Enhanced error handling and logging
|
||||
* - Ultra-concise method names for frequent operations
|
||||
* - Automatic caching for settings and contacts (massive performance gain)
|
||||
* - Settings update shortcuts reduce 90% of update boilerplate
|
||||
* - Entity operations eliminate verbose SQL INSERT/UPDATE patterns
|
||||
* - Result mapping helpers reduce row processing boilerplate by 75%
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 4.1.0
|
||||
* @since 2025-07-02
|
||||
@@ -1364,6 +1349,10 @@ export const PlatformServiceMixin = {
|
||||
return this._mapColumnsToValues(columns, values);
|
||||
},
|
||||
|
||||
// =================================================
|
||||
// CONTACT METHODS
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Insert or replace contact - $insertContact()
|
||||
* Eliminates verbose INSERT OR REPLACE patterns
|
||||
@@ -1520,6 +1509,10 @@ export const PlatformServiceMixin = {
|
||||
}
|
||||
},
|
||||
|
||||
// =================================================
|
||||
// CONTACT LABELS METHODS
|
||||
// =================================================
|
||||
|
||||
/**
|
||||
* Get labels for a specific contact - $getContactLabels()
|
||||
* @param did Contact DID
|
||||
@@ -1622,11 +1615,54 @@ export const PlatformServiceMixin = {
|
||||
}
|
||||
},
|
||||
|
||||
async $insertContactLabels(
|
||||
did: string,
|
||||
labels: string[],
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
for (const label of labels) {
|
||||
await this.$dbExec(
|
||||
"INSERT INTO contact_labels (did, label) VALUES (?, ?)",
|
||||
[did, label],
|
||||
);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[PlatformServiceMixin] Error inserting labels for contact ${did}:`,
|
||||
error,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async $updateContactLabels(
|
||||
did: string,
|
||||
labels: string[],
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await this.$dbExec("DELETE FROM contact_labels WHERE did = ?", [did]);
|
||||
for (const label of labels) {
|
||||
await this.$dbExec(
|
||||
"INSERT INTO contact_labels (did, label) VALUES (?, ?)",
|
||||
[did, label],
|
||||
);
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`[PlatformServiceMixin] Error updating labels for contact ${did}:`,
|
||||
error,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all unique labels available - $getUniqueLabels()
|
||||
* Get all unique labels available
|
||||
* @returns Promise<string[]> Array of unique labels
|
||||
*/
|
||||
async $getUniqueLabels(): Promise<string[]> {
|
||||
async $getUniqueContactLabels(): Promise<string[]> {
|
||||
try {
|
||||
const results = (await this.$dbQuery(
|
||||
"SELECT DISTINCT label FROM contact_labels ORDER BY label",
|
||||
@@ -2241,7 +2277,9 @@ export interface IPlatformServiceMixin {
|
||||
$getContactIdsWithAllLabels(labels: string[]): Promise<string[]>;
|
||||
$addContactLabel(did: string, label: string): Promise<boolean>;
|
||||
$deleteContactLabel(did: string, label: string): Promise<boolean>;
|
||||
$getUniqueLabels(): Promise<string[]>;
|
||||
$insertContactLabels(did: string, labels: string[]): Promise<boolean>;
|
||||
$updateContactLabels(did: string, labels: string[]): Promise<boolean>;
|
||||
$getUniqueContactLabels(): Promise<string[]>;
|
||||
$getAllAccounts(): Promise<Account[]>;
|
||||
$getAllAccountDids(): Promise<string[]>;
|
||||
$insertEntity(
|
||||
@@ -2391,7 +2429,9 @@ declare module "@vue/runtime-core" {
|
||||
$getContactIdsWithAllLabels(labels: string[]): Promise<string[]>;
|
||||
$addContactLabel(did: string, label: string): Promise<boolean>;
|
||||
$deleteContactLabel(did: string, label: string): Promise<boolean>;
|
||||
$getUniqueLabels(): Promise<string[]>;
|
||||
$insertContactLabels(did: string, labels: string[]): Promise<boolean>;
|
||||
$updateContactLabels(did: string, labels: string[]): Promise<boolean>;
|
||||
$getUniqueContactLabels(): Promise<string[]>;
|
||||
$getAllAccounts(): Promise<Account[]>;
|
||||
$getAllAccountDids(): Promise<string[]>;
|
||||
$insertEntity(
|
||||
|
||||
@@ -346,7 +346,7 @@ export default class ContactEditView extends Vue {
|
||||
this.originalLabels = [...labels];
|
||||
|
||||
// Load all labels for suggestions
|
||||
this.allUniqueLabels = await this.$getUniqueLabels();
|
||||
this.allUniqueLabels = await this.$getUniqueContactLabels();
|
||||
} else {
|
||||
this.notify.error(
|
||||
`${NOTIFY_CONTACT_NOT_FOUND.message} ${contactDid}`,
|
||||
|
||||
@@ -87,8 +87,14 @@
|
||||
<div class="border font-bold p-1">
|
||||
{{ capitalizeAndInsertSpacesBeforeCaps(contactField) }}
|
||||
</div>
|
||||
<div class="border p-1">{{ value.old }}</div>
|
||||
<div class="border p-1">{{ value.new }}</div>
|
||||
<div v-if="contactField === 'labels'" class="border p-1">
|
||||
{{ value.old.join(", ") }}
|
||||
</div>
|
||||
<div v-else class="border p-1">{{ value.old }}</div>
|
||||
<div v-if="contactField === 'labels'" class="border p-1">
|
||||
{{ value.new.join(", ") }}
|
||||
</div>
|
||||
<div v-else class="border p-1">{{ value.new }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,26 +182,6 @@
|
||||
* - Field-by-field comparison for existing contacts
|
||||
* - Batch visibility settings
|
||||
* - Auto-import for single new contacts
|
||||
* - Error handling and validation
|
||||
*
|
||||
* State Management:
|
||||
* - Tracks existing contacts
|
||||
* - Maintains selection state for bulk imports
|
||||
* - Records differences for duplicate contacts
|
||||
* - Manages visibility settings
|
||||
*
|
||||
* Security Considerations:
|
||||
* - JWT validation for imported contacts
|
||||
* - Visibility control per contact
|
||||
* - Error handling for malformed data
|
||||
*
|
||||
* @example
|
||||
* // Component usage in router
|
||||
* {
|
||||
* path: "/contact-import/:jwt?",
|
||||
* name: "contact-import",
|
||||
* component: ContactImportView
|
||||
* }
|
||||
*
|
||||
* @see {@link Contact} for contact data structure
|
||||
* @see {@link setVisibilityUtil} for visibility management
|
||||
@@ -209,7 +195,11 @@ import QuickNav from "../components/QuickNav.vue";
|
||||
import EntityIcon from "../components/EntityIcon.vue";
|
||||
import OfferDialog from "../components/OfferDialog.vue";
|
||||
import { APP_SERVER, AppString, NotificationIface } from "../constants/app";
|
||||
import { Contact, ContactMethod } from "../db/tables/contacts";
|
||||
import {
|
||||
Contact,
|
||||
ContactWithLabels,
|
||||
ContactMethod,
|
||||
} from "../db/tables/contacts";
|
||||
import * as libsUtil from "../libs/util";
|
||||
import {
|
||||
capitalizeAndInsertSpacesBeforeCaps,
|
||||
@@ -220,6 +210,25 @@ import { getContactJwtFromJwtUrl } from "../libs/crypto";
|
||||
import { decodeEndorserJwt } from "../libs/crypto/vc";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
|
||||
import { ContactLabel } from "@/db/tables/contactLabels";
|
||||
|
||||
type ContactDifferences = Record<
|
||||
string,
|
||||
{
|
||||
new:
|
||||
| string
|
||||
| boolean
|
||||
| Array<ContactMethod>
|
||||
| Array<ContactLabel>
|
||||
| undefined;
|
||||
old:
|
||||
| string
|
||||
| boolean
|
||||
| Array<ContactMethod>
|
||||
| Array<ContactLabel>
|
||||
| undefined;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Contact Import View Component
|
||||
@@ -287,22 +296,13 @@ export default class ContactImportView extends Vue {
|
||||
/** API server URL for backend communication */
|
||||
apiServer = "";
|
||||
/** Map of existing contacts keyed by DID for duplicate detection */
|
||||
contactsExisting: Record<string, Contact> = {};
|
||||
contactsExisting: Record<string, ContactWithLabels> = {};
|
||||
/** Array of contacts being imported from JWT */
|
||||
contactsImporting: Array<Contact> = [];
|
||||
contactsImporting: Array<ContactWithLabels> = [];
|
||||
/** Selection state for each importing contact */
|
||||
contactsSelected: Array<boolean> = [];
|
||||
/** Differences between existing and importing contacts */
|
||||
contactDifferences: Record<
|
||||
string,
|
||||
Record<
|
||||
string,
|
||||
{
|
||||
new: string | boolean | Array<ContactMethod> | undefined;
|
||||
old: string | boolean | Array<ContactMethod> | undefined;
|
||||
}
|
||||
>
|
||||
> = {};
|
||||
/** Each contact's differences between existing and importing info */
|
||||
contactDifferences: Record<string, ContactDifferences> = {};
|
||||
/** Loading state for import operations */
|
||||
checkingImports = false;
|
||||
/** JWT input for manual contact import */
|
||||
@@ -412,13 +412,17 @@ export default class ContactImportView extends Vue {
|
||||
* Processes contacts for import and checks for duplicates
|
||||
* @param contacts Array of contacts to process
|
||||
*/
|
||||
async setContactsSelected(contacts: Array<Contact>) {
|
||||
async setContactsSelected(contacts: Array<ContactWithLabels>) {
|
||||
this.contactsImporting = contacts;
|
||||
this.contactsSelected = new Array(this.contactsImporting.length).fill(true);
|
||||
this.contactsSelected = new Array(this.contactsImporting.length).fill(
|
||||
false,
|
||||
);
|
||||
|
||||
// Get all existing contacts for comparison
|
||||
const baseContacts = await this.$getAllContacts();
|
||||
|
||||
// get the labels for each contact
|
||||
|
||||
// Check for existing contacts and differences
|
||||
for (let i = 0; i < this.contactsImporting.length; i++) {
|
||||
const contactIn = this.contactsImporting[i];
|
||||
@@ -426,25 +430,24 @@ export default class ContactImportView extends Vue {
|
||||
(contact) => contact.did === contactIn.did,
|
||||
);
|
||||
if (existingContact) {
|
||||
this.contactsExisting[contactIn.did] = existingContact;
|
||||
const labels = await this.$getContactLabelsForDid(existingContact.did);
|
||||
this.contactsExisting[contactIn.did] = {
|
||||
...existingContact,
|
||||
labels: labels || [],
|
||||
};
|
||||
const existingFullContact = this.contactsExisting[contactIn.did];
|
||||
|
||||
// Compare contact fields for differences
|
||||
const differences: Record<
|
||||
string,
|
||||
{
|
||||
new: string | boolean | Array<ContactMethod> | undefined;
|
||||
old: string | boolean | Array<ContactMethod> | undefined;
|
||||
}
|
||||
> = {};
|
||||
const differences: ContactDifferences = {};
|
||||
Object.keys(contactIn).forEach((key) => {
|
||||
if (
|
||||
!R.equals(
|
||||
contactIn[key as keyof Contact],
|
||||
existingContact[key as keyof Contact],
|
||||
existingFullContact[key as keyof Contact],
|
||||
)
|
||||
) {
|
||||
differences[key] = {
|
||||
old: existingContact[key as keyof Contact],
|
||||
old: existingFullContact[key as keyof Contact],
|
||||
new: contactIn[key as keyof Contact],
|
||||
};
|
||||
}
|
||||
@@ -452,10 +455,13 @@ export default class ContactImportView extends Vue {
|
||||
this.contactDifferences[contactIn.did] = differences;
|
||||
if (R.isEmpty(differences)) {
|
||||
this.sameCount++;
|
||||
} else {
|
||||
// auto-select contacts with differences
|
||||
this.contactsSelected[i] = true;
|
||||
}
|
||||
|
||||
// Don't auto-select duplicates
|
||||
this.contactsSelected[i] = false;
|
||||
} else {
|
||||
// auto-select new contacts
|
||||
this.contactsSelected[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,16 +523,25 @@ export default class ContactImportView extends Vue {
|
||||
// Process selected contacts
|
||||
for (let i = 0; i < this.contactsImporting.length; i++) {
|
||||
if (this.contactsSelected[i]) {
|
||||
const contact = this.contactsImporting[i];
|
||||
const contactWithLabels = this.contactsImporting[i];
|
||||
const contact = {
|
||||
...contactWithLabels,
|
||||
labels: undefined,
|
||||
};
|
||||
const contactLabels = contactWithLabels.labels || [];
|
||||
const existingContact = this.contactsExisting[contact.did];
|
||||
|
||||
if (existingContact) {
|
||||
// Update existing contact
|
||||
await this.$updateContact(contact.did, contact);
|
||||
// update the labels for the contact
|
||||
await this.$updateContactLabels(contact.did, contactLabels);
|
||||
updatedCount++;
|
||||
} else {
|
||||
// Add new contact
|
||||
await this.$insertContact(contact);
|
||||
// add the labels for the contact
|
||||
await this.$insertContactLabels(contact.did, contactLabels);
|
||||
importedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +387,7 @@ export default class ContactsView extends Vue {
|
||||
|
||||
this.contacts = await this.$getAllContacts();
|
||||
this.contactsFiltered = await this.filteredContacts();
|
||||
this.allLabels = await this.$getUniqueLabels();
|
||||
this.allLabels = await this.$getUniqueContactLabels();
|
||||
}
|
||||
|
||||
private async processContactJwt() {
|
||||
|
||||
Reference in New Issue
Block a user