From b2bf5268b025aab0cbdb2ec82629a3549e778ba7 Mon Sep 17 00:00:00 2001 From: Maciej Samoraj Date: Mon, 23 Oct 2023 21:34:48 +0200 Subject: [PATCH] [ui/authorization] feature: simple data browsing (peers) Co-authored-by: elf Pavlik --- packages/api-messages/src/payloads.ts | 2 +- packages/api-messages/src/requests.ts | 26 ++++- .../src/readable/inheritable-data-grant.ts | 6 ++ .../src/readable/inherited-data-grant.ts | 6 ++ packages/service/src/handlers/api-handler.ts | 4 +- .../service/src/services/data-registries.ts | 55 ++++++++++- packages/service/src/services/descriptions.ts | 45 ++++++--- tsconfig.json | 1 + ui/authorization/src/backend.ts | 8 +- .../src/components/AuthorizeApp.vue | 7 +- ui/authorization/src/store/app.ts | 12 +-- ui/authorization/src/views/Dashboard.vue | 4 +- .../src/views/DataRegistryList.vue | 94 ++++++++++++------- .../src/views/SocialAgentList.vue | 10 +- 14 files changed, 207 insertions(+), 73 deletions(-) diff --git a/packages/api-messages/src/payloads.ts b/packages/api-messages/src/payloads.ts index 8a82656f..e29fee22 100644 --- a/packages/api-messages/src/payloads.ts +++ b/packages/api-messages/src/payloads.ts @@ -27,7 +27,7 @@ export interface DataRegistration extends UniqueId { shapeTree: IRI; // TODO dataOwner: IRI; dataRegistry?: IRI; - count: number; + count?: number; label?: string; // TODO label should be ensured } diff --git a/packages/api-messages/src/requests.ts b/packages/api-messages/src/requests.ts index e566e3a1..e4a4216a 100644 --- a/packages/api-messages/src/requests.ts +++ b/packages/api-messages/src/requests.ts @@ -41,7 +41,11 @@ export class SocialAgentsRequest extends MessageBase { export class AddSocialAgentRequest extends MessageBase { public type = RequestMessageTypes.ADD_SOCIAL_AGENT_REQUEST; - constructor(public webId: IRI, public label: string, public note?: string) { + constructor( + public webId: IRI, + public label: string, + public note?: string + ) { super(); } } @@ -49,7 +53,10 @@ export class AddSocialAgentRequest extends MessageBase { export class DataRegistriesRequest extends MessageBase { public type = RequestMessageTypes.DATA_REGISTRIES_REQUEST; - constructor(private lang: string) { + constructor( + private agentId: string, + private lang: string + ) { super(); } } @@ -57,7 +64,10 @@ export class DataRegistriesRequest extends MessageBase { export class DescriptionsRequest extends MessageBase { public type = RequestMessageTypes.DESCRIPTIONS_REQUEST; - constructor(private applicationId: IRI, private lang: string) { + constructor( + private applicationId: IRI, + private lang: string + ) { super(); } } @@ -65,7 +75,10 @@ export class DescriptionsRequest extends MessageBase { export class ListDataInstancesRequest extends MessageBase { public type = RequestMessageTypes.LIST_DATA_INSTANCES_REQUEST; - constructor(private registrationId: IRI) { + constructor( + private agentId: string, + private registrationId: IRI + ) { super(); } } @@ -81,7 +94,10 @@ export class ApplicationAuthorizationRequest extends MessageBase { export class ResourceRequest extends MessageBase { public type = RequestMessageTypes.RESOURCE_REQUEST; - constructor(public id: IRI, private lang: string) { + constructor( + public id: IRI, + private lang: string + ) { super(); } } diff --git a/packages/data-model/src/readable/inheritable-data-grant.ts b/packages/data-model/src/readable/inheritable-data-grant.ts index ad15713c..d521f833 100644 --- a/packages/data-model/src/readable/inheritable-data-grant.ts +++ b/packages/data-model/src/readable/inheritable-data-grant.ts @@ -25,4 +25,10 @@ export abstract class InheritableDataGrant extends AbstractDataGrant { get hasInheritingGrantIriList(): string[] { return this.getSubjectsArray(INTEROP.inheritsFromGrant).map((subject) => subject.value); } + + // TODO: extract to a mixin + get dataRegistryIri(): string { + const dataRegistrationIri = this.getObject('hasDataRegistration').value; + return `${dataRegistrationIri.split('/').slice(0, -2).join('/')}/`; + } } diff --git a/packages/data-model/src/readable/inherited-data-grant.ts b/packages/data-model/src/readable/inherited-data-grant.ts index 2e579d26..72e098de 100644 --- a/packages/data-model/src/readable/inherited-data-grant.ts +++ b/packages/data-model/src/readable/inherited-data-grant.ts @@ -37,4 +37,10 @@ export class InheritedDataGrant extends AbstractDataGrant { get canCreate(): boolean { return this.accessMode.includes(ACL.Write.value); } + + // TODO: extract to a mixin + get dataRegistryIri(): string { + const dataRegistrationIri = this.getObject('hasDataRegistration').value; + return `${dataRegistrationIri.split('/').slice(0, -2).join('/')}/`; + } } diff --git a/packages/service/src/handlers/api-handler.ts b/packages/service/src/handlers/api-handler.ts index d7ebcc2b..4ec8c005 100644 --- a/packages/service/src/handlers/api-handler.ts +++ b/packages/service/src/handlers/api-handler.ts @@ -83,7 +83,7 @@ export class ApiHandler extends HttpHandler { return { body: { type: ResponseMessageTypes.DATA_REGISTRIES_RESPONSE, - payload: await getDataRegistries(context.saiSession, body.lang) + payload: await getDataRegistries(body.agentId, body.lang, context.saiSession) }, status: 200, headers: {} @@ -101,7 +101,7 @@ export class ApiHandler extends HttpHandler { return { body: { type: ResponseMessageTypes.LIST_DATA_INSTANCES_RESPONSE, - payload: await listDataInstances(body.registrationId, context.saiSession) + payload: await listDataInstances(body.agentId, body.registrationId, context.saiSession) }, status: 200, headers: {} diff --git a/packages/service/src/services/data-registries.ts b/packages/service/src/services/data-registries.ts index 827a6565..26175629 100644 --- a/packages/service/src/services/data-registries.ts +++ b/packages/service/src/services/data-registries.ts @@ -1,4 +1,4 @@ -import type { CRUDDataRegistry } from '@janeirodigital/interop-data-model'; +import type { CRUDDataRegistry, DataGrant } from '@janeirodigital/interop-data-model'; import type { AuthorizationAgent } from '@janeirodigital/interop-authorization-agent'; import type { DataRegistration, DataRegistry } from '@janeirodigital/sai-api-messages'; @@ -24,7 +24,54 @@ const buildDataRegistry = async ( }; }; -export const getDataRegistries = async (saiSession: AuthorizationAgent, descriptionsLang: string) => - Promise.all( - saiSession.registrySet.hasDataRegistry.map((registry) => buildDataRegistry(registry, descriptionsLang, saiSession)) +const buildDataRegistryForGrant = async ( + registryIri: string, + dataGrants: DataGrant[], + descriptionsLang: string, + saiSession: AuthorizationAgent +): Promise => { + const registrations: DataRegistration[] = []; + for (const dataGrant of dataGrants) { + // eslint-disable-next-line no-await-in-loop + const shapeTree = await saiSession.factory.readable.shapeTree(dataGrant.registeredShapeTree, descriptionsLang); + registrations.push({ + id: dataGrant.hasDataRegistration, + shapeTree: dataGrant.registeredShapeTree, + dataRegistry: registryIri, + label: shapeTree.descriptions[descriptionsLang]?.label + }); + } + return { + id: registryIri, + registrations + }; +}; + +export const getDataRegistries = async (agentId: string, descriptionsLang: string, saiSession: AuthorizationAgent) => { + if (agentId === saiSession.webId) { + return Promise.all( + saiSession.registrySet.hasDataRegistry.map((registry) => + buildDataRegistry(registry, descriptionsLang, saiSession) + ) + ); + } + const socialAgentRegistration = (await saiSession.findSocialAgentRegistration(agentId)).reciprocalRegistration; + if (!socialAgentRegistration) { + throw new Error(`missing social agent registration: ${agentId}`); + } + if (!socialAgentRegistration.accessGrant) { + throw new Error(`missing access grant for social agent: ${agentId}`); + } + const dataGrantIndex = socialAgentRegistration.accessGrant.hasDataGrant.reduce((acc, dataGrant) => { + if (!acc[dataGrant.dataRegistryIri]) { + acc[dataGrant.dataRegistryIri] = [] as DataGrant[]; + } + acc[dataGrant.dataRegistryIri].push(dataGrant); + return acc; + }, {} as Record); + return Promise.all( + Object.entries(dataGrantIndex).map(([registryIri, dataGrants]) => + buildDataRegistryForGrant(registryIri, dataGrants, descriptionsLang, saiSession) + ) ); +}; diff --git a/packages/service/src/services/descriptions.ts b/packages/service/src/services/descriptions.ts index 049982dc..7ef4a892 100644 --- a/packages/service/src/services/descriptions.ts +++ b/packages/service/src/services/descriptions.ts @@ -3,9 +3,7 @@ import { DataAuthorizationData, InheritedDataGrant, ReadableAccessNeed, - ReadableAccessNeedGroup, - ReadableDataRegistration, - SelectedFromRegistryDataGrant + ReadableAccessNeedGroup } from '@janeirodigital/interop-data-model'; import type { AuthorizationAgent, @@ -158,19 +156,44 @@ export const getDescriptions = async ( }; export const listDataInstances = async ( + agentId: string, registrationId: string, saiSession: AuthorizationAgent ): Promise => { - const dataRegistration = await saiSession.factory.readable.dataRegistration(registrationId); const dataInstances: DataInstance[] = []; - for (const dataInstanceIri of dataRegistration.contains) { - // eslint-disable-next-line no-await-in-loop - const dataInstance = await saiSession.factory.readable.dataInstance(dataInstanceIri); - dataInstances.push({ - id: dataInstance.iri, - label: dataInstance.label - }); + if (agentId === saiSession.webId) { + const dataRegistration = await saiSession.factory.readable.dataRegistration(registrationId); + for (const dataInstanceIri of dataRegistration.contains) { + // eslint-disable-next-line no-await-in-loop + const dataInstance = await saiSession.factory.readable.dataInstance(dataInstanceIri); + dataInstances.push({ + id: dataInstance.iri, + label: dataInstance.label + }); + } + } else { + const socialAgentRegistration = (await saiSession.findSocialAgentRegistration(agentId)).reciprocalRegistration; + if (!socialAgentRegistration) { + throw new Error(`missing social agent registration: ${agentId}`); + } + if (!socialAgentRegistration.accessGrant) { + throw new Error(`missing access grant for social agent: ${agentId}`); + } + for (const dataGrant of socialAgentRegistration.accessGrant.hasDataGrant) { + if (dataGrant.hasDataRegistration === registrationId) { + // TODO: optimize not to create crud data instances + // eslint-disable-next-line no-await-in-loop + for await (const instance of dataGrant.getDataInstanceIterator()) { + const dataInstance = await saiSession.factory.readable.dataInstance(instance.iri); + dataInstances.push({ + id: dataInstance.iri, + label: dataInstance.label + }); + } + } + } } + return dataInstances; }; diff --git a/tsconfig.json b/tsconfig.json index 6d56b780..186a48f4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": true, "target": "ES2020", "sourceMap": true, + "declarationMap": true, "lib": ["ES2020", "DOM"] } } diff --git a/ui/authorization/src/backend.ts b/ui/authorization/src/backend.ts index 09eccfdf..fc727879 100644 --- a/ui/authorization/src/backend.ts +++ b/ui/authorization/src/backend.ts @@ -85,8 +85,8 @@ async function getAuthorization(clientId: IRI, lang: string): Promise { - const request = new ListDataInstancesRequest(registrationId); +async function listDataInstances(agentId: IRI, registrationId: IRI): Promise { + const request = new ListDataInstancesRequest(agentId, registrationId); const data = await getDataFromApi(request); const response = new ListDataInstancesResponse(data); return response.payload; @@ -120,8 +120,8 @@ async function listApplications(): Promise { return response.payload; } -async function listDataRegistires(lang: string): Promise { - const request = new DataRegistriesRequest(lang); +async function listDataRegistires(agentId: string, lang: string): Promise { + const request = new DataRegistriesRequest(agentId, lang); const data = await getDataFromApi(request); const response = new DataRegistriesResponse(data); return response.payload; diff --git a/ui/authorization/src/components/AuthorizeApp.vue b/ui/authorization/src/components/AuthorizeApp.vue index 31992d31..d57577ef 100644 --- a/ui/authorization/src/components/AuthorizeApp.vue +++ b/ui/authorization/src/components/AuthorizeApp.vue @@ -36,6 +36,7 @@ span.label { @@ -82,6 +83,7 @@ span.label { @@ -125,6 +127,7 @@ span.label { @@ -273,7 +276,7 @@ function buildRegistrationsIndex(data: typeof props.authorizationData.dataOwners id: registration.id, agent: agent.id, scope: 'all', - count: registration.count + count: registration.count! }; } } @@ -356,7 +359,7 @@ function registrationScopeChanged(agentId: string, registrationId: string, scope } async function loadDataInstances(agentId: string, registrationId: string, selected: boolean): Promise { - await appStore.listDataInstances(registrationId); + await appStore.listDataInstances(agentId, registrationId); addDataInstancesToIndex(agentId, registrationId, appStore.loadedDataInstances[registrationId], selected); } diff --git a/ui/authorization/src/store/app.ts b/ui/authorization/src/store/app.ts index cdc4abba..ca4a510b 100644 --- a/ui/authorization/src/store/app.ts +++ b/ui/authorization/src/store/app.ts @@ -25,7 +25,7 @@ export const useAppStore = defineStore('app', () => { const application = ref | null>(null); const loadedDataInstances = reactive>({}); const applicationList = reactive([]); - const dataRegistryList = reactive([]); + const dataRegistryList = reactive>({}); const backend = useBackend(); @@ -42,8 +42,8 @@ export const useAppStore = defineStore('app', () => { } // TODO rename list with load - async function listDataInstances(registrationId: string) { - const dataInstances = await backend.listDataInstances(registrationId); + async function listDataInstances(agentId: string, registrationId: string) { + const dataInstances = await backend.listDataInstances(agentId, registrationId); loadedDataInstances[registrationId] = [...dataInstances]; } @@ -66,10 +66,10 @@ export const useAppStore = defineStore('app', () => { } } - async function listDataRegistries(lang = 'en') { + async function listDataRegistries(agentId: string, lang = 'en') { if (!dataRegistryList.length) { - const dataRegistries = await backend.listDataRegistires(lang); - dataRegistryList.push(...dataRegistries); + const dataRegistries = await backend.listDataRegistires(agentId, lang); + dataRegistryList[agentId] = [...dataRegistries]; } } diff --git a/ui/authorization/src/views/Dashboard.vue b/ui/authorization/src/views/Dashboard.vue index 0366ec6b..18842beb 100644 --- a/ui/authorization/src/views/Dashboard.vue +++ b/ui/authorization/src/views/Dashboard.vue @@ -15,7 +15,7 @@ Peers - + mdi-hexagon-multiple-outline Data @@ -24,4 +24,6 @@ diff --git a/ui/authorization/src/views/DataRegistryList.vue b/ui/authorization/src/views/DataRegistryList.vue index 4cb6e5e7..d11252a7 100644 --- a/ui/authorization/src/views/DataRegistryList.vue +++ b/ui/authorization/src/views/DataRegistryList.vue @@ -4,46 +4,74 @@ span.label { } \ No newline at end of file diff --git a/ui/authorization/src/views/SocialAgentList.vue b/ui/authorization/src/views/SocialAgentList.vue index 457d7159..d5dca530 100644 --- a/ui/authorization/src/views/SocialAgentList.vue +++ b/ui/authorization/src/views/SocialAgentList.vue @@ -1,8 +1,10 @@