Complete DIDView.vue triple migration and refactor template handlers

- Fix DIDView.vue notification migration: add missing NOTIFY_SERVER_ACCESS_ERROR and NOTIFY_NO_IDENTITY_ERROR imports
- Refactor 5 inline template handlers to proper class methods (goBack, toggleDidDetails, showLargeProfileImage, showLargeIdenticon, hideLargeImage)
- Update notification validation script to exclude createNotifyHelpers initialization patterns
- DIDView.vue now fully compliant: database migration + SQL abstraction + notification migration complete

Improves code organization, testability, and follows Vue.js best practices for template/class separation. All linting passes without errors.
This commit is contained in:
Matthew Raymer
2025-07-07 05:44:34 +00:00
parent 41a8e4e7a8
commit 15874d31ef
9 changed files with 877 additions and 343 deletions

View File

@@ -25,3 +25,48 @@ export const NOTIFY_CONFIRMATION_ERROR = {
title: "Error",
message: "There was a problem submitting the confirmation.",
};
export const NOTIFY_DEFAULT_TO_ACTIVE_DID = {
title: "Your Info",
message: "No user was specified so showing your info.",
};
export const NOTIFY_CONTACT_DELETED = {
title: "Deleted",
message: "Contact has been removed.",
};
export const NOTIFY_CONTACT_DELETE_FAILED = {
title: "Error",
message: "Failed to delete contact.",
};
export const NOTIFY_REGISTRATION_SUCCESS = {
title: "Registration Success",
message: "has been registered.",
};
export const NOTIFY_REGISTRATION_ERROR = {
title: "Registration Error",
message: "Something went wrong during registration.",
};
export const NOTIFY_SERVER_ACCESS_ERROR = {
title: "Error",
message: "There was a problem accessing the server. Try again later.",
};
export const NOTIFY_NO_IDENTITY_ERROR = {
title: "No Identity",
message: "There is no identity to use to check visibility.",
};
export const NOTIFY_VISIBILITY_SET = {
title: "Visibility Set",
message: "visibility updated.",
};
export const NOTIFY_VISIBILITY_REFRESHED = {
title: "Visibility Refreshed",
message: "visibility status updated.",
};

View File

