|
- // Copyright (c) 2020 Jan Kaßel
- // Copyright (c) 2022 Gerben
- //
- // SPDX-License-Identifier: MIT
-
- import escapeString from 'escape-string-regexp';
- import etag from 'etag';
- import type { Request, Response } from 'express';
- import { CollectionInfo } from './handlers/collection.js';
- import { renderAnnotation } from './render/renderAnnotation.js';
- import { renderCollection } from './render/renderCollection.js';
-
- export function extractAnnotationIdFromUrl(
- fullAnnotationUrl: string,
- containerUrl: string,
- ) {
- const escapedUrl = escapeString(containerUrl);
- const pattern = new RegExp(`^${escapedUrl}\/([0-9a-z-]+)$`);
- const matches = fullAnnotationUrl.match(pattern);
- return !matches ? null : matches[1];
- }
-
- export function expandAnnotation(annotation, containerInfo: Container) {
- return {
- ...annotation,
- id: `${containerInfo.url}/${annotation.id}`,
- };
- }
-
- export function contractAnnotation(annotation, containerInfo: Container) {
- return {
- ...annotation,
- id: extractAnnotationIdFromUrl(annotation.id, containerInfo.url),
- };
- }
-
- export class Container {
- url: string;
- name: string;
- label: string;
-
- constructor(
- private req: Request,
- public containerPath: string,
- collectionInfo: CollectionInfo,
- ) {
- const host = this.req.headers['x-forwarded-host'] || this.req.headers.host;
- this.url = `${this.req.protocol}://${host}${this.req.baseUrl}/${this.containerPath}`;
- this.name = collectionInfo.name;
- this.label = collectionInfo.label;
- }
- }
-
- export class PagedContainer extends Container {
- public total: number;
- public pageSize: number;
- private _getPage: (pageNumber: number, pageSize: number) => any[];
-
- constructor(
- req: Request,
- containerPath: string,
- collectionInfo: CollectionInfo,
- getPage: any[],
- );
- constructor(
- req: Request,
- containerPath: string,
- collectionInfo: CollectionInfo,
- getPage: (pageNumber: number, pageSize: number) => any[],
- total: number,
- pageSize: number,
- );
- constructor(
- req: Request,
- containerPath: string,
- collectionInfo: CollectionInfo,
- getPage: ((pageNumber: number, pageSize: number) => any[]) | any[],
- total?: number,
- pageSize?: number,
- ) {
- super(req, containerPath, collectionInfo);
-
- if (Array.isArray(getPage)) {
- const items = getPage;
- this._getPage = () => items;
- this.total = items.length;
- this.pageSize = Infinity;
- } else {
- this._getPage = getPage;
- this.total = total as number;
- this.pageSize = pageSize as number;
- }
- }
-
- getPage(pageNumber: number) {
- return this._getPage(pageNumber, this.pageSize);
- }
-
- get lastPage(): number {
- return this.pageSize === Infinity
- ? 0
- : Math.floor(this.total / this.pageSize);
- }
- }
-
- export function sendPage(
- res: Response,
- container: PagedContainer,
- pageNumber: number,
- iris: boolean,
- ) {
- if (pageNumber > container.lastPage) {
- res.status(404).send('Not found');
- return;
- }
-
- const items = container.getPage(pageNumber);
- const page = {
- '@context': 'http://www.w3.org/ns/anno.jsonld',
- id: `${container.url}/?page=${pageNumber}&iris=${iris ? 1 : 0}`,
- type: 'AnnotationPage',
- partOf: {
- id: `${container.url}/?iris=${iris ? 1 : 0}`,
- total: container.total,
- },
- startIndex: pageNumber === 0 ? 0 : container.pageSize * pageNumber,
- items: iris ? items.map((item) => item.id) : items,
- };
-
- res.type('application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"');
- res.header('Allow', 'HEAD, GET, OPTIONS');
-
- res.send(page);
- }
-
- export function sendContainer(
- req: Request,
- res: Response,
- container: PagedContainer,
- iris: boolean,
- embedFirstPage?: boolean,
- ) {
- let firstPage: any;
- if (embedFirstPage) {
- const items = container.getPage(0);
- firstPage = {
- id: `${container.url}/?page=0&iris=${iris ? 1 : 0}`,
- type: 'AnnotationPage',
- startIndex: 0,
- items: iris ? items.map((item) => item.id) : items,
- };
- } else {
- firstPage = `${container.url}/?iris=${iris ? 1 : 0}&page=0`;
- }
-
- const collectionJsonldObj = {
- '@context': [
- 'http://www.w3.org/ns/anno.jsonld',
- 'http://www.w3.org/ns/ldp.jsonld',
- ],
- id: `${container.url}/?iris=${iris ? 1 : 0}`,
- type: ['BasicContainer', 'AnnotationCollection'],
- total: container.total,
- label: container.label,
- first: firstPage,
- ...(container.lastPage > 0 && {
- last: `${container.url}/?iris=${iris ? 1 : 0}&page=${container.lastPage}`,
- }),
- };
-
- res.header('ETag', etag(JSON.stringify(collectionJsonldObj)));
-
- res.format({
- html: () =>
- renderCollection(req, res, {
- collection: collectionJsonldObj,
- container,
- user: req.params.user,
- }),
- default: () => {
- res.header('Link', [
- '<http://www.w3.org/ns/ldp#BasicContainer>; rel="type"',
- '<http://www.w3.org/TR/annotation-protocol/>; rel="http://www.w3.org/ns/ldp#constrainedBy"',
- ]);
- res.header(
- 'Accept-Post',
- 'application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"',
- );
- res.type(
- 'application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"',
- );
- res.header('Allow', 'HEAD, GET, POST, OPTIONS');
-
- res.send(collectionJsonldObj);
- },
- });
- }
-
- export function sendAnnotation(
- req: Request,
- res: Response,
- annotation,
- containerInfo: Container,
- ) {
- annotation = expandAnnotation(annotation, containerInfo);
- res.header('ETag', etag(JSON.stringify(annotation)));
- res.format({
- html: () => renderAnnotation(req, res, { annotation, containerInfo }),
- default: () => {
- res.header('Allow', 'OPTIONS,HEAD,GET,PUT,DELETE');
- res.header('Link', '<http://www.w3.org/ns/ldp#Resource>; rel="type"');
- res.type(
- 'application/ld+json; profile="http://www.w3.org/ns/anno.jsonld"',
- );
- res.send(annotation);
- },
- });
- }
|