From c771c8edcba6816956f1c0b801fc6f15ff33bea1 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 5 Jun 2024 20:42:05 +0200 Subject: [PATCH 01/15] bookmarks: setup dedicated pipeline for bookmarks/rdflib --- .github/workflows/bookmarks-ci.yml | 3 +- .github/workflows/bookmarks-rdflib-ci-cd.yml | 89 ++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/bookmarks-rdflib-ci-cd.yml diff --git a/.github/workflows/bookmarks-ci.yml b/.github/workflows/bookmarks-ci.yml index a0f3fbbd..dc3f4d48 100644 --- a/.github/workflows/bookmarks-ci.yml +++ b/.github/workflows/bookmarks-ci.yml @@ -3,7 +3,8 @@ run-name: ${{ github.actor }} is running jest on: push: paths: - - bookmarks/** + - bookmarks/soukai/** + - bookmarks/vanilla/** - .github/workflows/bookmarks-ci.yml jobs: test-bookmarks-vanilla: diff --git a/.github/workflows/bookmarks-rdflib-ci-cd.yml b/.github/workflows/bookmarks-rdflib-ci-cd.yml new file mode 100644 index 00000000..cad084a6 --- /dev/null +++ b/.github/workflows/bookmarks-rdflib-ci-cd.yml @@ -0,0 +1,89 @@ +name: Bookmarks rdflib CI/CD + +on: + push: + paths: + - bookmarks/rdflib/** + - .github/workflows/bookmarks-rdflib-ci-cd.yml + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./bookmarks/rdflib/ + strategy: + matrix: + node-version: [ 20 ] + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + + - run: npm ci + - run: npm run lint + - run: npm run build + - run: npm test + - run: npm run test:e2e + - name: Save e2e test data + uses: actions/upload-artifact@v4 + with: + name: e2e-test-data + path: | + bookmarks/rdflib/src/e2e-tests/.test-data/ + retention-days: 1 + - name: Save build + uses: actions/upload-artifact@v4 + with: + name: build + path: | + bookmarks/rdflib/dist/ + bookmarks/rdflib/README.md + bookmarks/rdflib/package.json + retention-days: 1 + + npm-publish: + if: github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + outputs: + prereleaseVersion: ${{ steps.prerelease.outputs.version }} + steps: + - uses: actions/download-artifact@v4 + with: + name: build + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: rlespinasse/github-slug-action@v4.4.1 + - name: prerelease version + run: | + echo "::set-output name=version::$(npm version prerelease --preid ${GITHUB_SHA_SHORT} --no-git-tag-version)" + id: prerelease + - run: echo prerelease version is ${{ steps.prerelease.outputs.version }} + - uses: JS-DevTools/npm-publish@v3 + name: Publish @solid-data-modules/bookmarks-rdflib + with: + token: ${{ secrets.NPM_TOKEN }} + tag: ${{ env.GITHUB_REF_SLUG }} + access: public + + npm-release-latest: + if: github.ref == 'refs/heads/main' + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + name: build + - uses: actions/setup-node@v3 + with: + node-version: lts/* + - uses: JS-DevTools/npm-publish@v3 + name: Release @solid-data-modules/bookmarks-rdflib + with: + token: ${{ secrets.NPM_TOKEN }} + tag: latest + access: public From d365a38814964c2581e274fc9126439697119fa1 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 5 Jun 2024 21:23:03 +0200 Subject: [PATCH 02/15] bookmarks: setup interface for create bookmark and e2e and integration tests --- bookmarks/rdflib/.prettierrc.json | 4 ++ bookmarks/rdflib/jest.config.ts | 26 ++++++++++ bookmarks/rdflib/jest.e2e.config.ts | 28 +++++++++++ bookmarks/rdflib/package-lock.json | 38 +++++++++++++++ bookmarks/rdflib/package.json | 6 +++ .../src/e2e-tests/bookmarks.e2e.spec.ts | 15 ++++++ bookmarks/rdflib/src/e2e-tests/config.json | 38 +++++++++++++++ bookmarks/rdflib/src/e2e-tests/globalSetup.ts | 11 +++++ .../rdflib/src/e2e-tests/globalTeardown.ts | 6 +++ .../rdflib/src/e2e-tests/start-server.ts | 27 +++++++++++ bookmarks/rdflib/src/generate-id.ts | 7 +++ bookmarks/rdflib/src/index.ts | 26 +++++++++- .../src/module/BookmarksModuleRdfLib.ts | 12 ++++- .../create-bookmark.integration.spec.ts | 42 ++++++++++++++++ .../rdflib/src/test-support/expectRequests.ts | 30 ++++++++++++ .../rdflib/src/test-support/mockResponses.ts | 48 +++++++++++++++++++ .../rdflib/src/test-support/setupModule.ts | 11 +++++ 17 files changed, 371 insertions(+), 4 deletions(-) create mode 100644 bookmarks/rdflib/.prettierrc.json create mode 100644 bookmarks/rdflib/jest.config.ts create mode 100644 bookmarks/rdflib/jest.e2e.config.ts create mode 100644 bookmarks/rdflib/src/e2e-tests/bookmarks.e2e.spec.ts create mode 100644 bookmarks/rdflib/src/e2e-tests/config.json create mode 100644 bookmarks/rdflib/src/e2e-tests/globalSetup.ts create mode 100644 bookmarks/rdflib/src/e2e-tests/globalTeardown.ts create mode 100644 bookmarks/rdflib/src/e2e-tests/start-server.ts create mode 100644 bookmarks/rdflib/src/generate-id.ts create mode 100644 bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts create mode 100644 bookmarks/rdflib/src/test-support/expectRequests.ts create mode 100644 bookmarks/rdflib/src/test-support/mockResponses.ts create mode 100644 bookmarks/rdflib/src/test-support/setupModule.ts diff --git a/bookmarks/rdflib/.prettierrc.json b/bookmarks/rdflib/.prettierrc.json new file mode 100644 index 00000000..0e06f57c --- /dev/null +++ b/bookmarks/rdflib/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "proseWrap": "always", + "printWidth": 80 +} \ No newline at end of file diff --git a/bookmarks/rdflib/jest.config.ts b/bookmarks/rdflib/jest.config.ts new file mode 100644 index 00000000..dc4df91f --- /dev/null +++ b/bookmarks/rdflib/jest.config.ts @@ -0,0 +1,26 @@ +import type { Config } from "jest"; + +const config: Config = { + preset: "ts-jest", + testEnvironment: "node", + rootDir: "src", + testPathIgnorePatterns: [".*\\.e2e\\.spec\\.ts"], + detectOpenHandles: true, + forceExit: true, + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + transform: { + // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` + // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` + "^.+\\.ts$": [ + "ts-jest", + { + useESM: true, + }, + ], + }, +}; + +export default config; diff --git a/bookmarks/rdflib/jest.e2e.config.ts b/bookmarks/rdflib/jest.e2e.config.ts new file mode 100644 index 00000000..0ff11a8c --- /dev/null +++ b/bookmarks/rdflib/jest.e2e.config.ts @@ -0,0 +1,28 @@ +import type { Config } from "jest"; + +const config: Config = { + preset: "ts-jest", + testEnvironment: "node", + rootDir: "src/e2e-tests", + testTimeout: 60000, + detectOpenHandles: true, + globalSetup: "/globalSetup.ts", + globalTeardown: "/globalTeardown.ts", + forceExit: true, + extensionsToTreatAsEsm: [".ts"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + transform: { + // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` + // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` + "^.+\\.ts$": [ + "ts-jest", + { + useESM: true, + }, + ], + }, +}; + +export default config; diff --git a/bookmarks/rdflib/package-lock.json b/bookmarks/rdflib/package-lock.json index dbbb6223..58bf170c 100644 --- a/bookmarks/rdflib/package-lock.json +++ b/bookmarks/rdflib/package-lock.json @@ -8,6 +8,9 @@ "name": "@solid-data-modules/bookmarks-rdflib", "version": "0.1.0", "license": "MIT", + "dependencies": { + "short-unique-id": "^5.2.0" + }, "devDependencies": { "@solid/community-server": "^7.0.5", "@types/jest": "^29.5.12", @@ -15,7 +18,9 @@ "eslint": "^8.57.0", "eslint-plugin-require-extensions": "^0.1.3", "jest": "^29.7.0", + "jest-when": "^3.6.0", "mashlib": "^1.8.11", + "prettier": "^3.3.1", "serve": "^14.2.3", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", @@ -9810,6 +9815,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-when": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jest-when/-/jest-when-3.6.0.tgz", + "integrity": "sha512-+cZWTy0ekAJo7M9Om0Scdor1jm3wDiYJWmXE8U22UVnkH54YCXAuaqz3P+up/FdtOg8g4wHOxV7Thd7nKhT6Dg==", + "dev": true, + "peerDependencies": { + "jest": ">= 25" + } + }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -14441,6 +14455,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.1.tgz", + "integrity": "sha512-7CAwy5dRsxs8PHXT3twixW9/OEll8MLE0VRPCJyl7CkS6VHGPSlsVaWTiASPTyGyYRyApxlaWTzwUxVNrhcwDg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", @@ -15803,6 +15832,15 @@ "vscode-textmate": "^8.0.0" } }, + "node_modules/short-unique-id": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.0.tgz", + "integrity": "sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==", + "bin": { + "short-unique-id": "bin/short-unique-id", + "suid": "bin/short-unique-id" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", diff --git a/bookmarks/rdflib/package.json b/bookmarks/rdflib/package.json index 53b6d84b..1e901a83 100644 --- a/bookmarks/rdflib/package.json +++ b/bookmarks/rdflib/package.json @@ -11,6 +11,7 @@ "build:doc": "typedoc src/index.ts --out ../../gh-pages/bookmarks-rdflib --tsconfig ./tsconfig.json", "serve:doc": "serve ../../gh-pages", "test": "jest", + "test:e2e": "jest --config jest.e2e.config.ts", "lint": "eslint ./src/**", "pod": "community-solid-server --config ../dev-server/config/config-mashlib.json --seedConfig ../dev-server/seed.json --rootFilePath ../dev-server/data", "pod:init": "cp -r ../dev-server/initial-data/* ../dev-server/data/", @@ -48,11 +49,16 @@ "eslint": "^8.57.0", "eslint-plugin-require-extensions": "^0.1.3", "jest": "^29.7.0", + "jest-when": "^3.6.0", "mashlib": "^1.8.11", + "prettier": "^3.3.1", "serve": "^14.2.3", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "typedoc": "^0.25.13", "typescript": "^5.4.5" + }, + "dependencies": { + "short-unique-id": "^5.2.0" } } diff --git a/bookmarks/rdflib/src/e2e-tests/bookmarks.e2e.spec.ts b/bookmarks/rdflib/src/e2e-tests/bookmarks.e2e.spec.ts new file mode 100644 index 00000000..c262c342 --- /dev/null +++ b/bookmarks/rdflib/src/e2e-tests/bookmarks.e2e.spec.ts @@ -0,0 +1,15 @@ +import { setupModule } from "../test-support/setupModule.js"; + +describe("bookmarks", () => { + it("can create a new bookmark in a container", async () => { + const bookmarks = setupModule(); + const uri = await bookmarks.createBookmark({ + containerUri: "http://localhost:3456/bookmarks/", + title: "My favorite website", + url: "https://favorite.example", + }); + expect(uri).toMatch( + new RegExp("http://localhost:3456/bookmarks/[a-zA-Z0-9]{6}#it"), + ); + }); +}); diff --git a/bookmarks/rdflib/src/e2e-tests/config.json b/bookmarks/rdflib/src/e2e-tests/config.json new file mode 100644 index 00000000..922b3504 --- /dev/null +++ b/bookmarks/rdflib/src/e2e-tests/config.json @@ -0,0 +1,38 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^7.0.0/components/context.jsonld" + ], + "import": [ + "css:config/app/init/initialize-intro.json", + "css:config/app/main/default.json", + "css:config/app/variables/default.json", + "css:config/http/handler/default.json", + "css:config/http/middleware/default.json", + "css:config/http/notifications/all.json", + "css:config/http/server-factory/http.json", + "css:config/http/static/default.json", + "css:config/identity/access/public.json", + "css:config/identity/email/default.json", + "css:config/identity/handler/default.json", + "css:config/identity/oidc/default.json", + "css:config/identity/ownership/token.json", + "css:config/identity/pod/static.json", + "css:config/ldp/authentication/dpop-bearer.json", + "css:config/ldp/authorization/webacl.json", + "css:config/ldp/handler/default.json", + "css:config/ldp/metadata-parser/default.json", + "css:config/ldp/metadata-writer/default.json", + "css:config/ldp/modes/default.json", + "css:config/storage/backend/file.json", + "css:config/storage/key-value/resource-store.json", + "css:config/storage/location/root.json", + "css:config/storage/middleware/default.json", + "css:config/util/auxiliary/acl.json", + "css:config/util/identifiers/suffix.json", + "css:config/util/index/default.json", + "css:config/util/logging/winston.json", + "css:config/util/representation-conversion/default.json", + "css:config/util/resource-locker/memory.json", + "css:config/util/variables/default.json" + ] +} diff --git a/bookmarks/rdflib/src/e2e-tests/globalSetup.ts b/bookmarks/rdflib/src/e2e-tests/globalSetup.ts new file mode 100644 index 00000000..f1956f5b --- /dev/null +++ b/bookmarks/rdflib/src/e2e-tests/globalSetup.ts @@ -0,0 +1,11 @@ +import { startServer } from "./start-server"; +import { generateId } from "../generate-id"; +import { App } from "@solid/community-server"; + +export let server: App; + +export default async function () { + const testId = generateId(); + console.log("starting test server. Test ID is", testId); + server = await startServer(testId); +} diff --git a/bookmarks/rdflib/src/e2e-tests/globalTeardown.ts b/bookmarks/rdflib/src/e2e-tests/globalTeardown.ts new file mode 100644 index 00000000..a9163fd3 --- /dev/null +++ b/bookmarks/rdflib/src/e2e-tests/globalTeardown.ts @@ -0,0 +1,6 @@ +import { server } from "./globalSetup"; + +export default async function () { + console.log("stopping test server..."); + await server.stop(); +} diff --git a/bookmarks/rdflib/src/e2e-tests/start-server.ts b/bookmarks/rdflib/src/e2e-tests/start-server.ts new file mode 100644 index 00000000..47c6bef0 --- /dev/null +++ b/bookmarks/rdflib/src/e2e-tests/start-server.ts @@ -0,0 +1,27 @@ +import { AppRunner, joinFilePath } from "@solid/community-server"; + +import { cp } from "fs/promises"; + +export async function startServer(id: string) { + const testDataPath = joinFilePath(__dirname, "test-data"); + const rootFilePath = joinFilePath(__dirname, ".test-data", id); + const app = await new AppRunner().create({ + config: joinFilePath(__dirname, "./config.json"), + loaderProperties: { + mainModulePath: joinFilePath(__dirname, "../"), + dumpErrorState: false, + }, + shorthand: { + port: 3456, + loggingLevel: "off", + rootFilePath: rootFilePath, + }, + variableBindings: {}, + }); + + await app.start(); + + await cp(testDataPath, rootFilePath, { recursive: true }); + + return app; +} diff --git a/bookmarks/rdflib/src/generate-id.ts b/bookmarks/rdflib/src/generate-id.ts new file mode 100644 index 00000000..328a67bc --- /dev/null +++ b/bookmarks/rdflib/src/generate-id.ts @@ -0,0 +1,7 @@ +import ShortUniqueId from "short-unique-id"; + +const uid = new ShortUniqueId({ length: 10 }); + +export function generateId() { + return uid.rnd(6); +} diff --git a/bookmarks/rdflib/src/index.ts b/bookmarks/rdflib/src/index.ts index a31b45cb..0389b7c2 100644 --- a/bookmarks/rdflib/src/index.ts +++ b/bookmarks/rdflib/src/index.ts @@ -2,6 +2,28 @@ import { BookmarksModuleRdfLib } from "./module/BookmarksModuleRdfLib.js"; export default BookmarksModuleRdfLib; -export interface BookmarksModule { +/** + * Data needed to create a new bookmark within a container + */ +export interface CreateBookmarkInContainerCommand { + /** + * The URI of the target container + */ + containerUri: string; + /** + * The human-readable title of the bookmark + */ + title: string; + /** + * The URL to bookmark + */ + url: string; +} -} \ No newline at end of file +export interface BookmarksModule { + createBookmark({ + containerUri, + title, + url, + }: CreateBookmarkInContainerCommand): Promise; +} diff --git a/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts b/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts index 6f583cb7..44bf90f6 100644 --- a/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts +++ b/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts @@ -1,4 +1,4 @@ -import { BookmarksModule } from "../index.js"; +import { BookmarksModule, CreateBookmarkInContainerCommand } from "../index.js"; import { Fetcher, IndexedFormula, UpdateManager } from "rdflib"; interface ModuleConfig { @@ -17,4 +17,12 @@ export class BookmarksModuleRdfLib implements BookmarksModule { this.fetcher = config.fetcher; this.updater = config.updater; } -} \ No newline at end of file + + async createBookmark({ + containerUri, + title, + url, + }: CreateBookmarkInContainerCommand): Promise { + return containerUri + "abc123#it"; + } +} diff --git a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts new file mode 100644 index 00000000..03daa984 --- /dev/null +++ b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts @@ -0,0 +1,42 @@ +import { BookmarksModuleRdfLib } from "../BookmarksModuleRdfLib"; + +import { generateId } from "../../generate-id"; +import { expectPatchRequest } from "../../test-support/expectRequests"; +import { Fetcher, graph, UpdateManager } from "rdflib"; + +jest.mock("../../generate-id"); + +describe("create bookmark", () => { + it("creates a new document for the bookmark in the target container", async () => { + const authenticatedFetch = jest.fn(); + + (generateId as jest.Mock).mockReturnValueOnce("70501305"); + + const store = graph(); + const fetcher = new Fetcher(store, { + fetch: authenticatedFetch, + }); + const updater = new UpdateManager(store); + const bookmarks = new BookmarksModuleRdfLib({ + store, + fetcher, + updater, + }); + + const createdUri = await bookmarks.createBookmark({ + containerUri: "https://pod.test/alice/bookmarks/", + title: "My favorite website", + url: "http://favorite.example", + }); + + expect(createdUri).toEqual("https://pod.test/alice/bookmarks/70501305#it"); + expectPatchRequest( + authenticatedFetch, + "https://pod.test/alice/bookmarks/70501305", + `INSERT DATA { . + "My favorite website" . + . + }`, + ); + }); +}); diff --git a/bookmarks/rdflib/src/test-support/expectRequests.ts b/bookmarks/rdflib/src/test-support/expectRequests.ts new file mode 100644 index 00000000..31da2c25 --- /dev/null +++ b/bookmarks/rdflib/src/test-support/expectRequests.ts @@ -0,0 +1,30 @@ +export function expectPatchRequest( + authenticatedFetch: jest.Mock, + url: string, + expectedBody: string, +) { + expect(authenticatedFetch).toHaveBeenCalledWith(url, expect.anything()); + + const calls = authenticatedFetch.mock.calls; + const updateRequest = calls.find( + (it) => it[0] === url && it[1].method === "PATCH", + ); + expect(updateRequest).toBeDefined(); + const body = updateRequest[1].body; + expect(body.trim()).toEqual(expectedBody); +} + +export function expectPutEmptyTurtleFile( + authenticatedFetch: jest.Mock, + url: string, +) { + expect(authenticatedFetch).toHaveBeenCalledWith(url, expect.anything()); + + const calls = authenticatedFetch.mock.calls; + const updateRequest = calls.find( + (it) => it[0] === url && it[1].method === "PUT", + ); + expect(updateRequest).toBeDefined(); + const body = updateRequest[1].body; + expect(body).toEqual(undefined); +} diff --git a/bookmarks/rdflib/src/test-support/mockResponses.ts b/bookmarks/rdflib/src/test-support/mockResponses.ts new file mode 100644 index 00000000..6f8918a5 --- /dev/null +++ b/bookmarks/rdflib/src/test-support/mockResponses.ts @@ -0,0 +1,48 @@ +import { when } from "jest-when"; + +export function mockTurtleResponse(fetch: jest.Mock, uri: string, ttl: string) { + when(fetch) + .calledWith(uri, expect.anything()) + .mockResolvedValue({ + ok: true, + status: 200, + statusText: "OK", + headers: new Headers({ + "Content-Type": "text/turtle", + "wac-allow": 'user="read write append control",public="read"', + "accept-patch": "application/sparql-update", + }), + text: () => Promise.resolve(ttl), + } as Response); +} + +export function mockNotFound(fetch: jest.Mock, uri: string) { + when(fetch) + .calledWith(uri, expect.anything()) + .mockResolvedValue({ + ok: true, + status: 404, + statusText: "Not Found", + headers: new Headers({ + "Content-Type": "text/plain", + "wac-allow": 'user="read write append control",public="read"', + "accept-patch": "application/sparql-update", + }), + text: () => Promise.resolve("Not Found"), + } as Response); +} + +export function mockForbidden(fetch: jest.Mock, uri: string) { + when(fetch) + .calledWith(uri, expect.anything()) + .mockResolvedValue({ + ok: true, + status: 403, + statusText: "Forbidden", + headers: new Headers({ + "Content-Type": "text/plain", + "wac-allow": 'user="read write append control",public=""', + }), + text: () => Promise.resolve("You do not have access to this resource."), + } as Response); +} diff --git a/bookmarks/rdflib/src/test-support/setupModule.ts b/bookmarks/rdflib/src/test-support/setupModule.ts new file mode 100644 index 00000000..a0355c6d --- /dev/null +++ b/bookmarks/rdflib/src/test-support/setupModule.ts @@ -0,0 +1,11 @@ +import { Fetcher, graph, UpdateManager } from "rdflib"; +import { BookmarksModuleRdfLib } from "../module/BookmarksModuleRdfLib"; + +export function setupModule(authenticatedFetch: typeof fetch = fetch) { + const store = graph(); + const fetcher = new Fetcher(store, { + fetch: authenticatedFetch, + }); + const updater = new UpdateManager(store); + return new BookmarksModuleRdfLib({ store, fetcher, updater }); +} From c7e980461c5db73f3dbb7e5b7be08cbbc35b4d75 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 5 Jun 2024 21:44:35 +0200 Subject: [PATCH 03/15] bookmarks: create bookmark within container --- bookmarks/rdflib/package-lock.json | 10 +++ bookmarks/rdflib/package.json | 1 + .../src/module/BookmarksModuleRdfLib.ts | 6 +- .../create-bookmark.integration.spec.ts | 8 +- .../createBookmarkWithinContainer.spec.ts | 90 +++++++++++++++++++ .../createBookmarkWithinContainer.ts | 38 ++++++++ .../src/module/update-operations/index.ts | 14 +++ .../module/web-operations/executeUpdate.ts | 19 ++++ 8 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.spec.ts create mode 100644 bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts create mode 100644 bookmarks/rdflib/src/module/update-operations/index.ts create mode 100644 bookmarks/rdflib/src/module/web-operations/executeUpdate.ts diff --git a/bookmarks/rdflib/package-lock.json b/bookmarks/rdflib/package-lock.json index 58bf170c..74ceee2e 100644 --- a/bookmarks/rdflib/package-lock.json +++ b/bookmarks/rdflib/package-lock.json @@ -14,6 +14,7 @@ "devDependencies": { "@solid/community-server": "^7.0.5", "@types/jest": "^29.5.12", + "@types/jest-when": "^3.5.5", "@typescript-eslint/eslint-plugin": "^7.12.0", "eslint": "^8.57.0", "eslint-plugin-require-extensions": "^0.1.3", @@ -4508,6 +4509,15 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/jest-when": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@types/jest-when/-/jest-when-3.5.5.tgz", + "integrity": "sha512-H9MDPIrz7NOu6IXP9OHExNN9LnJbGYAzRsGIDKxWr7Fth9vovemNV8yFbkUWLSEmuA8PREvAEvt9yK0PPLmFHA==", + "dev": true, + "dependencies": { + "@types/jest": "*" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", diff --git a/bookmarks/rdflib/package.json b/bookmarks/rdflib/package.json index 1e901a83..5aaf07b6 100644 --- a/bookmarks/rdflib/package.json +++ b/bookmarks/rdflib/package.json @@ -45,6 +45,7 @@ "devDependencies": { "@solid/community-server": "^7.0.5", "@types/jest": "^29.5.12", + "@types/jest-when": "^3.5.5", "@typescript-eslint/eslint-plugin": "^7.12.0", "eslint": "^8.57.0", "eslint-plugin-require-extensions": "^0.1.3", diff --git a/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts b/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts index 44bf90f6..925bfd1e 100644 --- a/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts +++ b/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts @@ -1,5 +1,7 @@ import { BookmarksModule, CreateBookmarkInContainerCommand } from "../index.js"; import { Fetcher, IndexedFormula, UpdateManager } from "rdflib"; +import { createBookmarkWithinContainer } from "./update-operations/index.js"; +import { executeUpdate } from "./web-operations/executeUpdate.js"; interface ModuleConfig { store: IndexedFormula; @@ -23,6 +25,8 @@ export class BookmarksModuleRdfLib implements BookmarksModule { title, url, }: CreateBookmarkInContainerCommand): Promise { - return containerUri + "abc123#it"; + const operation = createBookmarkWithinContainer(containerUri, title, url); + await executeUpdate(this.fetcher, this.updater, operation); + return operation.uri; } } diff --git a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts index 03daa984..f0e94a81 100644 --- a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts +++ b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts @@ -3,6 +3,7 @@ import { BookmarksModuleRdfLib } from "../BookmarksModuleRdfLib"; import { generateId } from "../../generate-id"; import { expectPatchRequest } from "../../test-support/expectRequests"; import { Fetcher, graph, UpdateManager } from "rdflib"; +import { mockNotFound } from "../../test-support/mockResponses"; jest.mock("../../generate-id"); @@ -23,10 +24,15 @@ describe("create bookmark", () => { updater, }); + mockNotFound( + authenticatedFetch, + "https://pod.test/alice/bookmarks/70501305", + ); + const createdUri = await bookmarks.createBookmark({ containerUri: "https://pod.test/alice/bookmarks/", title: "My favorite website", - url: "http://favorite.example", + url: "https://favorite.example", }); expect(createdUri).toEqual("https://pod.test/alice/bookmarks/70501305#it"); diff --git a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.spec.ts b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.spec.ts new file mode 100644 index 00000000..15b68f83 --- /dev/null +++ b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.spec.ts @@ -0,0 +1,90 @@ +import { createBookmarkWithinContainer } from "./createBookmarkWithinContainer"; + +import { when } from "jest-when"; +import { generateId } from "../../generate-id"; +import { lit, st, sym } from "rdflib"; + +jest.mock("../../generate-id"); + +describe("createBookmarkWithinContainer", () => { + it("mints a new URI for the bookmark", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinContainer( + "https://alice.test/bookmarks/", + "irrelevant", + "https://site.test", + ); + expect(result.uri).toEqual("https://alice.test/bookmarks/abc123#it"); + }); + + it("inserts the type Bookmark", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinContainer( + "https://alice.test/bookmarks/", + "irrelevant", + "https://site.test", + ); + expect(result.insertions).toContainEqual( + st( + sym("https://alice.test/bookmarks/abc123#it"), + sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + sym("http://www.w3.org/2002/01/bookmark#Bookmark"), + sym("https://alice.test/bookmarks/abc123"), + ), + ); + }); + + it("inserts the title of the bookmark", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinContainer( + "https://alice.test/bookmarks/", + "My favorite website", + "https://site.test", + ); + expect(result.insertions).toContainEqual( + st( + sym("https://alice.test/bookmarks/abc123#it"), + sym("http://purl.org/dc/terms/title"), + lit("My favorite website"), + sym("https://alice.test/bookmarks/abc123"), + ), + ); + }); + + it("inserts the URL of the bookmark", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinContainer( + "https://alice.test/bookmarks/", + "My favorite website", + "https://site.test", + ); + expect(result.insertions).toContainEqual( + st( + sym("https://alice.test/bookmarks/abc123#it"), + sym("http://www.w3.org/2002/01/bookmark#recalls"), + sym("https://site.test"), + sym("https://alice.test/bookmarks/abc123"), + ), + ); + }); + + it("deletes nothing", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinContainer( + "https://alice.test/bookmarks/", + "irrelevant", + "https://site.test", + ); + expect(result.deletions).toEqual([]); + }); + + it("creates no files", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinContainer( + "https://alice.test/bookmarks/", + "irrelevant", + "https://site.test", + ); + expect(result.filesToCreate).toEqual([]); + }); +}); diff --git a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts new file mode 100644 index 00000000..88a6d81c --- /dev/null +++ b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts @@ -0,0 +1,38 @@ +import { UpdateOperation } from "./index.js"; +import { generateId } from "../../generate-id.js"; +import { lit, st, sym } from "rdflib"; + +export function createBookmarkWithinContainer( + containerUri: string, + title: string, + url: string, +): UpdateOperation { + const id = generateId(); + const uri = containerUri + id + "#it"; + const bookmarkNode = sym(uri); + return { + uri, + deletions: [], + insertions: [ + st( + bookmarkNode, + sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + sym("http://www.w3.org/2002/01/bookmark#Bookmark"), + bookmarkNode.doc(), + ), + st( + bookmarkNode, + sym("http://purl.org/dc/terms/title"), + lit(title), + bookmarkNode.doc(), + ), + st( + bookmarkNode, + sym("http://www.w3.org/2002/01/bookmark#recalls"), + sym(url), + bookmarkNode.doc(), + ), + ], + filesToCreate: [], + }; +} diff --git a/bookmarks/rdflib/src/module/update-operations/index.ts b/bookmarks/rdflib/src/module/update-operations/index.ts new file mode 100644 index 00000000..f3d5c2d1 --- /dev/null +++ b/bookmarks/rdflib/src/module/update-operations/index.ts @@ -0,0 +1,14 @@ +import { Statement } from "rdflib"; + +export { createBookmarkWithinContainer } from "./createBookmarkWithinContainer.js"; + +export interface FileToCreate { + uri: string; +} + +export interface UpdateOperation { + uri: string; + insertions: Statement[]; + deletions: Statement[]; + filesToCreate: FileToCreate[]; +} diff --git a/bookmarks/rdflib/src/module/web-operations/executeUpdate.ts b/bookmarks/rdflib/src/module/web-operations/executeUpdate.ts new file mode 100644 index 00000000..780f734d --- /dev/null +++ b/bookmarks/rdflib/src/module/web-operations/executeUpdate.ts @@ -0,0 +1,19 @@ +import { Fetcher, UpdateManager } from "rdflib"; +import { UpdateOperation } from "../update-operations"; + +export async function executeUpdate( + fetcher: Fetcher, + updater: UpdateManager, + operation: UpdateOperation, +) { + await updater.updateMany(operation.deletions, operation.insertions); + operation.filesToCreate.map((file) => { + createEmptyTurtleFile(fetcher, file.uri); + }); +} + +function createEmptyTurtleFile(fetcher: Fetcher, uri: string) { + return fetcher.webOperation("PUT", uri, { + contentType: "text/turtle", + }); +} From 837c92011085df25a38fac2ed9d0f817bf8fcc9d Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 5 Jun 2024 21:53:58 +0200 Subject: [PATCH 04/15] bookmarks: example for create bookmark within container --- bookmarks/rdflib/README.md | 2 +- .../examples/create-bookmark-within-container.mjs | 15 +++++++++++++++ bookmarks/rdflib/package.json | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 bookmarks/rdflib/examples/create-bookmark-within-container.mjs diff --git a/bookmarks/rdflib/README.md b/bookmarks/rdflib/README.md index 6144343c..65c58c94 100644 --- a/bookmarks/rdflib/README.md +++ b/bookmarks/rdflib/README.md @@ -64,7 +64,7 @@ After that you can run an example script like this: ```shell npm run build -node ./examples/create-bookmark.mjs +node ./examples/create-bookmark-within-container.mjs ``` ### Available features diff --git a/bookmarks/rdflib/examples/create-bookmark-within-container.mjs b/bookmarks/rdflib/examples/create-bookmark-within-container.mjs new file mode 100644 index 00000000..60dc6186 --- /dev/null +++ b/bookmarks/rdflib/examples/create-bookmark-within-container.mjs @@ -0,0 +1,15 @@ +import BookmarksModule from '../dist/index.js'; +import {Fetcher, graph, UpdateManager} from "rdflib"; + +const store = graph() +const fetcher = new Fetcher(store) +const updater = new UpdateManager(store) +const bookmarks = new BookmarksModule({store, fetcher, updater}) + +const uri = await bookmarks.createBookmark({ + containerUri: "http://localhost:3000/alice/bookmarks/", + title: "My favorite website", + url: "https://favorite.example" +}) + +console.log("new bookmark: " + uri) diff --git a/bookmarks/rdflib/package.json b/bookmarks/rdflib/package.json index 5aaf07b6..4c5f4b47 100644 --- a/bookmarks/rdflib/package.json +++ b/bookmarks/rdflib/package.json @@ -12,7 +12,7 @@ "serve:doc": "serve ../../gh-pages", "test": "jest", "test:e2e": "jest --config jest.e2e.config.ts", - "lint": "eslint ./src/**", + "lint": "eslint ./src/** ./examples/**", "pod": "community-solid-server --config ../dev-server/config/config-mashlib.json --seedConfig ../dev-server/seed.json --rootFilePath ../dev-server/data", "pod:init": "cp -r ../dev-server/initial-data/* ../dev-server/data/", "pod:clean": "rm -rf ../dev-server/data" From 9dd218f8ad895fcdeed45358df08135f52e9ac9d Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 5 Jun 2024 21:58:56 +0200 Subject: [PATCH 05/15] bookmarks: build api docs --- .github/workflows/pages.yml | 11 +++++++++++ bookmarks/rdflib/package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 6d59e537..c2cc7888 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -59,6 +59,17 @@ jobs: working-directory: ./bookmarks/soukai run: npm run build:doc +###################### +###################### bookmarks/rdflib module + + - name: Install bookmarks/rdflib dev dependencies + working-directory: ./bookmarks/rdflib + run: npm ci --only=dev + + - name: Build bookmarks/rdflib doc + working-directory: ./bookmarks/rdflib + run: npm run build:doc + ###################### - name: Upload pages diff --git a/bookmarks/rdflib/package.json b/bookmarks/rdflib/package.json index 4c5f4b47..d1ed60af 100644 --- a/bookmarks/rdflib/package.json +++ b/bookmarks/rdflib/package.json @@ -8,7 +8,7 @@ "scripts": { "clean": "rm -rf dist/", "build": "npm run clean && tsc", - "build:doc": "typedoc src/index.ts --out ../../gh-pages/bookmarks-rdflib --tsconfig ./tsconfig.json", + "build:doc": "typedoc src/index.ts --out ../../gh-pages/bookmarks-rdflib-api --tsconfig ./tsconfig.json", "serve:doc": "serve ../../gh-pages", "test": "jest", "test:e2e": "jest --config jest.e2e.config.ts", From 1bbbd4196d1e160a64b2a749b8d404d7917a6196 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 5 Jun 2024 22:00:30 +0200 Subject: [PATCH 06/15] bookmarks: changelog --- bookmarks/rdflib/CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 bookmarks/rdflib/CHANGELOG.md diff --git a/bookmarks/rdflib/CHANGELOG.md b/bookmarks/rdflib/CHANGELOG.md new file mode 100644 index 00000000..9d7fdee3 --- /dev/null +++ b/bookmarks/rdflib/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +All notable changes to this module will be documented in this file. + +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). + +## unreleased + +### Added + +- [createBookmark](https://solid-contrib.github.io/data-modules/bookmarks-rdflib-api/interfaces/BookmarksModule.html#createBookmark) From b56d734ec8b03f17638787a9c98058f9d4cac038 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 5 Jun 2024 22:03:02 +0200 Subject: [PATCH 07/15] bookmarks: fix imports --- bookmarks/rdflib/src/module/web-operations/executeUpdate.ts | 2 +- bookmarks/rdflib/src/test-support/setupModule.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookmarks/rdflib/src/module/web-operations/executeUpdate.ts b/bookmarks/rdflib/src/module/web-operations/executeUpdate.ts index 780f734d..8a7293fb 100644 --- a/bookmarks/rdflib/src/module/web-operations/executeUpdate.ts +++ b/bookmarks/rdflib/src/module/web-operations/executeUpdate.ts @@ -1,5 +1,5 @@ import { Fetcher, UpdateManager } from "rdflib"; -import { UpdateOperation } from "../update-operations"; +import { UpdateOperation } from "../update-operations/index.js"; export async function executeUpdate( fetcher: Fetcher, diff --git a/bookmarks/rdflib/src/test-support/setupModule.ts b/bookmarks/rdflib/src/test-support/setupModule.ts index a0355c6d..ea5b7d5b 100644 --- a/bookmarks/rdflib/src/test-support/setupModule.ts +++ b/bookmarks/rdflib/src/test-support/setupModule.ts @@ -1,5 +1,5 @@ import { Fetcher, graph, UpdateManager } from "rdflib"; -import { BookmarksModuleRdfLib } from "../module/BookmarksModuleRdfLib"; +import { BookmarksModuleRdfLib } from "../module/BookmarksModuleRdfLib.js"; export function setupModule(authenticatedFetch: typeof fetch = fetch) { const store = graph(); From 60bfe06952c72fb0965e4d2cdcfb951b77b7ab91 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 5 Jun 2024 22:07:58 +0200 Subject: [PATCH 08/15] bookmarks: add existing bookmark to e2e test data --- .../rdflib/src/e2e-tests/test-data/bookmarks/8beCSQ$.ttl | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 bookmarks/rdflib/src/e2e-tests/test-data/bookmarks/8beCSQ$.ttl diff --git a/bookmarks/rdflib/src/e2e-tests/test-data/bookmarks/8beCSQ$.ttl b/bookmarks/rdflib/src/e2e-tests/test-data/bookmarks/8beCSQ$.ttl new file mode 100644 index 00000000..289b5c7f --- /dev/null +++ b/bookmarks/rdflib/src/e2e-tests/test-data/bookmarks/8beCSQ$.ttl @@ -0,0 +1,4 @@ +<#it> + a ; + "Existing bookmark" ; + . From cf1340150453b8c7bfa9271f48ba8c14cca6d924 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Thu, 6 Jun 2024 19:07:22 +0200 Subject: [PATCH 09/15] bookmarks: add creation date --- .../create-bookmark.integration.spec.ts | 6 ++++- .../createBookmarkWithinContainer.spec.ts | 23 +++++++++++++++++++ .../createBookmarkWithinContainer.ts | 10 ++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts index f0e94a81..378b5c4e 100644 --- a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts +++ b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts @@ -4,6 +4,7 @@ import { generateId } from "../../generate-id"; import { expectPatchRequest } from "../../test-support/expectRequests"; import { Fetcher, graph, UpdateManager } from "rdflib"; import { mockNotFound } from "../../test-support/mockResponses"; +import { when } from "jest-when"; jest.mock("../../generate-id"); @@ -11,7 +12,9 @@ describe("create bookmark", () => { it("creates a new document for the bookmark in the target container", async () => { const authenticatedFetch = jest.fn(); - (generateId as jest.Mock).mockReturnValueOnce("70501305"); + when(generateId).mockReturnValue("70501305"); + + jest.useFakeTimers().setSystemTime(new Date("2024-01-02T03:04:05.678Z")); const store = graph(); const fetcher = new Fetcher(store, { @@ -42,6 +45,7 @@ describe("create bookmark", () => { `INSERT DATA { . "My favorite website" . . + "2024-01-02T03:04:05.678Z"^^ . }`, ); }); diff --git a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.spec.ts b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.spec.ts index 15b68f83..ec1585c5 100644 --- a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.spec.ts +++ b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.spec.ts @@ -68,6 +68,29 @@ describe("createBookmarkWithinContainer", () => { ); }); + it("inserts the current time as creation date", () => { + when(generateId).mockReturnValue("abc123"); + const now = new Date("2024-01-02T03:04:05.123Z"); + jest.useFakeTimers().setSystemTime(now); + const result = createBookmarkWithinContainer( + "https://alice.test/bookmarks/", + "My favorite website", + "https://site.test", + ); + expect(result.insertions).toContainEqual( + st( + sym("https://alice.test/bookmarks/abc123#it"), + sym("http://purl.org/dc/terms/created"), + lit( + "2024-01-02T03:04:05.123Z", + undefined, + sym("http://www.w3.org/2001/XMLSchema#dateTime"), + ), + sym("https://alice.test/bookmarks/abc123"), + ), + ); + }); + it("deletes nothing", () => { when(generateId).mockReturnValue("abc123"); const result = createBookmarkWithinContainer( diff --git a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts index 88a6d81c..e922d0a9 100644 --- a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts +++ b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts @@ -32,6 +32,16 @@ export function createBookmarkWithinContainer( sym(url), bookmarkNode.doc(), ), + st( + bookmarkNode, + sym("http://purl.org/dc/terms/created"), + lit( + new Date().toISOString(), + undefined, + sym("http://www.w3.org/2001/XMLSchema#dateTime"), + ), + bookmarkNode.doc(), + ), ], filesToCreate: [], }; From 147eab991a6a9b980de05058ea6b6cf348b825b6 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Thu, 6 Jun 2024 19:37:30 +0200 Subject: [PATCH 10/15] bookmarks: prepare update operation for creating a bookmark within a document --- .../update-operations/createBookmark.ts | 44 +++++++ .../createBookmarkWithinContainer.ts | 41 +------ .../createBookmarkWithinDocument.spec.ts | 113 ++++++++++++++++++ .../createBookmarkWithinDocument.ts | 13 ++ .../src/module/update-operations/index.ts | 1 + 5 files changed, 174 insertions(+), 38 deletions(-) create mode 100644 bookmarks/rdflib/src/module/update-operations/createBookmark.ts create mode 100644 bookmarks/rdflib/src/module/update-operations/createBookmarkWithinDocument.spec.ts create mode 100644 bookmarks/rdflib/src/module/update-operations/createBookmarkWithinDocument.ts diff --git a/bookmarks/rdflib/src/module/update-operations/createBookmark.ts b/bookmarks/rdflib/src/module/update-operations/createBookmark.ts new file mode 100644 index 00000000..35b1a0ef --- /dev/null +++ b/bookmarks/rdflib/src/module/update-operations/createBookmark.ts @@ -0,0 +1,44 @@ +import { lit, st, sym } from "rdflib"; + +export function createBookmark( + bookmarkUri: string, + title: string, + url: string, +) { + const bookmarkNode = sym(bookmarkUri); + return { + uri: bookmarkUri, + deletions: [], + insertions: [ + st( + bookmarkNode, + sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + sym("http://www.w3.org/2002/01/bookmark#Bookmark"), + bookmarkNode.doc(), + ), + st( + bookmarkNode, + sym("http://purl.org/dc/terms/title"), + lit(title), + bookmarkNode.doc(), + ), + st( + bookmarkNode, + sym("http://www.w3.org/2002/01/bookmark#recalls"), + sym(url), + bookmarkNode.doc(), + ), + st( + bookmarkNode, + sym("http://purl.org/dc/terms/created"), + lit( + new Date().toISOString(), + undefined, + sym("http://www.w3.org/2001/XMLSchema#dateTime"), + ), + bookmarkNode.doc(), + ), + ], + filesToCreate: [], + }; +} diff --git a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts index e922d0a9..562d9d39 100644 --- a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts +++ b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinContainer.ts @@ -1,6 +1,6 @@ import { UpdateOperation } from "./index.js"; import { generateId } from "../../generate-id.js"; -import { lit, st, sym } from "rdflib"; +import { createBookmark } from "./createBookmark.js"; export function createBookmarkWithinContainer( containerUri: string, @@ -8,41 +8,6 @@ export function createBookmarkWithinContainer( url: string, ): UpdateOperation { const id = generateId(); - const uri = containerUri + id + "#it"; - const bookmarkNode = sym(uri); - return { - uri, - deletions: [], - insertions: [ - st( - bookmarkNode, - sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), - sym("http://www.w3.org/2002/01/bookmark#Bookmark"), - bookmarkNode.doc(), - ), - st( - bookmarkNode, - sym("http://purl.org/dc/terms/title"), - lit(title), - bookmarkNode.doc(), - ), - st( - bookmarkNode, - sym("http://www.w3.org/2002/01/bookmark#recalls"), - sym(url), - bookmarkNode.doc(), - ), - st( - bookmarkNode, - sym("http://purl.org/dc/terms/created"), - lit( - new Date().toISOString(), - undefined, - sym("http://www.w3.org/2001/XMLSchema#dateTime"), - ), - bookmarkNode.doc(), - ), - ], - filesToCreate: [], - }; + const bookmarkUri = containerUri + id + "#it"; + return createBookmark(bookmarkUri, title, url); } diff --git a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinDocument.spec.ts b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinDocument.spec.ts new file mode 100644 index 00000000..01b96fa5 --- /dev/null +++ b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinDocument.spec.ts @@ -0,0 +1,113 @@ +import { createBookmarkWithinDocument } from "./createBookmarkWithinDocument"; + +import { when } from "jest-when"; +import { generateId } from "../../generate-id"; +import { lit, st, sym } from "rdflib"; + +jest.mock("../../generate-id"); + +describe("createBookmarkWithinDocument", () => { + it("mints a new fragment URI for the bookmark within the document", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinDocument( + "https://alice.test/bookmarks", + "irrelevant", + "https://site.test", + ); + expect(result.uri).toEqual("https://alice.test/bookmarks#abc123"); + }); + + it("inserts the type Bookmark", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinDocument( + "https://alice.test/bookmarks", + "irrelevant", + "https://site.test", + ); + expect(result.insertions).toContainEqual( + st( + sym("https://alice.test/bookmarks#abc123"), + sym("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"), + sym("http://www.w3.org/2002/01/bookmark#Bookmark"), + sym("https://alice.test/bookmarks"), + ), + ); + }); + + it("inserts the title of the bookmark", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinDocument( + "https://alice.test/bookmarks", + "My favorite website", + "https://site.test", + ); + expect(result.insertions).toContainEqual( + st( + sym("https://alice.test/bookmarks#abc123"), + sym("http://purl.org/dc/terms/title"), + lit("My favorite website"), + sym("https://alice.test/bookmarks"), + ), + ); + }); + + it("inserts the URL of the bookmark", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinDocument( + "https://alice.test/bookmarks", + "My favorite website", + "https://site.test", + ); + expect(result.insertions).toContainEqual( + st( + sym("https://alice.test/bookmarks#abc123"), + sym("http://www.w3.org/2002/01/bookmark#recalls"), + sym("https://site.test"), + sym("https://alice.test/bookmarks"), + ), + ); + }); + + it("inserts the current time as creation date", () => { + when(generateId).mockReturnValue("abc123"); + const now = new Date("2024-01-02T03:04:05.123Z"); + jest.useFakeTimers().setSystemTime(now); + const result = createBookmarkWithinDocument( + "https://alice.test/bookmarks", + "My favorite website", + "https://site.test", + ); + expect(result.insertions).toContainEqual( + st( + sym("https://alice.test/bookmarks#abc123"), + sym("http://purl.org/dc/terms/created"), + lit( + "2024-01-02T03:04:05.123Z", + undefined, + sym("http://www.w3.org/2001/XMLSchema#dateTime"), + ), + sym("https://alice.test/bookmarks"), + ), + ); + }); + + it("deletes nothing", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinDocument( + "https://alice.test/bookmarks", + "irrelevant", + "https://site.test", + ); + expect(result.deletions).toEqual([]); + }); + + it("creates no files", () => { + when(generateId).mockReturnValue("abc123"); + const result = createBookmarkWithinDocument( + "https://alice.test/bookmarks", + "irrelevant", + "https://site.test", + ); + expect(result.filesToCreate).toEqual([]); + }); +}); diff --git a/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinDocument.ts b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinDocument.ts new file mode 100644 index 00000000..5bad9cf4 --- /dev/null +++ b/bookmarks/rdflib/src/module/update-operations/createBookmarkWithinDocument.ts @@ -0,0 +1,13 @@ +import { UpdateOperation } from "./index.js"; +import { generateId } from "../../generate-id.js"; +import { createBookmark } from "./createBookmark.js"; + +export function createBookmarkWithinDocument( + documentUri: string, + title: string, + url: string, +): UpdateOperation { + const id = generateId(); + const bookmarkUri = documentUri + "#" + id; + return createBookmark(bookmarkUri, title, url); +} diff --git a/bookmarks/rdflib/src/module/update-operations/index.ts b/bookmarks/rdflib/src/module/update-operations/index.ts index f3d5c2d1..7c1af633 100644 --- a/bookmarks/rdflib/src/module/update-operations/index.ts +++ b/bookmarks/rdflib/src/module/update-operations/index.ts @@ -1,6 +1,7 @@ import { Statement } from "rdflib"; export { createBookmarkWithinContainer } from "./createBookmarkWithinContainer.js"; +export { createBookmarkWithinDocument } from "./createBookmarkWithinDocument.js"; export interface FileToCreate { uri: string; From fddfab7eccde9c8cded2fcc49ec2e7b741d486f0 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Thu, 6 Jun 2024 19:49:42 +0200 Subject: [PATCH 11/15] bookmarks: use neutral storageUrl for create bookmark, so that user does not need to know whether it is a container or a document --- bookmarks/rdflib/src/index.ts | 10 +++++----- bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts | 8 ++++---- .../create-bookmark.integration.spec.ts | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bookmarks/rdflib/src/index.ts b/bookmarks/rdflib/src/index.ts index 0389b7c2..4bba724b 100644 --- a/bookmarks/rdflib/src/index.ts +++ b/bookmarks/rdflib/src/index.ts @@ -5,11 +5,11 @@ export default BookmarksModuleRdfLib; /** * Data needed to create a new bookmark within a container */ -export interface CreateBookmarkInContainerCommand { +export interface CreateBookmarkCommand { /** - * The URI of the target container + * The URL of the target container or document to store the bookmark */ - containerUri: string; + storageUrl: string; /** * The human-readable title of the bookmark */ @@ -22,8 +22,8 @@ export interface CreateBookmarkInContainerCommand { export interface BookmarksModule { createBookmark({ - containerUri, + storageUrl, title, url, - }: CreateBookmarkInContainerCommand): Promise; + }: CreateBookmarkCommand): Promise; } diff --git a/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts b/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts index 925bfd1e..99a8e8a3 100644 --- a/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts +++ b/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts @@ -1,4 +1,4 @@ -import { BookmarksModule, CreateBookmarkInContainerCommand } from "../index.js"; +import { BookmarksModule, CreateBookmarkCommand } from "../index.js"; import { Fetcher, IndexedFormula, UpdateManager } from "rdflib"; import { createBookmarkWithinContainer } from "./update-operations/index.js"; import { executeUpdate } from "./web-operations/executeUpdate.js"; @@ -21,11 +21,11 @@ export class BookmarksModuleRdfLib implements BookmarksModule { } async createBookmark({ - containerUri, + storageUrl, title, url, - }: CreateBookmarkInContainerCommand): Promise { - const operation = createBookmarkWithinContainer(containerUri, title, url); + }: CreateBookmarkCommand): Promise { + const operation = createBookmarkWithinContainer(storageUrl, title, url); await executeUpdate(this.fetcher, this.updater, operation); return operation.uri; } diff --git a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts index 378b5c4e..f34ce50e 100644 --- a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts +++ b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts @@ -33,7 +33,7 @@ describe("create bookmark", () => { ); const createdUri = await bookmarks.createBookmark({ - containerUri: "https://pod.test/alice/bookmarks/", + storageUrl: "https://pod.test/alice/bookmarks/", title: "My favorite website", url: "https://favorite.example", }); From 97dbee310b9f343988ba4c051b079627709fcf75 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Thu, 6 Jun 2024 19:50:04 +0200 Subject: [PATCH 12/15] bookmarks: add failing e2e test to create bookmark within a document --- .../rdflib/src/e2e-tests/bookmarks.e2e.spec.ts | 14 +++++++++++++- .../src/e2e-tests/test-data/public/bookmarks$.ttl | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 bookmarks/rdflib/src/e2e-tests/test-data/public/bookmarks$.ttl diff --git a/bookmarks/rdflib/src/e2e-tests/bookmarks.e2e.spec.ts b/bookmarks/rdflib/src/e2e-tests/bookmarks.e2e.spec.ts index c262c342..6db3b42f 100644 --- a/bookmarks/rdflib/src/e2e-tests/bookmarks.e2e.spec.ts +++ b/bookmarks/rdflib/src/e2e-tests/bookmarks.e2e.spec.ts @@ -4,7 +4,7 @@ describe("bookmarks", () => { it("can create a new bookmark in a container", async () => { const bookmarks = setupModule(); const uri = await bookmarks.createBookmark({ - containerUri: "http://localhost:3456/bookmarks/", + storageUrl: "http://localhost:3456/bookmarks/", title: "My favorite website", url: "https://favorite.example", }); @@ -12,4 +12,16 @@ describe("bookmarks", () => { new RegExp("http://localhost:3456/bookmarks/[a-zA-Z0-9]{6}#it"), ); }); + + it("can create a new bookmark in an existing document", async () => { + const bookmarks = setupModule(); + const uri = await bookmarks.createBookmark({ + storageUrl: "http://localhost:3456/public/bookmarks", + title: "My favorite website", + url: "https://favorite.example", + }); + expect(uri).toMatch( + new RegExp("http://localhost:3456/public/bookmarks#[a-zA-Z0-9]{6}"), + ); + }); }); diff --git a/bookmarks/rdflib/src/e2e-tests/test-data/public/bookmarks$.ttl b/bookmarks/rdflib/src/e2e-tests/test-data/public/bookmarks$.ttl new file mode 100644 index 00000000..2d9a22fe --- /dev/null +++ b/bookmarks/rdflib/src/e2e-tests/test-data/public/bookmarks$.ttl @@ -0,0 +1,9 @@ +<#fcb771d4> + a ; + "First bookmark in a document" ; + . + +<#180a31e2> + a ; + "Second bookmark in a document" ; + . From 195030eb47067870bd191763bdb0c206e2fa0447 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Thu, 6 Jun 2024 20:21:50 +0200 Subject: [PATCH 13/15] bookmarks: create bookmark within a document --- .../src/module/BookmarksModuleRdfLib.ts | 22 ++++++-- .../create-bookmark.integration.spec.ts | 50 ++++++++++++++++++- bookmarks/rdflib/src/module/namespaces.ts | 10 ++++ .../rdflib/src/test-support/mockResponses.ts | 27 +++++++++- 4 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 bookmarks/rdflib/src/module/namespaces.ts diff --git a/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts b/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts index 99a8e8a3..e6d30511 100644 --- a/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts +++ b/bookmarks/rdflib/src/module/BookmarksModuleRdfLib.ts @@ -1,7 +1,11 @@ import { BookmarksModule, CreateBookmarkCommand } from "../index.js"; -import { Fetcher, IndexedFormula, UpdateManager } from "rdflib"; -import { createBookmarkWithinContainer } from "./update-operations/index.js"; +import { Fetcher, IndexedFormula, sym, UpdateManager } from "rdflib"; +import { + createBookmarkWithinContainer, + createBookmarkWithinDocument, +} from "./update-operations/index.js"; import { executeUpdate } from "./web-operations/executeUpdate.js"; +import { ldp, rdf } from "./namespaces.js"; interface ModuleConfig { store: IndexedFormula; @@ -25,7 +29,19 @@ export class BookmarksModuleRdfLib implements BookmarksModule { title, url, }: CreateBookmarkCommand): Promise { - const operation = createBookmarkWithinContainer(storageUrl, title, url); + const storageNode = sym(storageUrl); + await this.fetcher.load(storageNode.value); + const isContainer = this.store.holds( + storageNode, + rdf("type"), + ldp("Container"), + storageNode.doc(), + ); + + const operation = ( + isContainer ? createBookmarkWithinContainer : createBookmarkWithinDocument + )(storageUrl, title, url); + await executeUpdate(this.fetcher, this.updater, operation); return operation.uri; } diff --git a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts index f34ce50e..9525dc5b 100644 --- a/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts +++ b/bookmarks/rdflib/src/module/_integration-tests/create-bookmark.integration.spec.ts @@ -3,7 +3,11 @@ import { BookmarksModuleRdfLib } from "../BookmarksModuleRdfLib"; import { generateId } from "../../generate-id"; import { expectPatchRequest } from "../../test-support/expectRequests"; import { Fetcher, graph, UpdateManager } from "rdflib"; -import { mockNotFound } from "../../test-support/mockResponses"; +import { + mockLdpContainer, + mockNotFound, + mockTurtleDocument, +} from "../../test-support/mockResponses"; import { when } from "jest-when"; jest.mock("../../generate-id"); @@ -27,6 +31,8 @@ describe("create bookmark", () => { updater, }); + mockLdpContainer(authenticatedFetch, "https://pod.test/alice/bookmarks/"); + mockNotFound( authenticatedFetch, "https://pod.test/alice/bookmarks/70501305", @@ -46,6 +52,48 @@ describe("create bookmark", () => { "My favorite website" . . "2024-01-02T03:04:05.678Z"^^ . + }`, + ); + }); + + it("adds a new bookmark to an existing document", async () => { + const authenticatedFetch = jest.fn(); + + when(generateId).mockReturnValue("f7a4eeb7"); + + jest.useFakeTimers().setSystemTime(new Date("2024-01-02T03:04:05.678Z")); + + const store = graph(); + const fetcher = new Fetcher(store, { + fetch: authenticatedFetch, + }); + const updater = new UpdateManager(store); + const bookmarks = new BookmarksModuleRdfLib({ + store, + fetcher, + updater, + }); + + mockTurtleDocument( + authenticatedFetch, + "https://pod.test/alice/bookmarks", + ``, + ); + + const createdUri = await bookmarks.createBookmark({ + storageUrl: "https://pod.test/alice/bookmarks", + title: "My favorite website", + url: "https://favorite.example", + }); + + expect(createdUri).toEqual("https://pod.test/alice/bookmarks#f7a4eeb7"); + expectPatchRequest( + authenticatedFetch, + "https://pod.test/alice/bookmarks", + `INSERT DATA { . + "My favorite website" . + . + "2024-01-02T03:04:05.678Z"^^ . }`, ); }); diff --git a/bookmarks/rdflib/src/module/namespaces.ts b/bookmarks/rdflib/src/module/namespaces.ts new file mode 100644 index 00000000..2064ee37 --- /dev/null +++ b/bookmarks/rdflib/src/module/namespaces.ts @@ -0,0 +1,10 @@ +import { Namespace } from "rdflib"; + +export const rdf = Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + +export const solid = Namespace("http://www.w3.org/ns/solid/terms#"); +export const pim = Namespace("http://www.w3.org/ns/pim/space#"); +export const dct = Namespace("http://purl.org/dc/terms/"); +export const bookm = Namespace("http://www.w3.org/2002/01/bookmark#"); +export const xsd = Namespace("http://www.w3.org/2001/XMLSchema#"); +export const ldp = Namespace("http://www.w3.org/ns/ldp#"); diff --git a/bookmarks/rdflib/src/test-support/mockResponses.ts b/bookmarks/rdflib/src/test-support/mockResponses.ts index 6f8918a5..48e12f51 100644 --- a/bookmarks/rdflib/src/test-support/mockResponses.ts +++ b/bookmarks/rdflib/src/test-support/mockResponses.ts @@ -1,6 +1,6 @@ import { when } from "jest-when"; -export function mockTurtleResponse(fetch: jest.Mock, uri: string, ttl: string) { +export function mockTurtleDocument(fetch: jest.Mock, uri: string, ttl: string) { when(fetch) .calledWith(uri, expect.anything()) .mockResolvedValue({ @@ -9,6 +9,7 @@ export function mockTurtleResponse(fetch: jest.Mock, uri: string, ttl: string) { statusText: "OK", headers: new Headers({ "Content-Type": "text/turtle", + link: '; rel="type"', "wac-allow": 'user="read write append control",public="read"', "accept-patch": "application/sparql-update", }), @@ -16,6 +17,30 @@ export function mockTurtleResponse(fetch: jest.Mock, uri: string, ttl: string) { } as Response); } +export function mockLdpContainer(fetch: jest.Mock, uri: string) { + when(fetch) + .calledWith(uri, expect.anything()) + .mockResolvedValue({ + ok: true, + status: 200, + statusText: "OK", + headers: new Headers({ + "Content-Type": "text/turtle", + link: '; rel="type"', + "wac-allow": 'user="read write append control",public="read"', + "accept-patch": "application/sparql-update", + }), + text: () => + Promise.resolve(` + @prefix dc: . + @prefix ldp: . + @prefix xsd: . + + <> a ldp:Container, ldp:BasicContainer, ldp:Resource . +`), + } as Response); +} + export function mockNotFound(fetch: jest.Mock, uri: string) { when(fetch) .calledWith(uri, expect.anything()) From 74a66748e0831743bf4cc639c96577d04ff238d6 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Thu, 6 Jun 2024 20:33:03 +0200 Subject: [PATCH 14/15] bookmarks: example for creating bookmark within a document --- bookmarks/dev-server/initial-data/alice/.acl | 12 ++++++++++-- .../initial-data/alice/public/bookmarks$.ttl | 0 bookmarks/rdflib/README.md | 5 +++++ .../examples/create-bookmark-within-container.mjs | 2 +- .../examples/create-bookmark-within-document.mjs | 15 +++++++++++++++ 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 bookmarks/dev-server/initial-data/alice/public/bookmarks$.ttl create mode 100644 bookmarks/rdflib/examples/create-bookmark-within-document.mjs diff --git a/bookmarks/dev-server/initial-data/alice/.acl b/bookmarks/dev-server/initial-data/alice/.acl index 8093c7c5..d1c2a26d 100644 --- a/bookmarks/dev-server/initial-data/alice/.acl +++ b/bookmarks/dev-server/initial-data/alice/.acl @@ -16,11 +16,19 @@ a acl:Authorization; acl:agent ; # Optional owner email, to be used for account recovery: - + # Set the access to the root storage folder itself acl:accessTo <./>; # All resources will inherit this authorization, by default acl:default <./>; # The owner has all of the access modes allowed acl:mode - acl:Read, acl:Write, acl:Control. + acl:Read, acl:Write, acl:Control. + +# read / write access for public for testing without auth +<#ReadWrite> + a acl:Authorization; + acl:accessTo <./>; + acl:agentClass foaf:Agent; + acl:default <./>; + acl:mode acl:Read, acl:Write. \ No newline at end of file diff --git a/bookmarks/dev-server/initial-data/alice/public/bookmarks$.ttl b/bookmarks/dev-server/initial-data/alice/public/bookmarks$.ttl new file mode 100644 index 00000000..e69de29b diff --git a/bookmarks/rdflib/README.md b/bookmarks/rdflib/README.md index 65c58c94..da4281e1 100644 --- a/bookmarks/rdflib/README.md +++ b/bookmarks/rdflib/README.md @@ -51,6 +51,11 @@ const updater = new UpdateManager(store); const module: BookmarksModule = new BookmarksModuleRdfLib({store, fetcher, updater}); // 3️⃣ use the module to interact with bookmarks +const uri = await bookmarks.createBookmark({ + storageUrl: "http://localhost:3000/alice/public/bookmarks", + title: "My favorite website", + url: "https://favorite.example" +}) ``` diff --git a/bookmarks/rdflib/examples/create-bookmark-within-container.mjs b/bookmarks/rdflib/examples/create-bookmark-within-container.mjs index 60dc6186..a7ec4a69 100644 --- a/bookmarks/rdflib/examples/create-bookmark-within-container.mjs +++ b/bookmarks/rdflib/examples/create-bookmark-within-container.mjs @@ -7,7 +7,7 @@ const updater = new UpdateManager(store) const bookmarks = new BookmarksModule({store, fetcher, updater}) const uri = await bookmarks.createBookmark({ - containerUri: "http://localhost:3000/alice/bookmarks/", + storageUrl: "http://localhost:3000/alice/bookmarks/", title: "My favorite website", url: "https://favorite.example" }) diff --git a/bookmarks/rdflib/examples/create-bookmark-within-document.mjs b/bookmarks/rdflib/examples/create-bookmark-within-document.mjs new file mode 100644 index 00000000..e85d7002 --- /dev/null +++ b/bookmarks/rdflib/examples/create-bookmark-within-document.mjs @@ -0,0 +1,15 @@ +import BookmarksModule from '../dist/index.js'; +import {Fetcher, graph, UpdateManager} from "rdflib"; + +const store = graph() +const fetcher = new Fetcher(store) +const updater = new UpdateManager(store) +const bookmarks = new BookmarksModule({store, fetcher, updater}) + +const uri = await bookmarks.createBookmark({ + storageUrl: "http://localhost:3000/alice/public/bookmarks", + title: "My favorite website", + url: "https://favorite.example" +}) + +console.log("new bookmark: " + uri) From f12a8e9c80c428d5f7f40b4265b00aecdf5de1ae Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 12 Jun 2024 20:34:58 +0200 Subject: [PATCH 15/15] bookmarks: release version 0.1.0 --- bookmarks/rdflib/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookmarks/rdflib/CHANGELOG.md b/bookmarks/rdflib/CHANGELOG.md index 9d7fdee3..9997166b 100644 --- a/bookmarks/rdflib/CHANGELOG.md +++ b/bookmarks/rdflib/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this module will be documented in this file. 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). -## unreleased +## 0.1.0 ### Added