forked from jsnbuchanan/crowd-funder-for-time-pwa
allow bulk-imported contacts to have visibility set
This commit is contained in:
@@ -6,7 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [0.3.26]
|
||||
## [0.3.?]
|
||||
### Fixed
|
||||
- Allow visibility of bulk-imported contacts
|
||||
|
||||
|
||||
## [0.3.26] - 2024.09.16 - 8263ed2b29947b3ccc6f3133bbc9454c222bce28
|
||||
### Added
|
||||
- Separate 'isRegistered' flag for each account
|
||||
### Fixed
|
||||
|
||||
@@ -49,7 +49,7 @@ npm run lint
|
||||
```
|
||||
# (Let's replace this with a .env.development or .env.staging file.)
|
||||
# The test BVC_MEETUPS_PROJECT_CLAIM_ID does not resolve as a URL because it's only in the test DB and the prod redirect won't redirect there.
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_PASSKEYS_ENABLED=yep npm run build
|
||||
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_PASSKEYS_ENABLED=true npm run build
|
||||
```
|
||||
|
||||
* Production
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "TimeSafari",
|
||||
"version": "0.3.26",
|
||||
"version": "0.3.27-beta",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "TimeSafari",
|
||||
"version": "0.3.26",
|
||||
"version": "0.3.27-beta",
|
||||
"dependencies": {
|
||||
"@dicebear/collection": "^5.4.1",
|
||||
"@dicebear/core": "^5.4.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "TimeSafari",
|
||||
"version": "0.3.26",
|
||||
"version": "0.3.27-beta",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"serve": "vite preview",
|
||||
|
||||
@@ -436,7 +436,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 Contacts
|
||||
Import Only Contacts
|
||||
<br />
|
||||
after comparing
|
||||
</button>
|
||||
|
||||
@@ -90,12 +90,13 @@ import { Component, Vue } from "vue-facing-decorator";
|
||||
import { Router } from "vue-router";
|
||||
|
||||
import { AppString, NotificationIface } from "@/constants/app";
|
||||
import { db } from "@/db/index";
|
||||
import { db, retrieveSettingsForActiveAccount } from "@/db/index";
|
||||
import { Contact } from "@/db/tables/contacts";
|
||||
import * as libsUtil from "@/libs/util";
|
||||
import QuickNav from "@/components/QuickNav.vue";
|
||||
import EntityIcon from "@/components/EntityIcon.vue";
|
||||
import OfferDialog from "@/components/OfferDialog.vue";
|
||||
import { setVisibilityUtil } from "@/libs/endorserServer";
|
||||
|
||||
@Component({
|
||||
components: { EntityIcon, OfferDialog, QuickNav },
|
||||
@@ -107,6 +108,8 @@ export default class ContactImportView extends Vue {
|
||||
libsUtil = libsUtil;
|
||||
R = R;
|
||||
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
contactsExisting: Record<string, Contact> = {}; // user's contacts already in the system, keyed by DID
|
||||
contactsImporting: Array<Contact> = []; // contacts from the import
|
||||
contactsSelected: Array<boolean> = []; // whether each contact in contactsImporting is selected
|
||||
@@ -119,6 +122,10 @@ export default class ContactImportView extends Vue {
|
||||
sameCount = 0;
|
||||
|
||||
async created() {
|
||||
const settings = await retrieveSettingsForActiveAccount();
|
||||
this.activeDid = settings.activeDid || "";
|
||||
this.apiServer = settings.apiServer || "";
|
||||
|
||||
// Retrieve the imported contacts from the query parameter
|
||||
const importedContacts =
|
||||
((this.$route as Router).query["contacts"] as string) || "[]";
|
||||
@@ -176,13 +183,46 @@ export default class ContactImportView extends Vue {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.makeVisible) {
|
||||
const failedVisibileToContacts = [];
|
||||
for (let i = 0; i < this.contactsImporting.length; i++) {
|
||||
const contact = this.contactsImporting[i];
|
||||
if (contact) {
|
||||
const visResult = await setVisibilityUtil(
|
||||
this.activeDid,
|
||||
this.apiServer,
|
||||
this.axios,
|
||||
db,
|
||||
contact,
|
||||
true,
|
||||
);
|
||||
if (!visResult.success) {
|
||||
failedVisibileToContacts.push(contact);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (failedVisibileToContacts.length) {
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "danger",
|
||||
title: "Visibility Error",
|
||||
text: `Failed to set visibility for ${failedVisibileToContacts.length} contact${
|
||||
failedVisibileToContacts.length == 1 ? "" : "s"
|
||||
}. You must set them individually: ${failedVisibileToContacts.map((c) => c.name).join(", ")}`,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.importing = false;
|
||||
|
||||
this.$notify(
|
||||
{
|
||||
group: "alert",
|
||||
type: "success",
|
||||
title: "Import Success",
|
||||
title: "Imported",
|
||||
text:
|
||||
`${importedCount} contact${importedCount == 1 ? "" : "s"} imported.` +
|
||||
(updatedCount ? ` ${updatedCount} updated.` : ""),
|
||||
|
||||
@@ -907,74 +907,6 @@ export default class ContactsView extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
// note that this is also in DIDView.vue
|
||||
private async checkVisibility(contact: Contact) {
|
||||
const url =
|
||||
this.apiServer +
|
||||
"/api/report/canDidExplicitlySeeMe?did=" +
|
||||
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,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await this.axios.get(url, { headers });
|
||||
if (resp.status === 200) {
|
||||
const visibility = resp.data;
|
||||
contact.seesMe = visibility;
|
||||
//console.log("Visi check:", visibility, contact.seesMe, contact.did);
|
||||
await db.contacts.update(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,
|
||||
);
|
||||
} else {
|
||||
console.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,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private confirmShowGiftedDialog(giverDid: string, recipientDid: string) {
|
||||
// if they have unconfirmed amounts, ask to confirm those
|
||||
if (
|
||||
|
||||
@@ -88,8 +88,6 @@
|
||||
>
|
||||
<fa icon="eye-slash" class="fa-fw" />
|
||||
</button>
|
||||
<!-- otherwise it's this user so hide it -->
|
||||
<fa v-else icon="eye" class="text-white mx-2.5" />
|
||||
|
||||
<button
|
||||
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"
|
||||
@@ -99,8 +97,6 @@
|
||||
>
|
||||
<fa icon="rotate" class="fa-fw" />
|
||||
</button>
|
||||
<!-- otherwise it's this user so hide it -->
|
||||
<fa v-else icon="rotate" class="text-white mx-2.5" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -116,8 +112,6 @@
|
||||
/>
|
||||
<fa v-else icon="person-circle-question" class="fa-fw" />
|
||||
</button>
|
||||
<!-- otherwise it's this user so hide it -->
|
||||
<fa v-else icon="rotate" class="text-white ml-6 px-2.5" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -346,15 +340,20 @@ export default class DIDView extends Vue {
|
||||
|
||||
// prompt with confirmation if they want to delete a contact
|
||||
confirmDeleteContact(contact: Contact) {
|
||||
let message =
|
||||
"Are you sure you want to remove " +
|
||||
libsUtil.nameForContact(contact, false) +
|
||||
" from your contact list?";
|
||||
if (contact.seesMe) {
|
||||
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:
|
||||
"Are you sure you want to remove " +
|
||||
libsUtil.nameForContact(contact, false) +
|
||||
" from your contact list?",
|
||||
text: message,
|
||||
onYes: async () => {
|
||||
await this.deleteContact(contact);
|
||||
},
|
||||
|
||||
@@ -176,4 +176,22 @@ test('Add contact, copy details, delete, and import various ways', async ({ page
|
||||
await page.locator('button', { hasText: 'Import' }).click();
|
||||
// check that there are more contacts
|
||||
await expect(page.getByTestId('contactListItem')).toHaveCount(2);
|
||||
|
||||
// Import via the file backup-import
|
||||
await page.goto('./account');
|
||||
await page.getByRole('heading', { name: 'Advanced' }).click();
|
||||
const fileSelect = await page.locator('input[type="file"]')
|
||||
//fileSelect.click();
|
||||
fileSelect.setInputFiles('./test-playwright/exported-data.json');
|
||||
await page.locator('button', { hasText: 'Import Only Contacts' }).click();
|
||||
// we're on the contact-import page
|
||||
await expect(page.locator('li', { hasText: '- New' })).toHaveCount(3);
|
||||
await expect(page.locator('li', { hasText: '- Existing' })).toHaveCount(1);
|
||||
await expect(page.locator('span').filter({ hasText: 'the same as' })).toBeHidden();
|
||||
await page.locator('button', { hasText: 'Import' }).click();
|
||||
// check that there are more contacts
|
||||
await expect(page.getByTestId('contactListItem')).toHaveCount(5);
|
||||
// The visibility error is because currently the server returns an error for the same person.
|
||||
// But it should only show that one, for User #000.
|
||||
|
||||
});
|
||||
|
||||
86
test-playwright/exported-data.json
Normal file
86
test-playwright/exported-data.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"formatName": "dexie",
|
||||
"formatVersion": 1,
|
||||
"data": {
|
||||
"databaseName": "TimeSafari",
|
||||
"databaseVersion": 4,
|
||||
"tables": [
|
||||
{
|
||||
"name": "contacts",
|
||||
"schema": "did,name",
|
||||
"rowCount": 12
|
||||
},
|
||||
{
|
||||
"name": "logs",
|
||||
"schema": "date",
|
||||
"rowCount": 0
|
||||
},
|
||||
{
|
||||
"name": "settings",
|
||||
"schema": "id,&accountDid",
|
||||
"rowCount": 2
|
||||
},
|
||||
{
|
||||
"name": "temp",
|
||||
"schema": "id",
|
||||
"rowCount": 0
|
||||
}
|
||||
],
|
||||
"data": [{
|
||||
"tableName": "contacts",
|
||||
"inbound": true,
|
||||
"rows": [
|
||||
{
|
||||
"did": "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F",
|
||||
"name": "User #00",
|
||||
"publicKeyBase64": "A7Ix5zQT8dNrMyd2OtmkS7gfyRSAoUl3qzz9Mt8FZK8d",
|
||||
"nextPubKeyHashB64": "d9D/wZLUvI/EyOiMKyxcml0uPKrTh5T0tMGcQjjaqE4=",
|
||||
"seesMe": false
|
||||
},
|
||||
{
|
||||
"did": "did:ethr:0x0Fc2683554C20B3Ea75aa5bf77B3519005082037",
|
||||
"name": "tester",
|
||||
"nextPubKeyHashB64": "CCOqpInfn4Exg7rIdiUxU+K+BUr5GQUVdSmN6SHOHKs=",
|
||||
"profileImageUrl": 0,
|
||||
"publicKeyBase64": "AnWEewlbkBH7Q+DZgAglXkqd3Ufxvqvf5OcjenO62Opl",
|
||||
"registered": true,
|
||||
"seesMe": true
|
||||
},
|
||||
{
|
||||
"did": "did:ethr:0x111d15564f824D56C7a07b913aA7aDd03382aA39",
|
||||
"name": "User 111",
|
||||
"nextPubKeyHashB64": "ge3fGAoxP+Ak48UFg2u9BPdd4ircmvqT34p9spU+h5M=",
|
||||
"profileImageUrl": "https://test-image.timesafari.app/6b3cba6970f44a883bcbaf302384c50f9b0940e4812ac188649a3b9ec0ebada9.png",
|
||||
"publicKeyBase64": "A1HdQoCMRkWkTgBvTcJFT6tZ6EXIWZaa0aFsnYNzfE/L",
|
||||
"registered": true,
|
||||
"seesMe": true
|
||||
},
|
||||
{
|
||||
"did": "did:peer:0zKMFjvUgYrM1hXwDciKXRoR8dd5PXWoHxAFQZ9jU46wURZizUC128RpzpEc6CpzxQWMdHVS5b3W91yGR6hLUkfcC7UdLtU5jB2fW5TMrQTUte",
|
||||
"name": "did:peer ixroo",
|
||||
"publicKeyBase64": 0,
|
||||
"nextPubKeyHashB64": 0,
|
||||
"registered": true,
|
||||
"seesMe": true
|
||||
}
|
||||
]
|
||||
},{
|
||||
"tableName": "settings",
|
||||
"inbound": true,
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
"activeDid": "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F",
|
||||
"apiServer": "https://test-api.endorser.ch",
|
||||
"lastViewedClaimId": "01J7SCRCJBYHS3RQWFP9EHXYJZ",
|
||||
"firstName": "Me"
|
||||
},
|
||||
{
|
||||
"isRegistered": false,
|
||||
"accountDid": "did:ethr:0x0Fc2683554C20B3Ea75aa5bf77B3519005082037",
|
||||
"id": 2
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user