From 6d8ff2515fb5863dc2bf82df82c070cbcac0bcc1 Mon Sep 17 00:00:00 2001 From: Ailin Luca Date: Sat, 16 Sep 2023 12:52:54 -0400 Subject: [PATCH] Attempt to do access control --- package-lock.json | 2 + packages/demo-react/src/Layout.tsx | 4 +- .../src/dashboard/BuildMainContainer.tsx | 59 ++++++++ .../src/dashboard/BuildRootContainer.tsx | 37 ----- .../demo-react/src/dashboard/Dashboard.tsx | 8 +- packages/solid/package.json | 1 + packages/solid/src/ResourceStore.ts | 7 +- .../requester/requestResults/AccessRule.ts | 51 +++++++ .../requester/requestResults/ErrorResult.ts | 33 ++++- .../requester/requests/checkRootContainer.ts | 4 +- .../requester/requests/createDataResource.ts | 14 +- .../src/requester/requests/getAccessRules.ts | 21 +++ .../src/requester/requests/requestParams.ts | 2 + .../src/requester/requests/setAccessRules.ts | 80 +++++++++++ .../src/requester/requests/uploadResource.ts | 4 +- packages/solid/src/resource/Container.ts | 128 ++++++++++++++++-- packages/solid/src/resource/Leaf.ts | 43 ++++-- packages/solid/src/resource/Resource.ts | 74 ++++------ packages/solid/src/util/rdfUtils.ts | 16 ++- 19 files changed, 459 insertions(+), 129 deletions(-) create mode 100644 packages/demo-react/src/dashboard/BuildMainContainer.tsx delete mode 100644 packages/demo-react/src/dashboard/BuildRootContainer.tsx create mode 100644 packages/solid/src/requester/requestResults/AccessRule.ts create mode 100644 packages/solid/src/requester/requests/getAccessRules.ts create mode 100644 packages/solid/src/requester/requests/setAccessRules.ts diff --git a/package-lock.json b/package-lock.json index f81b6d1..5cf16ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36194,6 +36194,7 @@ "version": "0.0.0", "license": "MIT", "dependencies": { + "@inrupt/solid-client": "^1.30.0", "@ldo/dataset": "^0.0.0", "@ldo/ldo": "^0.0.0", "@ldo/rdf-utils": "^0.0.0", @@ -46960,6 +46961,7 @@ "@ldo/solid": { "version": "file:packages/solid", "requires": { + "@inrupt/solid-client": "*", "@inrupt/solid-client-authn-core": "^1.17.1", "@ldo/cli": "^0.0.0", "@ldo/dataset": "^0.0.0", diff --git a/packages/demo-react/src/Layout.tsx b/packages/demo-react/src/Layout.tsx index 221bd96..109bddb 100644 --- a/packages/demo-react/src/Layout.tsx +++ b/packages/demo-react/src/Layout.tsx @@ -4,12 +4,12 @@ import type { FunctionComponent } from "react"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { Dashboard } from "./dashboard/Dashboard"; import { Media } from "./media/Media"; -import { BuildRootContainer } from "./dashboard/BuildRootContainer"; +import { BuildMainContainer } from "./dashboard/BuildMainContainer"; const router = createBrowserRouter([ { path: "/", - element: , + element: , }, { path: "/media/:uri", diff --git a/packages/demo-react/src/dashboard/BuildMainContainer.tsx b/packages/demo-react/src/dashboard/BuildMainContainer.tsx new file mode 100644 index 0000000..d9c29de --- /dev/null +++ b/packages/demo-react/src/dashboard/BuildMainContainer.tsx @@ -0,0 +1,59 @@ +import React, { useState, useEffect } from "react"; +import type { FunctionComponent } from "react"; +import type { Container, LeafUri } from "@ldo/solid"; +import { useSolidAuth, useLdo } from "@ldo/solid-react"; + +export interface BuildMainContainerChildProps { + mainContainer: Container; +} + +export const BuildMainContainer: FunctionComponent<{ + child: FunctionComponent; +}> = ({ child }) => { + const Child = child; + const [mainContainer, setMainContainer] = useState(); + const { session } = useSolidAuth(); + const { getResource } = useLdo(); + + useEffect(() => { + if (session.webId) { + const webIdResource = getResource(session.webId as LeafUri); + webIdResource.getRootContainer().then(async (rootContainer) => { + if (rootContainer.type === "error") { + alert(rootContainer.message); + return; + } + const mainContainer = + await rootContainer.createChildIfAbsent("demo-react/"); + if (mainContainer.type === "error") { + alert(mainContainer.message); + return; + } + setMainContainer(mainContainer); + mainContainer.setAccessRules({ + public: { + read: true, + write: false, + append: false, + control: false, + }, + agent: { + [session.webId!]: { + read: true, + write: true, + append: true, + control: true, + }, + }, + }); + }); + } + }, [session.webId]); + + if (!session.webId || !mainContainer) { + // Return blank screen + return

Loading

; + } + + return ; +}; diff --git a/packages/demo-react/src/dashboard/BuildRootContainer.tsx b/packages/demo-react/src/dashboard/BuildRootContainer.tsx deleted file mode 100644 index c4419fa..0000000 --- a/packages/demo-react/src/dashboard/BuildRootContainer.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useState, useEffect } from "react"; -import type { FunctionComponent } from "react"; -import type { Container, LeafUri } from "@ldo/solid"; -import { useSolidAuth, useLdo } from "@ldo/solid-react"; - -export interface BuildRootContainerChildProps { - rootContainer: Container; -} - -export const BuildRootContainer: FunctionComponent<{ - child: FunctionComponent; -}> = ({ child }) => { - const Child = child; - const [rootContainer, setRootContainer] = useState(); - const { session } = useSolidAuth(); - const { getResource } = useLdo(); - - useEffect(() => { - if (session.webId) { - const webIdResource = getResource(session.webId as LeafUri); - webIdResource.getRootContainer().then((rootContainer) => { - if (rootContainer.type !== "error") { - setRootContainer(rootContainer); - } else { - alert(rootContainer.message); - } - }); - } - }, [session.webId]); - - if (!session.webId || !rootContainer) { - // Return blank screen - return

Loading

; - } - - return ; -}; diff --git a/packages/demo-react/src/dashboard/Dashboard.tsx b/packages/demo-react/src/dashboard/Dashboard.tsx index ca2db02..fe9f6af 100644 --- a/packages/demo-react/src/dashboard/Dashboard.tsx +++ b/packages/demo-react/src/dashboard/Dashboard.tsx @@ -1,11 +1,11 @@ import React from "react"; import type { FunctionComponent } from "react"; -import type { BuildRootContainerChildProps } from "./BuildRootContainer"; +import type { BuildMainContainerChildProps } from "./BuildMainContainer"; -export const Dashboard: FunctionComponent = ({ - rootContainer, +export const Dashboard: FunctionComponent = ({ + mainContainer, }) => { - return

{rootContainer.uri}

; + return

{mainContainer.uri}

; // return ( //
//
diff --git a/packages/solid/package.json b/packages/solid/package.json index 4f4a767..31a268f 100644 --- a/packages/solid/package.json +++ b/packages/solid/package.json @@ -37,6 +37,7 @@ "typed-emitter": "^2.1.0" }, "dependencies": { + "@inrupt/solid-client": "^1.30.0", "@ldo/dataset": "^0.0.0", "@ldo/ldo": "^0.0.0", "@ldo/rdf-utils": "^0.0.0", diff --git a/packages/solid/src/ResourceStore.ts b/packages/solid/src/ResourceStore.ts index 95ba596..b3fba0f 100644 --- a/packages/solid/src/ResourceStore.ts +++ b/packages/solid/src/ResourceStore.ts @@ -1,6 +1,5 @@ import { Container } from "./resource/Container"; import { Leaf } from "./resource/Leaf"; -import type { Resource } from "./resource/Resource"; import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext"; import type { ContainerUri, LeafUri } from "./util/uriTypes"; import { isContainerUri } from "./util/uriTypes"; @@ -10,7 +9,7 @@ export interface ResourceGetterOptions { } export class ResourceStore { - protected resourceMap: Map; + protected resourceMap: Map; protected context: SolidLdoDatasetContext; constructor(context: SolidLdoDatasetContext) { @@ -20,8 +19,8 @@ export class ResourceStore { get(uri: ContainerUri, options?: ResourceGetterOptions): Container; get(uri: LeafUri, options?: ResourceGetterOptions): Leaf; - get(uri: string, options?: ResourceGetterOptions): Resource; - get(uri: string, options?: ResourceGetterOptions): Resource { + get(uri: string, options?: ResourceGetterOptions): Leaf | Container; + get(uri: string, options?: ResourceGetterOptions): Leaf | Container { // Normalize URI by removing hash const url = new URL(uri); url.hash = ""; diff --git a/packages/solid/src/requester/requestResults/AccessRule.ts b/packages/solid/src/requester/requestResults/AccessRule.ts new file mode 100644 index 0000000..06290b4 --- /dev/null +++ b/packages/solid/src/requester/requestResults/AccessRule.ts @@ -0,0 +1,51 @@ +import type { Access } from "@inrupt/solid-client"; +import { ErrorResult } from "./ErrorResult"; +import { RequesterResult } from "./RequesterResult"; + +export interface AccessRule { + public?: Access; + agent?: Record; +} + +export class AccessRuleChangeResult + extends RequesterResult + implements AccessRule +{ + type = "accessRuleChange" as const; + readonly public?: Access; + readonly agent?: Record; + + constructor( + uri: string, + publicRules?: Access, + agentRules?: Record, + ) { + super(uri); + this.public = publicRules; + this.agent = agentRules; + } +} + +export class AccessRuleResult extends RequesterResult implements AccessRule { + type = "accessRule" as const; + readonly public: Access; + readonly agent: Record; + + constructor( + uri: string, + publicRules: Access, + agentRules: Record, + ) { + super(uri); + this.public = publicRules; + this.agent = agentRules; + } +} + +export class AccessRuleFetchError extends ErrorResult { + readonly errorType = "accessRuleFetch" as const; + + constructor(uri: string, message?: string) { + super(uri, message || `Cannot get access rules for ${uri}.`); + } +} diff --git a/packages/solid/src/requester/requestResults/ErrorResult.ts b/packages/solid/src/requester/requestResults/ErrorResult.ts index 8cc2e2e..79aa760 100644 --- a/packages/solid/src/requester/requestResults/ErrorResult.ts +++ b/packages/solid/src/requester/requestResults/ErrorResult.ts @@ -11,7 +11,7 @@ export abstract class ErrorResult extends Error { export class UnexpectedError extends ErrorResult { error: Error; - readonly errorType = "unexpected"; + readonly errorType = "unexpected" as const; constructor(uri: string, error: Error) { super(uri, error.message); @@ -31,3 +31,34 @@ export class UnexpectedError extends ErrorResult { } } } + +export class AggregateError extends ErrorResult { + readonly errorType = "aggregate" as const; + readonly errors: ErrorType[]; + + constructor( + uri: string, + errors: (ErrorType | AggregateError)[], + message?: string, + ) { + const allErrors: ErrorType[] = []; + errors.forEach((error) => { + if (error instanceof AggregateError) { + error.errors.forEach((subError) => { + allErrors.push(subError); + }); + } else { + allErrors.push(error); + } + }); + super( + uri, + message || + `Encountered multiple errors:${allErrors.reduce( + (agg, cur) => `${agg}\n${cur}`, + "", + )}`, + ); + this.errors = allErrors; + } +} diff --git a/packages/solid/src/requester/requests/checkRootContainer.ts b/packages/solid/src/requester/requests/checkRootContainer.ts index f71fb67..0f38e3a 100644 --- a/packages/solid/src/requester/requests/checkRootContainer.ts +++ b/packages/solid/src/requester/requests/checkRootContainer.ts @@ -3,7 +3,7 @@ import { type HttpErrorResultType, } from "../requestResults/HttpErrorResult"; import { UnexpectedError } from "../requestResults/ErrorResult"; -import type { RequestParams } from "./requestParams"; +import type { SimpleRequestParams } from "./requestParams"; import { parse as parseLinkHeader } from "http-link-header"; export type CheckRootResult = boolean | CheckRootResultError; @@ -12,7 +12,7 @@ export type CheckRootResultError = HttpErrorResultType | UnexpectedError; export async function checkRootContainer({ uri, fetch, -}: Omit): Promise { +}: SimpleRequestParams): Promise { try { // Fetch options to determine the document type const response = await fetch(uri, { method: "HEAD" }); diff --git a/packages/solid/src/requester/requests/createDataResource.ts b/packages/solid/src/requester/requests/createDataResource.ts index 8df298c..2932d8b 100644 --- a/packages/solid/src/requester/requests/createDataResource.ts +++ b/packages/solid/src/requester/requests/createDataResource.ts @@ -3,6 +3,7 @@ import { getParentUri, getSlug, } from "../../util/rdfUtils"; +import { isContainerUri } from "../../util/uriTypes"; import type { BinaryResult } from "../requestResults/BinaryResult"; import type { TurtleFormattingError } from "../requestResults/DataResult"; import { DataResult } from "../requestResults/DataResult"; @@ -57,12 +58,17 @@ export async function createDataResource( } // Create the document const parentUri = getParentUri(uri)!; + console.log("This is the URI", uri); + const headers: HeadersInit = { + "content-type": "text/turtle", + slug: getSlug(uri), + }; + if (isContainerUri(uri)) { + headers.link = '; rel="type"'; + } const response = await fetch(parentUri, { method: "post", - headers: { - "content-type": "text/turtle", - slug: getSlug(uri), - }, + headers, }); const httpError = HttpErrorResult.checkResponse(uri, response); diff --git a/packages/solid/src/requester/requests/getAccessRules.ts b/packages/solid/src/requester/requests/getAccessRules.ts new file mode 100644 index 0000000..f62ee13 --- /dev/null +++ b/packages/solid/src/requester/requests/getAccessRules.ts @@ -0,0 +1,21 @@ +import { universalAccess } from "@inrupt/solid-client"; +import type { + AccessRuleFetchError, + AccessRuleResult, +} from "../requestResults/AccessRule"; +import type { SimpleRequestParams } from "./requestParams"; + +export async function getAccessRules({ + uri, + fetch, +}: SimpleRequestParams): Promise { + throw new Error("Not Implemented"); + // const [publicAccess, agentAccess] = await Promise.all([ + // universalAccess.getPublicAccess(uri, { fetch }), + // universalAccess.getAgentAccessAll(uri, { fetch }), + // ]); + // if (agentAccess === null || publicAccess === null) { + // return new AccessRuleFetchError(uri); + // } + // return new AccessRuleResult(uri, publicAccess, agentAccess); +} diff --git a/packages/solid/src/requester/requests/requestParams.ts b/packages/solid/src/requester/requests/requestParams.ts index 5c47641..33f8eda 100644 --- a/packages/solid/src/requester/requests/requestParams.ts +++ b/packages/solid/src/requester/requests/requestParams.ts @@ -6,3 +6,5 @@ export interface RequestParams { fetch: typeof fetch; transaction: TransactionalDataset; } + +export type SimpleRequestParams = Omit; diff --git a/packages/solid/src/requester/requests/setAccessRules.ts b/packages/solid/src/requester/requests/setAccessRules.ts new file mode 100644 index 0000000..9b5baa7 --- /dev/null +++ b/packages/solid/src/requester/requests/setAccessRules.ts @@ -0,0 +1,80 @@ +import type { AclDataset, WithChangeLog } from "@inrupt/solid-client"; +import { getAgentAccessAll } from "@inrupt/solid-client"; +import { getPublicAccess } from "@inrupt/solid-client"; +import { + getSolidDatasetWithAcl, + hasResourceAcl, + hasFallbackAcl, + hasAccessibleAcl, + createAclFromFallbackAcl, + getResourceAcl, + setAgentResourceAccess, + saveAclFor, + setPublicDefaultAccess, + setPublicResourceAccess, + setAgentDefaultAccess, +} from "@inrupt/solid-client"; +import { isContainerUri } from "../../util/uriTypes"; +import type { AccessRule } from "../requestResults/AccessRule"; +import { AccessRuleChangeResult } from "../requestResults/AccessRule"; +import { AccessRuleFetchError } from "../requestResults/AccessRule"; +import type { SimpleRequestParams } from "./requestParams"; + +export async function setAccessRules( + { uri, fetch }: SimpleRequestParams, + newAccessRules: AccessRule, +): Promise { + console.warn("Access Control is stil underdeveloped. Use with caution."); + const isContainer = isContainerUri(uri); + + // Code Copied from https://docs.inrupt.com/developer-tools/javascript/client-libraries/tutorial/manage-wac/ + // Fetch the SolidDataset and its associated ACLs, if available: + const myDatasetWithAcl = await getSolidDatasetWithAcl(uri, { fetch }); + + // Obtain the SolidDataset's own ACL, if available, + // or initialise a new one, if possible: + let resourceAcl; + if (!hasResourceAcl(myDatasetWithAcl)) { + if (!hasAccessibleAcl(myDatasetWithAcl)) { + return new AccessRuleFetchError( + uri, + "The current user does not have permission to change access rights to this Resource.", + ); + } + if (!hasFallbackAcl(myDatasetWithAcl)) { + return new AccessRuleFetchError( + "The current user does not have permission to see who currently has access to this Resource.", + ); + } + resourceAcl = createAclFromFallbackAcl(myDatasetWithAcl); + } else { + resourceAcl = getResourceAcl(myDatasetWithAcl); + } + + // Give someone Control access to the given Resource: + + let updatedAcl: AclDataset & WithChangeLog = resourceAcl; + if (newAccessRules.public) { + if (isContainer) { + updatedAcl = setPublicDefaultAccess(updatedAcl, newAccessRules.public); + } else { + updatedAcl = setPublicResourceAccess(updatedAcl, newAccessRules.public); + } + } + if (newAccessRules.agent) { + const setAgentAccess = isContainer + ? setAgentDefaultAccess + : setAgentResourceAccess; + Object.entries(newAccessRules.agent).forEach(([webId, rules]) => { + updatedAcl = setAgentAccess(updatedAcl, webId, rules); + }); + } + + // Now save the ACL: + await saveAclFor(myDatasetWithAcl, updatedAcl, { fetch }); + return new AccessRuleChangeResult( + uri, + getPublicAccess(myDatasetWithAcl) || undefined, + getAgentAccessAll(myDatasetWithAcl) || undefined, + ); +} diff --git a/packages/solid/src/requester/requests/uploadResource.ts b/packages/solid/src/requester/requests/uploadResource.ts index 50fd4c3..b52ad84 100644 --- a/packages/solid/src/requester/requests/uploadResource.ts +++ b/packages/solid/src/requester/requests/uploadResource.ts @@ -44,13 +44,13 @@ export function uploadResource( blob: Blob, mimeType: string, overwrite?: boolean, -): Promise; +): Promise; export async function uploadResource( params: RequestParams, blob: Blob, mimeType: string, overwrite?: boolean, -): Promise { +): Promise { const { uri, transaction, fetch } = params; try { if (overwrite) { diff --git a/packages/solid/src/resource/Container.ts b/packages/solid/src/resource/Container.ts index ab413b6..0088214 100644 --- a/packages/solid/src/resource/Container.ts +++ b/packages/solid/src/resource/Container.ts @@ -1,9 +1,20 @@ +import { namedNode } from "@rdfjs/data-model"; import { ContainerRequester } from "../requester/ContainerRequester"; -import { UnexpectedError } from "../requester/requestResults/ErrorResult"; +import { + AggregateError, + UnexpectedError, +} from "../requester/requestResults/ErrorResult"; import type { CheckRootResultError } from "../requester/requests/checkRootContainer"; +import type { + CreateResultErrors, + CreateResultWithoutOverwriteErrors, +} from "../requester/requests/createDataResource"; +import type { DeleteResultError } from "../requester/requests/deleteResource"; +import type { ReadResultError } from "../requester/requests/readResource"; import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; -import { getParentUri } from "../util/rdfUtils"; -import type { ContainerUri } from "../util/uriTypes"; +import { getParentUri, ldpContains } from "../util/rdfUtils"; +import type { ContainerUri, LeafUri } from "../util/uriTypes"; +import type { Leaf } from "./Leaf"; import { Resource } from "./Resource"; export class Container extends Resource { @@ -20,11 +31,9 @@ export class Container extends Resource { return this.rootContainer; } - getParentContainer(): Promise { - throw new Error("Method not implemented."); - } - - async getRootContainer(): Promise { + private async checkIfIsRootContainer(): Promise< + CheckRootResultError | undefined + > { if (this.rootContainer === undefined) { const rootContainerResult = await this.requester.isRootContainer(); if (typeof rootContainerResult !== "boolean") { @@ -32,6 +41,27 @@ export class Container extends Resource { } this.rootContainer = rootContainerResult; } + } + + async getParentContainer(): Promise< + Container | CheckRootResultError | undefined + > { + const checkResult = await this.checkIfIsRootContainer(); + if (checkResult) return checkResult; + if (this.rootContainer) return undefined; + const parentUri = getParentUri(this.uri); + if (!parentUri) { + return new UnexpectedError( + this.uri, + new Error("Resource does not have a root container"), + ); + } + return this.context.resourceStore.get(parentUri); + } + + async getRootContainer(): Promise { + const checkResult = await this.checkIfIsRootContainer(); + if (checkResult) return checkResult; if (this.rootContainer) { return this; } @@ -42,10 +72,86 @@ export class Container extends Resource { new Error("Resource does not have a root container"), ); } - return this.context.resourceStore.get(parentUri); + return this.context.resourceStore.get(parentUri).getRootContainer(); + } + + children(): (Leaf | Container)[] { + const childQuads = this.context.solidLdoDataset.match( + namedNode(this.uri), + ldpContains, + null, + namedNode(this.uri), + ); + return childQuads.toArray().map((childQuad) => { + return this.context.resourceStore.get(childQuad.object.value); + }); + } + + createChildAndOverwrite( + slug: ContainerUri, + ): Promise; + createChildAndOverwrite(slug: LeafUri): Promise; + createChildAndOverwrite(slug: string): Promise; + createChildAndOverwrite( + slug: string, + ): Promise { + const resource = this.context.resourceStore.get(`${this.uri}${slug}`); + return resource.createAndOverwrite(); + } + + createChildIfAbsent( + slug: ContainerUri, + ): Promise; + createChildIfAbsent( + slug: LeafUri, + ): Promise; + createChildIfAbsent( + slug: string, + ): Promise; + createChildIfAbsent( + slug: string, + ): Promise { + const resource = this.context.resourceStore.get(`${this.uri}${slug}`); + return resource.createIfAbsent(); + } + + async clear(): Promise< + AggregateError | this + > { + const readResult = await this.read(); + if (readResult.type === "error") + return new AggregateError(this.uri, [readResult]); + const errors = ( + await Promise.all( + this.children().map(async (child) => { + const deleteError = await child.delete(); + if (deleteError.type === "error") return deleteError; + }), + ) + ) + .flat() + .filter( + ( + value, + ): value is + | DeleteResultError + | AggregateError => !!value, + ); + if (errors.length > 0) { + return new AggregateError(this.uri, errors); + } + return this; } - getMimeType(): string { - throw new Error("Method not implemented."); + async delete(): Promise< + | this + | AggregateError + | DeleteResultError + > { + const clearResult = await this.clear(); + if (clearResult.type === "error") return clearResult; + return this.parseResult(await this.requester.delete()) as + | this + | DeleteResultError; } } diff --git a/packages/solid/src/resource/Leaf.ts b/packages/solid/src/resource/Leaf.ts index 114337e..d9c062b 100644 --- a/packages/solid/src/resource/Leaf.ts +++ b/packages/solid/src/resource/Leaf.ts @@ -1,7 +1,12 @@ import type { DatasetChanges } from "@ldo/rdf-utils"; import { LeafRequester } from "../requester/LeafRequester"; import type { Requester } from "../requester/Requester"; +import type { AbsentResult } from "../requester/requestResults/AbsentResult"; +import type { BinaryResult } from "../requester/requestResults/BinaryResult"; +import type { DataResult } from "../requester/requestResults/DataResult"; +import type { ErrorResult } from "../requester/requestResults/ErrorResult"; import type { CheckRootResultError } from "../requester/requests/checkRootContainer"; +import type { DeleteResultError } from "../requester/requests/deleteResource"; import type { UpdateResultError } from "../requester/requests/updateDataResource"; import type { UploadResultError, @@ -17,21 +22,38 @@ export class Leaf extends Resource { protected requester: Requester; readonly type = "leaf" as const; + protected binaryData: { data: Blob; mimeType: string } | undefined; + constructor(uri: LeafUri, context: SolidLdoDatasetContext) { super(uri, context); this.requester = new LeafRequester(uri, context); } - getParentContainer(): Promise { - throw new Error("Method not implemented."); + protected parseResult( + result: AbsentResult | BinaryResult | DataResult | PossibleErrors, + ): this | PossibleErrors { + if (result.type === "binary") { + this.binaryData = { + data: result.blob, + mimeType: result.mimeType, + }; + } else { + delete this.binaryData; + } + return super.parseResult(result); + } + + getParentContainer(): Container { + const parentUri = getParentUri(this.uri)!; + return this.context.resourceStore.get(parentUri); } getRootContainer(): Promise { const parentUri = getParentUri(this.uri)!; const parent = this.context.resourceStore.get(parentUri); return parent.getRootContainer(); } - getMimeType(): string { - throw new Error("Method not implemented."); + getMimeType(): string | undefined { + return this.binaryData?.mimeType; } isBinary(): boolean | undefined { if (!this.didInitialFetch) { @@ -50,21 +72,22 @@ export class Leaf extends Resource { blob: Blob, mimeType: string, ): Promise { - return this.parseResult(await this.requester.upload(blob, mimeType)) as - | this - | UploadResultError; + return this.parseResult(await this.requester.upload(blob, mimeType, true)); } async uploadIfAbsent( blob: Blob, mimeType: string, ): Promise { - return this.parseResult( - await this.requester.upload(blob, mimeType, true), - ) as this | UploadResultWithoutOverwriteError; + return this.parseResult(await this.requester.upload(blob, mimeType)); } update(_changes: DatasetChanges): Promise { throw new Error("Method not implemented"); } + + // Delete Method + async delete(): Promise { + return this.parseResult(await this.requester.delete()); + } } diff --git a/packages/solid/src/resource/Resource.ts b/packages/solid/src/resource/Resource.ts index 1ff8c74..9b72b1a 100644 --- a/packages/solid/src/resource/Resource.ts +++ b/packages/solid/src/resource/Resource.ts @@ -2,19 +2,23 @@ import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext"; import type { AbsentResult } from "../requester/requestResults/AbsentResult"; import type { BinaryResult } from "../requester/requestResults/BinaryResult"; import type { DataResult } from "../requester/requestResults/DataResult"; -import { - UnexpectedError, - type ErrorResult, -} from "../requester/requestResults/ErrorResult"; +import { type ErrorResult } from "../requester/requestResults/ErrorResult"; import type { CreateResultErrors, CreateResultWithoutOverwriteErrors, } from "../requester/requests/createDataResource"; -import type { DeleteResultError } from "../requester/requests/deleteResource"; import type { ReadResultError } from "../requester/requests/readResource"; import type { Container } from "./Container"; import type { Requester } from "../requester/Requester"; import type { CheckRootResultError } from "../requester/requests/checkRootContainer"; +import type { + AccessRule, + AccessRuleChangeResult, + AccessRuleFetchError, + AccessRuleResult, +} from "../requester/requestResults/AccessRule"; +import { getAccessRules } from "../requester/requests/getAccessRules"; +import { setAccessRules } from "../requester/requests/setAccessRules"; export abstract class Resource { // All intance variables @@ -24,7 +28,6 @@ export abstract class Resource { protected abstract readonly requester: Requester; protected didInitialFetch: boolean = false; protected absent: boolean | undefined; - protected binaryData: { data: Blob; mimeType: string } | undefined; constructor(uri: string, context: SolidLdoDatasetContext) { this.uri = uri; @@ -68,43 +71,26 @@ export abstract class Resource { return this.absent === undefined ? undefined : !this.absent; } - protected parseResult( - result: AbsentResult | BinaryResult | DataResult | ErrorResult, - ) { + protected parseResult( + result: AbsentResult | BinaryResult | DataResult | PossibleErrors, + ): this | PossibleErrors { switch (result.type) { case "error": return result; case "absent": this.didInitialFetch = true; this.absent = true; - delete this.binaryData; - return this; - case "data": - this.didInitialFetch = true; - this.absent = false; - delete this.binaryData; return this; - case "binary": + default: this.didInitialFetch = true; this.absent = false; - this.binaryData = { - data: result.blob, - mimeType: result.mimeType, - }; return this; - default: - return new UnexpectedError( - this.uri, - new Error("Unknown request result"), - ); } } // Read Methods async read(): Promise { - return this.parseResult(await this.requester.read()) as - | this - | ReadResultError; + return this.parseResult(await this.requester.read()); } async readIfUnfetched(): Promise { if (this.didInitialFetch) { @@ -115,30 +101,26 @@ export abstract class Resource { // Create Methods async createAndOverwrite(): Promise { - return this.parseResult(await this.requester.createDataResource(true)) as - | this - | CreateResultErrors; + return this.parseResult(await this.requester.createDataResource(true)); } async createIfAbsent(): Promise { - return this.parseResult(await this.requester.createDataResource()) as - | this - | CreateResultWithoutOverwriteErrors; - } - - // Delete Method - async delete(): Promise { - return this.parseResult(await this.requester.delete()) as - | Resource - | DeleteResultError; + return this.parseResult(await this.requester.createDataResource()); } // Parent Container Methods -- Remember to change for Container - abstract getParentContainer(): Promise; abstract getRootContainer(): Promise; - // Exclusing Methods ========================================================= - // Data Methods (Data Leaf Only) - // Binary Methods (Binary Only) - abstract getMimeType(): string; + async getAccessRules(): Promise { + return getAccessRules({ uri: this.uri, fetch: this.context.fetch }); + } + + async setAccessRules( + newAccessRules: AccessRule, + ): Promise { + return setAccessRules( + { uri: this.uri, fetch: this.context.fetch }, + newAccessRules, + ); + } } diff --git a/packages/solid/src/util/rdfUtils.ts b/packages/solid/src/util/rdfUtils.ts index 882e393..75f3cb2 100644 --- a/packages/solid/src/util/rdfUtils.ts +++ b/packages/solid/src/util/rdfUtils.ts @@ -6,11 +6,15 @@ import type { Dataset } from "@rdfjs/types"; import type { ContainerUri } from "./uriTypes"; import { isContainerUri } from "./uriTypes"; -const ldpContains = namedNode("http://www.w3.org/ns/ldp#contains"); -const rdfType = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); -const ldpResource = namedNode("http://www.w3.org/ns/ldp#Resource"); -const ldpContainer = namedNode("http://www.w3.org/ns/ldp#Container"); -const ldpBasicContainer = namedNode("http://www.w3.org/ns/ldp#BasicContainer"); +export const ldpContains = namedNode("http://www.w3.org/ns/ldp#contains"); +export const rdfType = namedNode( + "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", +); +export const ldpResource = namedNode("http://www.w3.org/ns/ldp#Resource"); +export const ldpContainer = namedNode("http://www.w3.org/ns/ldp#Container"); +export const ldpBasicContainer = namedNode( + "http://www.w3.org/ns/ldp#BasicContainer", +); export function getParentUri(uri: string): ContainerUri | undefined { const urlObject = new URL(uri); @@ -32,7 +36,7 @@ export function getParentUri(uri: string): ContainerUri | undefined { export function getSlug(uri: string): string { const urlObject = new URL(uri); const pathItems = urlObject.pathname.split("/"); - return pathItems[pathItems.length - 1]; + return pathItems[pathItems.length - 1] || pathItems[pathItems.length - 2]; } export function deleteResourceRdfFromContainer(