diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d0e1f9..0cef73da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add dark theme +- Integrate joining community via [community inbox service](https://github.com/solidcouch/community-inbox) ### Changed diff --git a/cypress/e2e/setup.cy.ts b/cypress/e2e/setup.cy.ts index 384aa1d8..bc0201f0 100644 --- a/cypress/e2e/setup.cy.ts +++ b/cypress/e2e/setup.cy.ts @@ -1,8 +1,16 @@ import { Parser, Store } from 'n3' -import { sioc, solid, space } from 'rdf-namespaces' -import { processAcl } from '../../src/utils/helpers' -import { UserConfig } from '../support/css-authentication' -import { CommunityConfig, SkipOptions } from '../support/setup' +import { foaf, ldp, sioc, solid, space, vcard } from 'rdf-namespaces' +import { processAcl, removeHashFromURI } from '../../src/utils/helpers' +import { + getAuthenticatedFetch, + UserConfig, +} from '../support/css-authentication' +import { generateAcl } from '../support/helpers/acl' +import { + CommunityConfig, + SkipOptions, + throwIfResponseNotOk, +} from '../support/setup' const preparePod = () => { cy.createRandomAccount().as('user1') @@ -85,6 +93,96 @@ describe('Setup Solid pod', () => { }) }) + context('community not joined (new join service)', () => { + const inboxUrl = `https://inbox.community.org/inbox` + + beforeEach(() => { + cy.get('@community') + .then(com => { + // add an inbox to the community + cy.authenticatedRequest(com.user, { + url: com.community, + method: 'PATCH', + headers: { 'content-type': 'text/n3' }, + body: ` + _:addInbox a <${solid.InsertDeletePatch}>; + <${solid.inserts}> { <${com.community}> <${ldp.inbox}> <${inboxUrl}>. }. + `, + }) + + // change access rights of the group to read only + const resource = (() => { + const url = new URL(com.group) + url.hash = '' + return url.toString() + })() + const groupAcl = generateAcl(resource, [ + { + permissions: ['Read', 'Write', 'Append', 'Control'], + agents: [com.user.webId], + }, + { + permissions: ['Read', 'Write'], + agents: ['https://inbox.community.org/profile/card#bot'], + }, + { permissions: ['Read'], agentClasses: [foaf.Agent] }, + ]) + cy.authenticatedRequest(com.user, { + url: resource + '.acl', + method: 'PUT', + body: groupAcl, + headers: { 'content-type': 'text/turtle' }, + }) + + // mock requests to that inbox + cy.intercept<{ + actor: { id: string } + }>('POST', inboxUrl, async req => { + const authFetch = await getAuthenticatedFetch(com.user) + const altRes = await authFetch(com.group, { + method: 'PATCH', + headers: { 'content-type': `text/n3` }, + body: `_:insertPerson a <${solid.InsertDeletePatch}>; + <${solid.inserts}> { <${com.group}> <${vcard.hasMember}> <${req.body.actor.id}>. }.`, + }) + await throwIfResponseNotOk(altRes) + return req.reply({ + statusCode: 200, + headers: { location: com.group }, + }) + }) + }) + .as('joinActivity') + }) + + beforeEach(setupPod(['joinCommunity'])) + + it('should send a `Join` activity to community inbox', () => { + cy.get('@community').then(community => { + cy.get('@user1').then(user => { + cy.login(user) + cy.contains('button', 'Continue!') + cy.intercept('GET', removeHashFromURI(community.group)).as( + 'groupUpdate', + ) + cy.contains('button', 'Continue!').click() + + // check that the activity was sent to inbox + cy.wait('@joinActivity') + .its('request.body') + .should('deep.nested.include', { + actor: { type: 'Person', id: user.webId }, + object: { type: 'Group', id: community.community }, + }) + + // check that the group was refetched afterwards + cy.wait('@groupUpdate') + }) + }) + cy.contains('a', 'travel') + }) + }) + context('personal hospex document for this community does not exist', () => { beforeEach(setupPod(['personalHospexDocument'])) it('should create personal hospex document for this community', () => { diff --git a/cypress/support/css-authentication.ts b/cypress/support/css-authentication.ts index c7183b40..7129e15d 100644 --- a/cypress/support/css-authentication.ts +++ b/cypress/support/css-authentication.ts @@ -5,6 +5,7 @@ import { } from '@inrupt/solid-client-authn-core' import { buildAuthenticatedFetch } from './buildAuthenticatedFetch' import { cyFetchWrapper, cyUnwrapFetch } from './css-authentication-helpers' +import { throwIfResponseNotOk } from './setup' export interface UserConfig { idp: string @@ -144,3 +145,68 @@ export const getAuthenticatedRequest = (user: UserConfig) => const authRequest = cyUnwrapFetch(authFetchWrapper) return cy.wrap(authRequest, { log: false }) }) + +// TODO replace with css-authn when it works in browser again +export const getAuthenticatedFetch = async (user: UserConfig) => { + const res1 = await fetch(new URL('/.account/', user.idp)) + await throwIfResponseNotOk(res1) + const loginEndpoint = (await res1.json()).controls.password.login as string + const res2 = await fetch(loginEndpoint, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ email: user.email, password: user.password }), + }) + await throwIfResponseNotOk(res2) + const authorization = (await res2.json()).authorization + + const res3 = await fetch(new URL('/.account/', user.idp), { + headers: { authorization: `CSS-Account-Token ${authorization}` }, + }) + await throwIfResponseNotOk(res3) + + const controls = (await res3.json()).controls + + const res4 = await fetch(controls.account.clientCredentials, { + method: 'POST', + headers: { + authorization: `CSS-Account-Token ${authorization}`, + 'content-type': 'application/json', + }, + // The name field will be used when generating the ID of your token. + // The WebID field determines which WebID you will identify as when using the token. + // Only WebIDs linked to your account can be used. + body: JSON.stringify({ name: 'cypress-login-token', webId: user.webId }), + }) + await throwIfResponseNotOk(res4) + + const { id, secret } = await res4.json() + + const tokenUrl = new URL('/.oidc/token', user.idp) + const dpopKey = await generateDpopKeyPair() + const dpop = await createDpopHeader(tokenUrl.toString(), 'POST', dpopKey) + const res5 = await fetch(tokenUrl, { + method: 'POST', + headers: { + // The header needs to be in base64 encoding. + authorization: `Basic ${btoa( + `${encodeURIComponent(id)}:${encodeURIComponent(secret)}`, + )}`, + 'content-type': 'application/x-www-form-urlencoded', + dpop, + }, + body: 'grant_type=client_credentials&scope=webid', + }) + await throwIfResponseNotOk(res5) + + const token = (await res5.json()).access_token + + const authFetch = await buildAuthenticatedFetch(token, { dpopKey }) + + const resLogout = await fetch(controls.account.logout, { + method: 'POST', + headers: { authorization: `CSS-Account-Token ${authorization}` }, + }) + await throwIfResponseNotOk(resLogout) + + return authFetch +} diff --git a/cypress/support/helpers/acl.ts b/cypress/support/helpers/acl.ts new file mode 100644 index 00000000..f42a9101 --- /dev/null +++ b/cypress/support/helpers/acl.ts @@ -0,0 +1,45 @@ +import { acl } from 'rdf-namespaces' + +interface AclConfig { + permissions: ('Read' | 'Write' | 'Append' | 'Control')[] + identifier?: string + agents?: string[] + agentGroups?: string[] + agentClasses?: string[] + isDefault?: boolean +} + +const generateAuthorization = (resource: string, config: AclConfig) => { + config.identifier ??= config.permissions.join('') + const { + permissions, + agents, + agentGroups, + agentClasses, + isDefault, + identifier, + } = config + + return `<#${identifier}> a <${acl.Authorization}>; + ${ + agents && agents.length > 0 + ? `<${acl.agent}> ${agents.map(a => `<${a}>`).join(', ')};` + : '' + } + ${ + agentGroups && agentGroups.length > 0 + ? `<${acl.agentGroup}> ${agentGroups.map(a => `<${a}>`).join(', ')};` + : '' + } + ${ + agentClasses && agentClasses.length > 0 + ? `<${acl.agentClass}> ${agentClasses.map(a => `<${a}>`).join(', ')};` + : '' + } + <${acl.accessTo}> <${resource}>; + ${isDefault ? `<${acl.default__workaround}> <${resource}>;` : ''} + <${acl.mode}> ${permissions.map(p => `<${acl[p]}>`).join(', ')}.` +} + +export const generateAcl = (resource: string, acls: AclConfig[]) => + acls.map(config => generateAuthorization(resource, config)).join('\n\n') diff --git a/cypress/support/setup.ts b/cypress/support/setup.ts index e3597e63..0b472f85 100644 --- a/cypress/support/setup.ts +++ b/cypress/support/setup.ts @@ -530,6 +530,13 @@ export const stubMailer = ({ }).as('simpleEmailNotification') } +export const throwIfResponseNotOk = async (response: Response) => { + if (!response.ok) + throw new Error( + `Query was not successful: ${response.status} ${await response.text()}`, + ) +} + const createAccountAsync = (ifNotExist?: boolean) => async ({ @@ -557,15 +564,6 @@ const createAccountAsync = const accountEndpoint = new URL('.account/account/', provider).toString() - const throwIfResponseNotOk = async (response: Response) => { - if (!response.ok) - throw new Error( - `Query was not successful: ${ - response.status - } ${await response.text()}`, - ) - } - // create the account const response = await fetch(accountEndpoint, { method: 'post', diff --git a/package.json b/package.json index 6e6725f4..0cf7e488 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build:ldo": "ldo build --input src/shapes --output src/ldo", "postbuild:ldo": "yarn format", "cy:dev": "concurrently --kill-others \"yarn cy:dev:app\" \"yarn preview\" \"yarn cy:dev:css\" \"yarn cy:dev:open\"", - "cy:dev:app": "BROWSER=none VITE_COMMUNITY=http://localhost:4000/test-community/community#us VITE_EMAIL_NOTIFICATIONS_SERVICE=http://localhost:3005 VITE_ENABLE_DEV_CLIENT_ID=true VITE_EMAIL_NOTIFICATIONS_IDENTITY=http://localhost:4000/mailbot/profile/card#me VITE_EMAIL_NOTIFICATIONS_TYPE=simple VITE_GEOINDEX= yarn build --watch", + "cy:dev:app": "VITE_COMMUNITY=http://localhost:4000/test-community/community#us VITE_EMAIL_NOTIFICATIONS_SERVICE=http://localhost:3005 VITE_ENABLE_DEV_CLIENT_ID=true VITE_EMAIL_NOTIFICATIONS_IDENTITY=http://localhost:4000/mailbot/profile/card#me VITE_EMAIL_NOTIFICATIONS_TYPE=simple VITE_GEOINDEX= yarn build --watch", "cy:dev:css": "community-solid-server -p 4000 -c ./cypress/css-config-no-log.json", "cy:dev:open": "cypress open", "knip": "knip" diff --git a/src/hooks/data/queries/community.ts b/src/hooks/data/queries/community.ts index b559b145..67581d3d 100644 --- a/src/hooks/data/queries/community.ts +++ b/src/hooks/data/queries/community.ts @@ -1,5 +1,5 @@ import type { RdfQuery } from '@ldhop/core' -import { sioc, vcard } from 'rdf-namespaces' +import { ldp, sioc, vcard } from 'rdf-namespaces' export const readCommunityQuery: RdfQuery = [ { @@ -9,6 +9,13 @@ export const readCommunityQuery: RdfQuery = [ pick: 'object', target: '?group', }, + { + type: 'match', + subject: '?community', + predicate: ldp.inbox, + pick: 'object', + target: '?inbox', + }, ] export const readCommunityMembersQuery: RdfQuery = [ diff --git a/src/hooks/data/useCommunity.ts b/src/hooks/data/useCommunity.ts index e0e92017..43ecedc9 100644 --- a/src/hooks/data/useCommunity.ts +++ b/src/hooks/data/useCommunity.ts @@ -73,7 +73,17 @@ export const useReadCommunity = (communityId: URI) => { pun, groups: variables.group ?? [], isLoading, + inbox: variables.inbox?.[0], }), - [about, community.logo, communityId, isLoading, name, pun, variables.group], + [ + about, + community.logo, + communityId, + isLoading, + name, + pun, + variables.group, + variables.inbox, + ], ) } diff --git a/src/hooks/data/useJoinGroup.ts b/src/hooks/data/useJoinGroup.ts index cf2486c2..048235f0 100644 --- a/src/hooks/data/useJoinGroup.ts +++ b/src/hooks/data/useJoinGroup.ts @@ -1,9 +1,17 @@ import { URI } from '@/types' +import { HttpError } from '@/utils/errors' +import { removeHashFromURI } from '@/utils/helpers' import { solid, vcard } from '@/utils/rdf-namespaces' +import { fetch } from '@inrupt/solid-client-authn-browser' +import { useMutation, useQueryClient } from '@tanstack/react-query' import { useCallback } from 'react' import { useUpdateRdfDocument } from './useRdfDocument' -export const useJoinGroup = () => { +/** + * Join community by appending membership triple directly to the group + * @deprecated This method of joining is unsafe, and will be removed in the future. Send Join activity to community inbox with `useJoinCommunity` instead. + */ +export const useJoinGroupLegacy = () => { const updateMutation = useUpdateRdfDocument() return useCallback( async ({ person, group }: { person: URI; group: URI }) => { @@ -17,3 +25,52 @@ export const useJoinGroup = () => { [updateMutation], ) } + +type NotificationData = { + actor: string + object: string + type: 'Join' +} + +const getNotificationBody = ({ actor, object, type }: NotificationData) => ({ + '@context': 'https://www.w3.org/ns/activitystreams', + type, + actor: { type: 'Person', id: actor }, + object: { type: 'Group', id: object }, +}) + +const notifyCommunityInbox = async ({ + inbox, + ...data +}: NotificationData & { inbox: string }) => { + const response = await fetch(inbox, { + method: 'POST', + body: JSON.stringify(getNotificationBody(data)), + headers: { 'content-type': 'application/ld+json' }, + }) + + if (!response.ok) + throw new HttpError('Community inbox responded with error', response) + + const group = response.headers.get('location') + + return { ...data, status: response.status, group } +} + +/** + * Join community by sending "Join" activity to community inbox + */ +export const useJoinCommunity = () => { + const queryClient = useQueryClient() + + const { mutateAsync } = useMutation({ + mutationFn: notifyCommunityInbox, + onSuccess: async data => { + if (!data.group) return + await queryClient.invalidateQueries({ + queryKey: ['rdfDocument', removeHashFromURI(data.group)], + }) + }, + }) + return mutateAsync +} diff --git a/src/ldo/accommodation.schema.ts b/src/ldo/accommodation.schema.ts index fc3d9f7e..a6f05440 100644 --- a/src/ldo/accommodation.schema.ts +++ b/src/ldo/accommodation.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/accommodation.shapeTypes.ts b/src/ldo/accommodation.shapeTypes.ts index fbe776ae..fe652ec5 100644 --- a/src/ldo/accommodation.shapeTypes.ts +++ b/src/ldo/accommodation.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { accommodationContext } from './accommodation.context' import { accommodationSchema } from './accommodation.schema' +import { accommodationContext } from './accommodation.context' import { Accommodation, Point } from './accommodation.typings' /** diff --git a/src/ldo/activity.schema.ts b/src/ldo/activity.schema.ts index 821789e4..20a1d00e 100644 --- a/src/ldo/activity.schema.ts +++ b/src/ldo/activity.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/activity.shapeTypes.ts b/src/ldo/activity.shapeTypes.ts index 60d056bb..d8114b5b 100644 --- a/src/ldo/activity.shapeTypes.ts +++ b/src/ldo/activity.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { activityContext } from './activity.context' import { activitySchema } from './activity.schema' +import { activityContext } from './activity.context' import { Activity } from './activity.typings' /** diff --git a/src/ldo/app.schema.ts b/src/ldo/app.schema.ts index 22658de3..79447489 100644 --- a/src/ldo/app.schema.ts +++ b/src/ldo/app.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/app.shapeTypes.ts b/src/ldo/app.shapeTypes.ts index 44440fa5..0052cf33 100644 --- a/src/ldo/app.shapeTypes.ts +++ b/src/ldo/app.shapeTypes.ts @@ -1,25 +1,25 @@ import { ShapeType } from '@ldo/ldo' -import { appContext } from './app.context' import { appSchema } from './app.schema' +import { appContext } from './app.context' import { + SolidProfile, + FoafProfile, + HospexProfile, Accommodation, + Point, + PublicTypeIndex, + PrivateTypeIndex, + TypeRegistration, + ChatShape, + ChatParticipationShape, ChatMessageListShape, ChatMessageShape, - ChatParticipationShape, - ChatShape, - ContactInvitationActivity, - ContactRelationship, Container, - FoafProfile, - HospexProfile, + Resource, Inbox, MessageActivity, - Point, - PrivateTypeIndex, - PublicTypeIndex, - Resource, - SolidProfile, - TypeRegistration, + ContactInvitationActivity, + ContactRelationship, } from './app.typings' /** diff --git a/src/ldo/container.schema.ts b/src/ldo/container.schema.ts index 2d7ced8f..0abac4ce 100644 --- a/src/ldo/container.schema.ts +++ b/src/ldo/container.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/container.shapeTypes.ts b/src/ldo/container.shapeTypes.ts index 8db8ee5a..80b416b1 100644 --- a/src/ldo/container.shapeTypes.ts +++ b/src/ldo/container.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { containerContext } from './container.context' import { containerSchema } from './container.schema' +import { containerContext } from './container.context' import { Container, Resource } from './container.typings' /** diff --git a/src/ldo/foafProfile.schema.ts b/src/ldo/foafProfile.schema.ts index b1b6c1ed..3c4f1fe2 100644 --- a/src/ldo/foafProfile.schema.ts +++ b/src/ldo/foafProfile.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/foafProfile.shapeTypes.ts b/src/ldo/foafProfile.shapeTypes.ts index 3ce6d2e3..c33d4582 100644 --- a/src/ldo/foafProfile.shapeTypes.ts +++ b/src/ldo/foafProfile.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { foafProfileContext } from './foafProfile.context' import { foafProfileSchema } from './foafProfile.schema' +import { foafProfileContext } from './foafProfile.context' import { FoafProfile } from './foafProfile.typings' /** diff --git a/src/ldo/hospexCommunity.context.ts b/src/ldo/hospexCommunity.context.ts index c9a2b8bb..1fd90265 100644 --- a/src/ldo/hospexCommunity.context.ts +++ b/src/ldo/hospexCommunity.context.ts @@ -35,6 +35,10 @@ export const hospexCommunityContext: ContextDefinition = { '@id': 'http://xmlns.com/foaf/0.1/homepage', '@type': '@id', }, + inbox: { + '@id': 'http://www.w3.org/ns/ldp#inbox', + '@type': '@id', + }, hasUsergroup: { '@id': 'http://rdfs.org/sioc/ns#has_usergroup', '@type': '@id', diff --git a/src/ldo/hospexCommunity.schema.ts b/src/ldo/hospexCommunity.schema.ts index 87c0a4fb..2bb2593f 100644 --- a/src/ldo/hospexCommunity.schema.ts +++ b/src/ldo/hospexCommunity.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= @@ -113,6 +113,16 @@ export const hospexCommunitySchema: Schema = { min: 0, max: 1, }, + { + type: 'TripleConstraint', + predicate: 'http://www.w3.org/ns/ldp#inbox', + valueExpr: { + type: 'NodeConstraint', + nodeKind: 'iri', + }, + min: 0, + max: 1, + }, { type: 'TripleConstraint', predicate: 'http://rdfs.org/sioc/ns#has_usergroup', diff --git a/src/ldo/hospexCommunity.shapeTypes.ts b/src/ldo/hospexCommunity.shapeTypes.ts index 061ad17c..6643e922 100644 --- a/src/ldo/hospexCommunity.shapeTypes.ts +++ b/src/ldo/hospexCommunity.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { hospexCommunityContext } from './hospexCommunity.context' import { hospexCommunitySchema } from './hospexCommunity.schema' +import { hospexCommunityContext } from './hospexCommunity.context' import { HospexCommunity, HospexGroup } from './hospexCommunity.typings' /** diff --git a/src/ldo/hospexCommunity.typings.ts b/src/ldo/hospexCommunity.typings.ts index 25e76ae8..843d339b 100644 --- a/src/ldo/hospexCommunity.typings.ts +++ b/src/ldo/hospexCommunity.typings.ts @@ -38,6 +38,9 @@ export interface HospexCommunity { homepage?: { '@id': string } + inbox?: { + '@id': string + } hasUsergroup: HospexGroup[] } diff --git a/src/ldo/hospexProfile.schema.ts b/src/ldo/hospexProfile.schema.ts index c516fa47..02a6b700 100644 --- a/src/ldo/hospexProfile.schema.ts +++ b/src/ldo/hospexProfile.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/hospexProfile.shapeTypes.ts b/src/ldo/hospexProfile.shapeTypes.ts index 459b461e..4fb36db0 100644 --- a/src/ldo/hospexProfile.shapeTypes.ts +++ b/src/ldo/hospexProfile.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { hospexProfileContext } from './hospexProfile.context' import { hospexProfileSchema } from './hospexProfile.schema' +import { hospexProfileContext } from './hospexProfile.context' import { HospexProfile } from './hospexProfile.typings' /** diff --git a/src/ldo/longChat.schema.ts b/src/ldo/longChat.schema.ts index e48a0fd6..d2eee9e1 100644 --- a/src/ldo/longChat.schema.ts +++ b/src/ldo/longChat.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/longChat.shapeTypes.ts b/src/ldo/longChat.shapeTypes.ts index acb46c8a..2516390c 100644 --- a/src/ldo/longChat.shapeTypes.ts +++ b/src/ldo/longChat.shapeTypes.ts @@ -1,11 +1,11 @@ import { ShapeType } from '@ldo/ldo' -import { longChatContext } from './longChat.context' import { longChatSchema } from './longChat.schema' +import { longChatContext } from './longChat.context' import { + ChatShape, + ChatParticipationShape, ChatMessageListShape, ChatMessageShape, - ChatParticipationShape, - ChatShape, } from './longChat.typings' /** diff --git a/src/ldo/oidc.schema.ts b/src/ldo/oidc.schema.ts index a2267022..91114b3b 100644 --- a/src/ldo/oidc.schema.ts +++ b/src/ldo/oidc.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/oidc.shapeTypes.ts b/src/ldo/oidc.shapeTypes.ts index 9dc3ae14..a98e4cc7 100644 --- a/src/ldo/oidc.shapeTypes.ts +++ b/src/ldo/oidc.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { oidcContext } from './oidc.context' import { oidcSchema } from './oidc.schema' +import { oidcContext } from './oidc.context' import { OidcIssuer } from './oidc.typings' /** diff --git a/src/ldo/publicTypeIndex.schema.ts b/src/ldo/publicTypeIndex.schema.ts index 04d95f6b..56694914 100644 --- a/src/ldo/publicTypeIndex.schema.ts +++ b/src/ldo/publicTypeIndex.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/publicTypeIndex.shapeTypes.ts b/src/ldo/publicTypeIndex.shapeTypes.ts index f49af23c..8e3ae411 100644 --- a/src/ldo/publicTypeIndex.shapeTypes.ts +++ b/src/ldo/publicTypeIndex.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { publicTypeIndexContext } from './publicTypeIndex.context' import { publicTypeIndexSchema } from './publicTypeIndex.schema' +import { publicTypeIndexContext } from './publicTypeIndex.context' import { PublicTypeIndex, TypeRegistration } from './publicTypeIndex.typings' /** diff --git a/src/ldo/solidProfile.schema.ts b/src/ldo/solidProfile.schema.ts index a8060218..03d63110 100644 --- a/src/ldo/solidProfile.schema.ts +++ b/src/ldo/solidProfile.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/solidProfile.shapeTypes.ts b/src/ldo/solidProfile.shapeTypes.ts index a690ca80..fc14458a 100644 --- a/src/ldo/solidProfile.shapeTypes.ts +++ b/src/ldo/solidProfile.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { solidProfileContext } from './solidProfile.context' import { solidProfileSchema } from './solidProfile.schema' +import { solidProfileContext } from './solidProfile.context' import { SolidProfile } from './solidProfile.typings' /** diff --git a/src/ldo/wac.schema.ts b/src/ldo/wac.schema.ts index d7176a19..3a7e0c9c 100644 --- a/src/ldo/wac.schema.ts +++ b/src/ldo/wac.schema.ts @@ -1,4 +1,4 @@ -import type { Schema } from 'shexj' +import { Schema } from 'shexj' /** * ============================================================================= diff --git a/src/ldo/wac.shapeTypes.ts b/src/ldo/wac.shapeTypes.ts index 71134a65..2dbbba55 100644 --- a/src/ldo/wac.shapeTypes.ts +++ b/src/ldo/wac.shapeTypes.ts @@ -1,6 +1,6 @@ import { ShapeType } from '@ldo/ldo' -import { wacContext } from './wac.context' import { wacSchema } from './wac.schema' +import { wacContext } from './wac.context' import { Authorization } from './wac.typings' /** diff --git a/src/pages/HospexSetup/HospexSetup.tsx b/src/pages/HospexSetup/HospexSetup.tsx index 0ca10bd6..9e3d5019 100644 --- a/src/pages/HospexSetup/HospexSetup.tsx +++ b/src/pages/HospexSetup/HospexSetup.tsx @@ -2,7 +2,7 @@ import { Button } from '@/components' import { Loading } from '@/components/Loading/Loading.tsx' import { useConfig } from '@/config/hooks' import { useReadCommunity } from '@/hooks/data/useCommunity' -import { useJoinGroup } from '@/hooks/data/useJoinGroup' +import { useJoinCommunity, useJoinGroupLegacy } from '@/hooks/data/useJoinGroup' import { SetupSettings, SetupTask, @@ -57,7 +57,8 @@ export const HospexSetup = ({ const setupHospex = useSetupHospex() const storage = useStorage(auth.webId ?? '') const community = useReadCommunity(communityId) - const joinGroup = useJoinGroup() + const joinGroupLegacy = useJoinGroupLegacy() + const joinCommunity = useJoinCommunity() const [isSaving, setIsSaving] = useState(false) const [email, setEmail] = useState('') const [selectedHospexDocument, setSelectedHospexDocument] = useState('') @@ -116,10 +117,18 @@ export const HospexSetup = ({ onNotificationsInitialized() } if (!isMember) - await joinGroup({ - person: auth.webId as URI, - group: community.groups[0], - }) + if (community.inbox) + await joinCommunity({ + actor: auth.webId!, + object: community.community, + type: 'Join', + inbox: community.inbox, + }) + else + await joinGroupLegacy({ + person: auth.webId as URI, + group: community.groups[0], + }) setIsSaving(false) } diff --git a/src/shapes/hospexCommunity.shex b/src/shapes/hospexCommunity.shex index b2263f9e..e1931b75 100644 --- a/src/shapes/hospexCommunity.shex +++ b/src/shapes/hospexCommunity.shex @@ -1,6 +1,7 @@ PREFIX ex: PREFIX foaf: PREFIX hospex: +PREFIX ldp: PREFIX rdf: PREFIX rdfs: PREFIX sioc: @@ -17,6 +18,7 @@ ex:HospexCommunity EXTRA a { foaf:logo IRI {0,2} // rdfs:comment "Logo of the community. If two are specified, the second one may be used for highlight of the first one"; foaf:homepage IRI ?; + ldp:inbox IRI ?; sioc:has_usergroup @ex:HospexGroup + ; }