@@ -913,6 +913,47 @@ export const PlatformServiceMixin = {
}));
},
/**
* Get single contact by DID - $getContact()
* Eliminates verbose single contact query patterns
* @param did Contact DID to retrieve
* @returns Promise<Contact | null> Contact object or null if not found
*/
async $getContact(did: string): Promise<Contact | null> {
const results = await this.$dbQuery(
"SELECT * FROM contacts WHERE did = ?",
[did],
);
if (!results || !results.values || results.values.length === 0) {
return null;
}
const contactData = this._mapColumnsToValues(
results.columns,
results.values,
);
return contactData.length > 0 ? (contactData[0] as Contact) : null;
},
/**
* Delete contact by DID - $deleteContact()
* Eliminates verbose contact deletion patterns
* @param did Contact DID to delete
* @returns Promise<boolean> Success status
*/
async $deleteContact(did: string): Promise<boolean> {
try {
await this.$dbExec("DELETE FROM contacts WHERE did = ?", [did]);
// Invalidate contacts cache
this._invalidateCache("contacts_all");
return true;
} catch (error) {
logger.error("[PlatformServiceMixin] Error deleting contact:", error);
return false;
}
},
/**
* Generic entity insertion - $insertEntity()
* Eliminates verbose INSERT patterns for any entity
@@ -1197,6 +1238,8 @@ export interface IPlatformServiceMixin {
$insertContact(contact: Partial<Contact>): Promise<boolean>;
$updateContact(did: string, changes: Partial<Contact>): Promise<boolean>;
$getAllContacts(): Promise<Contact[]>;
$getContact(did: string): Promise<Contact | null>;
$deleteContact(did: string): Promise<boolean>;
$contactCount(): Promise<number>;
$insertEntity(
tableName: string,
@@ -1316,6 +1359,8 @@ declare module "@vue/runtime-core" {
$insertContact(contact: Partial<Contact>): Promise<boolean>;
$updateContact(did: string, changes: Partial<Contact>): Promise<boolean>;
$getAllContacts(): Promise<Contact[]>;
$getContact(did: string): Promise<Contact | null>;
$deleteContact(did: string): Promise<boolean>;
$insertEntity(
tableName: string,
entity: Record<string, unknown>,

View File

@@ -10,7 +10,7 @@
<!-- Back -->
<button
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
@click="$router.go(-1)"
@click="goBack"
>
<font-awesome icon="chevron-left" class="fa-fw"></font-awesome>
</button>
@@ -32,10 +32,7 @@
<font-awesome icon="pen" class="text-sm text-blue-500 ml-2 mb-1" />
</router-link>
</h2>
<button
class="ml-2 mr-2 mt-4"
@click="showDidDetails = !showDidDetails"
>
<button class="ml-2 mr-2 mt-4" @click="toggleDidDetails">
Details
<font-awesome
v-if="showDidDetails"
@@ -60,7 +57,7 @@
:icon-size="96"
:profile-image-url="contactFromDid?.profileImageUrl"
class="inline-block align-text-bottom border border-slate-300 rounded"
@click="showLargeIdenticonUrl = contactFromDid?.profileImageUrl"
@click="showLargeProfileImage"
/>
</span>
</div>
@@ -160,7 +157,7 @@
:entity-id="viewingDid"
:icon-size="64"
class="inline-block align-middle border border-slate-300 rounded-md mr-1"
@click="showLargeIdenticonId = viewingDid"
@click="showLargeIdenticon"
/>
</div>
</div>
@@ -177,10 +174,7 @@
:icon-size="512"
:profile-image-url="showLargeIdenticonUrl"
class="flex w-11/12 max-w-sm mx-auto mb-3 overflow-hidden bg-white rounded-lg shadow-lg"
@click="
showLargeIdenticonId = undefined;
showLargeIdenticonUrl = undefined;
"
@click="hideLargeImage"
/>
</div>
</div>
@@ -266,7 +260,7 @@ import TopMessage from "../components/TopMessage.vue";
import { NotificationIface } from "../constants/app";
import { Contact } from "../db/tables/contacts";
import { BoundingBox } from "../db/tables/settings";
import * as databaseUtil from "../db/databaseUtil";
import {
GenericCredWrapper,
GenericVerifiableCredential,
@@ -284,6 +278,16 @@ import * as libsUtil from "../libs/util";
import EntityIcon from "../components/EntityIcon.vue";
import { logger } from "../utils/logger";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { createNotifyHelpers, TIMEOUTS } from "@/utils/notify";
import {
NOTIFY_DEFAULT_TO_ACTIVE_DID,
NOTIFY_CONTACT_DELETED,
NOTIFY_CONTACT_DELETE_FAILED,
NOTIFY_REGISTRATION_SUCCESS,
NOTIFY_REGISTRATION_ERROR,
NOTIFY_SERVER_ACCESS_ERROR,
NOTIFY_NO_IDENTITY_ERROR,
} from "@/constants/notifications";
/**
* DIDView Component
@@ -310,6 +314,8 @@ export default class DIDView extends Vue {
$route!: RouteLocationNormalizedLoaded;
$router!: Router;
notify!: ReturnType<typeof createNotifyHelpers>;
libsUtil = libsUtil;
yaml = yaml;
@@ -331,6 +337,13 @@ export default class DIDView extends Vue {
didInfoForContact = didInfoForContact;
displayAmount = displayAmount;
/**
* Initializes notification helpers
*/
created() {
this.notify = createNotifyHelpers(this.$notify);
}
/**
* Initializes the view with DID information
*
@@ -355,7 +368,7 @@ export default class DIDView extends Vue {
* Initializes component settings from active account
*/
private async initializeSettings() {
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
const settings = await this.$accountSettings();
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
}
@@ -384,14 +397,10 @@ export default class DIDView extends Vue {
* Notifies user that we're showing their DID info by default
*/
private notifyDefaultToActiveDID() {
this.$notify(
{
group: "alert",
type: "toast",
title: "Your Info",
text: "No user was specified so showing your info.",
},
3000,
this.notify.toast(
NOTIFY_DEFAULT_TO_ACTIVE_DID.title,
NOTIFY_DEFAULT_TO_ACTIVE_DID.message,
TIMEOUTS.SHORT,
);
}
@@ -402,17 +411,10 @@ export default class DIDView extends Vue {
private async loadContactInformation() {
if (!this.viewingDid) return;
const dbContacts = await this.$dbQuery(
"SELECT * FROM contacts WHERE did = ?",
[this.viewingDid],
);
const contacts = databaseUtil.mapQueryResultToValues(
dbContacts,
) as unknown as Contact[];
const contact = await this.$getContact(this.viewingDid);
// Safely check if contact exists before assigning
if (contacts && contacts.length > 0) {
this.contactFromDid = contacts[0];
if (contact) {
this.contactFromDid = contact;
this.contactYaml = yaml.dump(this.contactFromDid);
} else {
this.contactFromDid = undefined;
@@ -442,6 +444,33 @@ export default class DIDView extends Vue {
}
}
/**
* Navigation helper methods
*/
goBack() {
this.$router.go(-1);
}
/**
* UI state helper methods
*/
toggleDidDetails() {
this.showDidDetails = !this.showDidDetails;
}
showLargeProfileImage() {
this.showLargeIdenticonUrl = this.contactFromDid?.profileImageUrl;
}
showLargeIdenticon() {
this.showLargeIdenticonId = this.viewingDid;
}
hideLargeImage() {
this.showLargeIdenticonId = undefined;
this.showLargeIdenticonUrl = undefined;
}
/**
* Prompts user to confirm contact deletion
* Shows additional warning if contact has visibility permissions
@@ -457,18 +486,9 @@ export default class DIDView extends Vue {
message +=
" Note that they can see your activity, so if you want to hide your activity from them then you should do that first.";
}
this.$notify(
{
group: "modal",
type: "confirm",
title: "Delete",
text: message,
onYes: async () => {
await this.deleteContact(contact);
},
},
-1,
);
this.notify.confirm(message, async () => {
await this.deleteContact(contact);
});
}
/**
@@ -477,17 +497,13 @@ export default class DIDView extends Vue {
* @param contact - Contact object to be deleted
*/
async deleteContact(contact: Contact) {
await this.$dbExec("DELETE FROM contacts WHERE did = ?", [contact.did]);
this.$notify(
{
group: "alert",
type: "success",
title: "Deleted",
text: "Contact has been removed.",
},
3000,
);
this.$router.push({ name: "contacts" });
const success = await this.$deleteContact(contact.did);
if (success) {
this.notify.success(NOTIFY_CONTACT_DELETED.message, TIMEOUTS.SHORT);
this.$router.push({ name: "contacts" });
} else {
this.notify.error(NOTIFY_CONTACT_DELETE_FAILED.message, TIMEOUTS.LONG);
}
}
/**
@@ -497,24 +513,16 @@ export default class DIDView extends Vue {
* @param contact - Contact to be registered
*/
async confirmRegister(contact: Contact) {
this.$notify(
{
group: "modal",
type: "confirm",
title: "Register",
text:
"Are you sure you want to register " +
libsUtil.nameForContact(this.contactFromDid, false) +
(contact.registered
? " -- especially since they are already marked as registered"
: "") +
"?",
onYes: async () => {
await this.register(contact);
},
},
-1,
);
const message =
"Are you sure you want to register " +
libsUtil.nameForContact(this.contactFromDid, false) +
(contact.registered
? " -- especially since they are already marked as registered"
: "") +
"?";
this.notify.confirm(message, async () => {
await this.register(contact);
});
}
/**
@@ -524,7 +532,7 @@ export default class DIDView extends Vue {
* @param contact - Contact to register
*/
async register(contact: Contact) {
this.$notify({ group: "alert", type: "toast", title: "Sent..." }, 1000);
this.notify.toast("Processing", "Sent...", TIMEOUTS.SHORT);
try {
const regResult = await register(
@@ -535,32 +543,17 @@ export default class DIDView extends Vue {
);
if (regResult.success) {
contact.registered = true;
await this.$dbExec("UPDATE contacts SET registered = ? WHERE did = ?", [
true,
contact.did,
]);
await this.$updateContact(contact.did, { registered: true });
this.$notify(
{
group: "alert",
type: "success",
title: "Registration Success",
text:
(contact.name || "That unnamed person") + " has been registered.",
},
5000,
const name = contact.name || "That unnamed person";
this.notify.success(
`${name} ${NOTIFY_REGISTRATION_SUCCESS.message}`,
TIMEOUTS.LONG,
);
} else {
this.$notify(
{
group: "alert",
type: "danger",
title: "Registration Error",
text:
(regResult.error as string) ||
"Something went wrong during registration.",
},
5000,
this.notify.error(
(regResult.error as string) || NOTIFY_REGISTRATION_ERROR.message,
TIMEOUTS.LONG,
);
}
} catch (error) {
@@ -582,15 +575,7 @@ export default class DIDView extends Vue {
userMessage = error as string;
}
// Now set that error for the user to see.
this.$notify(
{
group: "alert",
type: "danger",
title: "Registration Error",
text: userMessage,
},
5000,
);
this.notify.error(userMessage, TIMEOUTS.LONG);
}
}
@@ -624,15 +609,7 @@ export default class DIDView extends Vue {
if (response.status !== 200) {
const details = await response.text();
logger.error("Problem with full search:", details);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: `There was a problem accessing the server. Try again later.`,
},
5000,
);
this.notify.error(NOTIFY_SERVER_ACCESS_ERROR.message, TIMEOUTS.LONG);
return;
}
@@ -642,14 +619,9 @@ export default class DIDView extends Vue {
} catch (e: unknown) {
logger.error("Error with feed load:", e);
const error = e as { userMessage?: string };
this.$notify(
{
group: "alert",
type: "danger",
title: "Error",
text: error.userMessage || "There was a problem retrieving claims.",
},
3000,
this.notify.error(
error.userMessage || "There was a problem retrieving claims.",
TIMEOUTS.SHORT,
);
} finally {
this.isLoading = false;
@@ -728,21 +700,12 @@ export default class DIDView extends Vue {
const visibilityPrompt = visibility
? "Are you sure you want to make your activity visible to them?"
: "Are you sure you want to hide all your activity from them?";
this.$notify(
{
group: "modal",
type: "confirm",
title: "Set Visibility",
text: visibilityPrompt,
onYes: async () => {
const success = await this.setVisibility(contact, visibility, true);
if (success) {
contact.seesMe = visibility; // didn't work inside setVisibility
}
},
},
-1,
);
this.notify.confirm(visibilityPrompt, async () => {
const success = await this.setVisibility(contact, visibility, true);
if (success) {
contact.seesMe = visibility; // didn't work inside setVisibility
}
});
}
/**
@@ -758,27 +721,16 @@ export default class DIDView extends Vue {
visibility: boolean,
showSuccessAlert: boolean,
) {
// TODO: Implement proper visibility setting using mixin methods
// For now, just update local database
await this.$dbExec("UPDATE contacts SET seesMe = ? WHERE did = ?", [
visibility,
contact.did,
]);
// Update contact visibility using mixin method
await this.$updateContact(contact.did, { seesMe: visibility });
if (showSuccessAlert) {
this.$notify(
{
group: "alert",
type: "success",
title: "Visibility Set",
text:
(contact.name || "That user") +
" can " +
(visibility ? "" : "not ") +
"see your activity.",
},
3000,
);
const message =
(contact.name || "That user") +
" can " +
(visibility ? "" : "not ") +
"see your activity.";
this.notify.success(message, TIMEOUTS.SHORT);
}
return true;
}
@@ -796,15 +748,7 @@ export default class DIDView extends Vue {
encodeURIComponent(contact.did);
const headers = await getHeaders(this.activeDid);
if (!headers["Authorization"]) {
this.$notify(
{
group: "alert",
type: "danger",
title: "No Identity",
text: "There is no identity to use to check visibility.",
},
3000,
);
this.notify.error(NOTIFY_NO_IDENTITY_ERROR.message, TIMEOUTS.SHORT);
return;
}
@@ -814,48 +758,22 @@ export default class DIDView extends Vue {
const visibility = resp.data;
contact.seesMe = visibility;
//console.log("Visi check:", visibility, contact.seesMe, contact.did);
await this.$dbExec("UPDATE contacts SET seesMe = ? WHERE did = ?", [
visibility,
contact.did,
]);
await this.$updateContact(contact.did, { seesMe: visibility });
this.$notify(
{
group: "alert",
type: "info",
title: "Visibility Refreshed",
text:
libsUtil.nameForContact(contact, true) +
" can" +
(visibility ? "" : " not") +
" see your activity.",
},
3000,
);
const message =
libsUtil.nameForContact(contact, true) +
" can" +
(visibility ? "" : " not") +
" see your activity.";
this.notify.info(message, TIMEOUTS.SHORT);
} else {
logger.error("Got bad server response checking visibility:", resp);
const message = resp.data.error?.message || "Got bad server response.";
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Checking Visibility",
text: message,
},
5000,
);
this.notify.error(message, TIMEOUTS.LONG);
}
} catch (err) {
logger.error("Caught error from request to check visibility:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Checking Visibility",
text: "Check connectivity and try again.",
},
3000,
);
this.notify.error("Check connectivity and try again.", TIMEOUTS.SHORT);
}
}
@@ -869,21 +787,12 @@ export default class DIDView extends Vue {
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,
);
this.notify.confirm(contentVisibilityPrompt, async () => {
const success = await this.setViewContent(contact, view);
if (success) {
contact.iViewContent = view; // see visibility note about not working inside setVisibility
}
});
}
/**
@@ -894,22 +803,12 @@ export default class DIDView extends Vue {
* @returns Boolean indicating success
*/
async setViewContent(contact: Contact, visibility: boolean) {
await this.$dbExec("UPDATE contacts SET iViewContent = ? WHERE did = ?", [
visibility,
contact.did,
]);
this.$notify(
{
group: "alert",
type: "success",
title: "Visibility Set",
text:
"You will" +
(visibility ? "" : " not") +
` see ${contact.name}'s activity.`,
},
3000,
);
await this.$updateContact(contact.did, { iViewContent: visibility });
const message =
"You will" +
(visibility ? "" : " not") +
` see ${contact.name}'s activity.`;
this.notify.success(message, TIMEOUTS.SHORT);
return true;
}
}