Merge branch 'sql-absurd-sql-back'

This commit is contained in:
2025-06-07 17:18:10 -06:00
147 changed files with 10865 additions and 3762 deletions

View File

@@ -76,7 +76,8 @@
Set Your Name
</button>
<p class="text-xs text-slate-500 mt-1">
(Don't worry: this is not visible to anyone until you share it with them. It's not sent to any servers.)
(Don't worry: this is not visible to anyone until you share it with
them. It's not sent to any servers.)
</p>
<UserNameDialog ref="userNameDialog" />
</span>
@@ -110,7 +111,10 @@
<font-awesome icon="camera" class="fa-fw" />
</div>
</template>
<!-- If not registered, they don't need to see this at all. We show a prompt to register below. -->
<!--
If not registered, they don't need to see this at all. We show a prompt
to register below.
-->
</div>
<ImageMethodDialog
ref="imageMethodDialog"
@@ -427,12 +431,16 @@
<div class="mb-4 text-center">
{{ limitsMessage }}
</div>
<div>
<div v-if="endorserLimits">
<p class="text-sm">
You have done
<b>{{ endorserLimits?.doneClaimsThisWeek || "?" }} claims</b> out of
<b>{{ endorserLimits?.maxClaimsPerWeek || "?" }}</b> for this week.
Your claims counter resets at
<b
>{{ endorserLimits?.doneClaimsThisWeek || "?" }} claim{{
endorserLimits?.doneClaimsThisWeek === 1 ? "" : "s"
}}</b
>
out of <b>{{ endorserLimits?.maxClaimsPerWeek || "?" }}</b> for this
week. Your claims counter resets at
<b class="whitespace-nowrap">{{
readableDate(endorserLimits?.nextWeekBeginDateTime)
}}</b>
@@ -443,7 +451,9 @@
>{{
endorserLimits?.doneRegistrationsThisMonth || "?"
}}
registrations</b
registration{{
endorserLimits?.doneRegistrationsThisMonth === 1 ? "" : "s"
}}</b
>
out of
<b>{{ endorserLimits?.maxRegistrationsPerMonth || "?" }}</b> for this
@@ -456,9 +466,13 @@
</p>
<p class="mt-3 text-sm">
You have uploaded
<b>{{ imageLimits?.doneImagesThisWeek || "?" }} images</b> out of
<b>{{ imageLimits?.maxImagesPerWeek || "?" }}</b> for this week. Your
image counter resets at
<b
>{{ imageLimits?.doneImagesThisWeek || "?" }} image{{
imageLimits?.doneImagesThisWeek === 1 ? "" : "s"
}}</b
>
out of <b>{{ imageLimits?.maxImagesPerWeek || "?" }}</b> for this
week. Your image counter resets at
<b class="whitespace-nowrap">{{
readableDate(imageLimits?.nextWeekBeginDateTime)
}}</b>
@@ -495,7 +509,7 @@
<!-- Deep Identity Details -->
<span class="text-slate-500 text-sm font-bold mb-2">
Deep Identifier Details
Identifier Details
</span>
<div
id="sectionDeepIdentifier"
@@ -578,20 +592,9 @@
Switch Identifier
</router-link>
<div class="flex mt-4">
<button>
<router-link
:to="{ name: 'statistics' }"
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 mb-2"
>
See Global Animated History of Giving
</router-link>
</button>
</div>
<div id="sectionImportContactsSettings" class="mt-4">
<h2 class="text-slate-500 text-sm font-bold">
Import Contacts & Settings Database
Import Contacts
</h2>
<div class="ml-4 mt-2">
@@ -622,9 +625,7 @@
class="block text-center text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-6"
@click="checkContactImports()"
>
Import Only Contacts
<br />
after comparing
Import Contacts
</button>
</div>
</div>
@@ -902,9 +903,9 @@
</div>
</label>
<div id="sectionPasskeyExpiration" class="flex justify-between">
<div id="sectionPasskeyExpiration" class="flex mt-4 justify-between">
<span>
<span class="text-slate-500 text-sm font-bold mb-2">
<span class="text-slate-500 text-sm font-bold">
Passkey Expiration Minutes
</span>
<br />
@@ -950,10 +951,27 @@
<router-link
:to="{ name: 'logs' }"
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 mb-2"
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 mt-2"
>
View Logs
Logs
</router-link>
<router-link
:to="{ name: 'test' }"
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 mt-2"
>
Test Page
</router-link>
<div class="flex mt-2">
<button>
<router-link
:to="{ name: 'statistics' }"
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"
>
See Global Animated History of Giving
</router-link>
</button>
</div>
</section>
</main>
</template>
@@ -965,7 +983,7 @@ import { AxiosError } from "axios";
import { Buffer } from "buffer/";
import Dexie from "dexie";
import "dexie-export-import";
// @ts-ignore - they aren't exporting it but it's there
// @ts-expect-error - they aren't exporting it but it's there
import { ImportProgress } from "dexie-export-import";
import { LeafletMouseEvent } from "leaflet";
import * as R from "ramda";
@@ -990,6 +1008,7 @@ import {
DEFAULT_PUSH_SERVER,
IMAGE_TYPE_PROFILE,
NotificationIface,
USE_DEXIE_DB,
} from "../constants/app";
import {
db,
@@ -1003,6 +1022,7 @@ import {
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
MASTER_SETTINGS_KEY,
} from "../db/tables/settings";
import * as databaseUtil from "../db/databaseUtil";
import {
clearPasskeyToken,
EndorserRateLimits,
@@ -1021,6 +1041,7 @@ import {
} from "../libs/util";
import { UserProfile } from "@/libs/partnerServer";
import { logger } from "../utils/logger";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
const inputImportFileNameRef = ref<Blob>();
@@ -1136,9 +1157,14 @@ export default class AccountViewView extends Vue {
if (error.status === 404) {
// this is ok: the profile is not yet created
} else {
logConsoleAndDb(
databaseUtil.logConsoleAndDb(
"Error loading profile: " + errorStringForLog(error),
);
if (USE_DEXIE_DB) {
logConsoleAndDb(
"Error loading profile: " + errorStringForLog(error),
);
}
this.$notify(
{
group: "alert",
@@ -1187,7 +1213,6 @@ export default class AccountViewView extends Vue {
this.turnOffNotifyingFlags();
}
}
// console.log("Got to the end of 'mounted' call in AccountViewView.");
/**
* Beware! I've seen where we never get to this point because "ready" never resolves.
*/
@@ -1215,8 +1240,11 @@ export default class AccountViewView extends Vue {
* Initializes component state with values from the database or defaults.
*/
async initializeState() {
await db.open();
const settings = await retrieveSettingsForActiveAccount();
let settings = await databaseUtil.retrieveSettingsForActiveAccount();
if (USE_DEXIE_DB) {
await db.open();
settings = await retrieveSettingsForActiveAccount();
}
this.activeDid = settings.activeDid || "";
this.apiServer = settings.apiServer || "";
@@ -1259,42 +1287,67 @@ export default class AccountViewView extends Vue {
async toggleShowContactAmounts() {
this.showContactGives = !this.showContactGives;
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
showContactGivesInline: this.showContactGives,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showContactGivesInline: this.showContactGives,
});
}
}
async toggleShowGeneralAdvanced() {
this.showGeneralAdvanced = !this.showGeneralAdvanced;
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
showGeneralAdvanced: this.showGeneralAdvanced,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showGeneralAdvanced: this.showGeneralAdvanced,
});
}
}
async toggleProdWarning() {
this.warnIfProdServer = !this.warnIfProdServer;
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
warnIfProdServer: this.warnIfProdServer,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
warnIfProdServer: this.warnIfProdServer,
});
}
}
async toggleTestWarning() {
this.warnIfTestServer = !this.warnIfTestServer;
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
warnIfTestServer: this.warnIfTestServer,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
warnIfTestServer: this.warnIfTestServer,
});
}
}
async toggleShowShortcutBvc() {
this.showShortcutBvc = !this.showShortcutBvc;
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
showShortcutBvc: this.showShortcutBvc,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showShortcutBvc: this.showShortcutBvc,
});
}
}
readableDate(timeStr: string) {
@@ -1305,9 +1358,18 @@ export default class AccountViewView extends Vue {
* Processes the identity and updates the component's state.
*/
async processIdentity() {
const account: Account | undefined = await retrieveAccountMetadata(
this.activeDid,
let account: Account | undefined = undefined;
const platformService = PlatformServiceFactory.getInstance();
const dbAccount = await platformService.dbQuery(
"SELECT * FROM accounts WHERE did = ?",
[this.activeDid],
);
if (dbAccount) {
account = databaseUtil.mapQueryResultToValues(dbAccount)[0] as Account;
}
if (USE_DEXIE_DB) {
account = await retrieveAccountMetadata(this.activeDid);
}
if (account?.identity) {
const identity = JSON.parse(account.identity as string) as IIdentifier;
this.publicHex = identity.keys[0].publicKeyHex;
@@ -1350,9 +1412,14 @@ export default class AccountViewView extends Vue {
this.$refs.pushNotificationPermission as PushNotificationPermission
).open(DAILY_CHECK_TITLE, async (success: boolean, timeText: string) => {
if (success) {
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
notifyingNewActivityTime: timeText,
});
if (USE_DEXIE_DB) {
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingNewActivityTime: timeText,
});
}
this.notifyingNewActivity = true;
this.notifyingNewActivityTime = timeText;
}
@@ -1366,9 +1433,14 @@ export default class AccountViewView extends Vue {
text: "", // unused, only here to satisfy type check
callback: async (success) => {
if (success) {
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
notifyingNewActivityTime: "",
});
if (USE_DEXIE_DB) {
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingNewActivityTime: "",
});
}
this.notifyingNewActivity = false;
this.notifyingNewActivityTime = "";
}
@@ -1410,10 +1482,16 @@ export default class AccountViewView extends Vue {
DIRECT_PUSH_TITLE,
async (success: boolean, timeText: string, message?: string) => {
if (success) {
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
notifyingReminderMessage: message,
notifyingReminderTime: timeText,
});
if (USE_DEXIE_DB) {
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingReminderMessage: message,
notifyingReminderTime: timeText,
});
}
this.notifyingReminder = true;
this.notifyingReminderMessage = message || "";
this.notifyingReminderTime = timeText;
@@ -1429,10 +1507,16 @@ export default class AccountViewView extends Vue {
text: "", // unused, only here to satisfy type check
callback: async (success) => {
if (success) {
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
notifyingReminderMessage: "",
notifyingReminderTime: "",
});
if (USE_DEXIE_DB) {
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingReminderMessage: "",
notifyingReminderTime: "",
});
}
this.notifyingReminder = false;
this.notifyingReminderMessage = "";
this.notifyingReminderTime = "";
@@ -1446,30 +1530,47 @@ export default class AccountViewView extends Vue {
public async toggleHideRegisterPromptOnNewContact() {
const newSetting = !this.hideRegisterPromptOnNewContact;
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
hideRegisterPromptOnNewContact: newSetting,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
hideRegisterPromptOnNewContact: newSetting,
});
}
this.hideRegisterPromptOnNewContact = newSetting;
}
public async updatePasskeyExpiration() {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
passkeyExpirationMinutes: this.passkeyExpirationMinutes,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
passkeyExpirationMinutes: this.passkeyExpirationMinutes,
});
}
clearPasskeyToken();
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
}
public async turnOffNotifyingFlags() {
// should tell the push server as well
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
notifyingNewActivityTime: "",
notifyingReminderMessage: "",
notifyingReminderTime: "",
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
notifyingNewActivityTime: "",
notifyingReminderMessage: "",
notifyingReminderTime: "",
});
}
this.notifyingNewActivity = false;
this.notifyingNewActivityTime = "";
this.notifyingReminder = false;
@@ -1509,7 +1610,10 @@ export default class AccountViewView extends Vue {
* @returns {Promise<Blob>} The generated blob object.
*/
private async generateDatabaseBlob(): Promise<Blob> {
return await db.export({ prettyJson: true });
if (USE_DEXIE_DB) {
return await db.export({ prettyJson: true });
}
throw new Error("Not implemented");
}
/**
@@ -1530,7 +1634,7 @@ export default class AccountViewView extends Vue {
private downloadDatabaseBackup(url: string) {
const downloadAnchor = this.$refs.downloadLink as HTMLAnchorElement;
downloadAnchor.href = url;
downloadAnchor.download = `${db.name}-backup.json`;
downloadAnchor.download = `${AppString.APP_NAME_NO_SPACES}-backup.json`;
downloadAnchor.click(); // doesn't work for some browsers, eg. DuckDuckGo
}
@@ -1611,25 +1715,30 @@ export default class AccountViewView extends Vue {
*/
async submitImportFile() {
if (inputImportFileNameRef.value != null) {
await db.delete()
.then(async () => {
// BulkError: settings.bulkAdd(): 1 of 21 operations failed. Errors: ConstraintError: Key already exists in the object store.
await Dexie.import(inputImportFileNameRef.value as Blob, {
progressCallback: this.progressCallback,
if (USE_DEXIE_DB) {
await db
.delete()
.then(async () => {
// BulkError: settings.bulkAdd(): 1 of 21 operations failed. Errors: ConstraintError: Key already exists in the object store.
await Dexie.import(inputImportFileNameRef.value as Blob, {
progressCallback: this.progressCallback,
});
})
})
.catch((error) => {
logger.error("Error importing file:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Importing",
text: "There was an error in the import. Your identities and contacts may have been affected, so you may have to restore your identifier and use the contact import method.",
},
-1,
);
});
.catch((error) => {
logger.error("Error importing file:", error);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Importing",
text: "There was an error in the import. Your identities and contacts may have been affected, so you may have to restore your identifier and use the contact import method.",
},
-1,
);
});
} else {
throw new Error("Not implemented");
}
}
}
@@ -1717,7 +1826,15 @@ export default class AccountViewView extends Vue {
if (!this.isRegistered) {
// the user was not known to be registered, but now they are (because we got no error) so let's record it
try {
await updateAccountSettings(did, { isRegistered: true });
await databaseUtil.updateAccountSettings(did, {
isRegistered: true,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
isRegistered: true,
});
}
this.isRegistered = true;
} catch (err) {
logger.error("Got an error updating settings:", err);
@@ -1777,26 +1894,41 @@ export default class AccountViewView extends Vue {
}
async onClickSaveApiServer() {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
apiServer: this.apiServerInput,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
apiServer: this.apiServerInput,
});
}
this.apiServer = this.apiServerInput;
}
async onClickSavePartnerServer() {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
partnerApiServer: this.partnerApiServerInput,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
partnerApiServer: this.partnerApiServerInput,
});
}
this.partnerApiServer = this.partnerApiServerInput;
}
async onClickSavePushServer() {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
webPushServer: this.webPushServerInput,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
webPushServer: this.webPushServerInput,
});
}
this.webPushServer = this.webPushServerInput;
this.$notify(
{
@@ -1812,10 +1944,15 @@ export default class AccountViewView extends Vue {
openImageDialog() {
(this.$refs.imageMethodDialog as ImageMethodDialog).open(
async (imgUrl) => {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
profileImageUrl: imgUrl,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
profileImageUrl: imgUrl,
});
}
this.profileImageUrl = imgUrl;
//console.log("Got image URL:", imgUrl);
},
@@ -1876,10 +2013,15 @@ export default class AccountViewView extends Vue {
// keep the imageUrl in localStorage so the user can try again if they want
}
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
await databaseUtil.updateDefaultSettings({
profileImageUrl: undefined,
});
if (USE_DEXIE_DB) {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
profileImageUrl: undefined,
});
}
this.profileImageUrl = undefined;
} catch (error) {
@@ -1888,9 +2030,14 @@ export default class AccountViewView extends Vue {
if ((error as any).response.status === 404) {
logger.error("The image was already deleted:", error);
await updateAccountSettings(this.activeDid, {
await databaseUtil.updateAccountSettings(this.activeDid, {
profileImageUrl: undefined,
});
if (USE_DEXIE_DB) {
await updateAccountSettings(this.activeDid, {
profileImageUrl: undefined,
});
}
this.profileImageUrl = undefined;
@@ -1968,7 +2115,12 @@ export default class AccountViewView extends Vue {
throw Error("Profile not saved");
}
} catch (error) {
logConsoleAndDb("Error saving profile: " + errorStringForLog(error));
databaseUtil.logConsoleAndDb(
"Error saving profile: " + errorStringForLog(error),
);
if (USE_DEXIE_DB) {
logConsoleAndDb("Error saving profile: " + errorStringForLog(error));
}
const errorMessage: string =
error.response?.data?.error?.message ||
error.response?.data?.error ||
@@ -2058,7 +2210,12 @@ export default class AccountViewView extends Vue {
throw Error("Profile not deleted");
}
} catch (error) {
logConsoleAndDb("Error deleting profile: " + errorStringForLog(error));
databaseUtil.logConsoleAndDb(
"Error deleting profile: " + errorStringForLog(error),
);
if (USE_DEXIE_DB) {
logConsoleAndDb("Error deleting profile: " + errorStringForLog(error));
}
const errorMessage: string =
error.response?.data?.error?.message ||
error.response?.data?.error ||