From 8f9584af26c070ecaed4333786669ea3a0f514a9 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 11 Feb 2022 12:49:18 -0500 Subject: [PATCH] Set last-modified header, handle if-modified-since Extend https://github.com/HistoryAtState/hsg-shell/pull/396 to previous & next navigation buttons on TEI resources --- modules/frus-ajax.xql | 104 ++++++++++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 24 deletions(-) diff --git a/modules/frus-ajax.xql b/modules/frus-ajax.xql index ac58b62cf..a9448f3c2 100644 --- a/modules/frus-ajax.xql +++ b/modules/frus-ajax.xql @@ -1,4 +1,4 @@ -xquery version "3.0"; +xquery version "3.1"; declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; declare namespace tei="http://www.tei-c.org/ns/1.0"; @@ -18,44 +18,100 @@ declare option output:media-type "application/json"; : This module is called from Javascript when the user wants to navigate to the next/previous : page. :) + +(:~ + : Look up the created and last modified dates of a resource, set the appropriate HTTP response headers, + : and, if appropriate, issue a 304 Not Modified status code. + :) +declare function local:set-headers($publication-id as xs:string, $document-id as xs:string, $section-id as xs:string?) { + let $created := pages:created($publication-id, $document-id, $section-id) + let $last-modified := + pages:last-modified($publication-id, $document-id, $section-id) + (: For the purpose of comparing the resource's last modified date with the If-Modified-Since + : header supplied by the client, we must truncate any milliseconds from the last modified date. + : This is because HTTP-date is only specific to the second. + : @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 :) + => format-dateTime("[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01][Z]") + => xs:dateTime() + let $if-modified-since := try { request:get-attribute("if-modified-since") => parse-ietf-date() } catch * { () } + let $should-return-304 := + if (exists($last-modified) and exists($if-modified-since)) then + $if-modified-since ge $last-modified + else + () + return + ( + app:set-created($created), + app:set-last-modified($last-modified), + if ($should-return-304) then + response:set-status-code(304) + else + () + ) +}; + let $url := request:get-parameter("url", ()) -(: remove after beta. brittle url logic here. :) -let $url := if (starts-with($url, '/beta')) then substring-after($url, '/beta') else $url let $toc := boolean(request:get-parameter("toc", ())) let $match := analyze-string($url, "^/([^/]+)/([^/]+)/?(.*)$") -let $publication := $match//fn:group[@nr = "1"]/string() -let $volume := $match//fn:group[@nr = "2"]/string() -let $id := $match//fn:group[@nr = "3"]/string() -(: TODO Please add comments describing assumption of the path parsing and mapping onto publication-id/document-id/section-id logic :) +let $url-slug-1 := $match//fn:group[@nr = "1"]/string() +let $url-slug-2 := $match//fn:group[@nr = "2"]/string() +let $url-slug-3 := $match//fn:group[@nr = "3"]/string() + +(: First, map the URL slugs onto the required parameters for looking up the requested XML source: + : publication-id, document-id, and section-id. + : + : For example: + : + : - /historicaldocuments/frus1969-76v18/d1 -> frus, frus1969-76v18, d1 + : - /historicaldocuments/frus-history/chapter-2 -> frus-history-monograph, frus-history-monograph, chapter-2 + : - /departmenthistory/buildings/intro -> buildings, buildings, intro + : - /about/hac/August-2021 -> hac, hac, August-2021 + :) + +let $section-id := $url-slug-3 +let $document-id := + if ($url-slug-2) then + $url-slug-2 + else + $url-slug-1 let $publication-id := - switch ($publication) + switch ($url-slug-1) case 'historicaldocuments' return - switch ($volume) + switch ($document-id) case "frus-history" return "frus-history-monograph" default return 'frus' - case 'about' return $volume + case 'about' return $document-id case 'departmenthistory' return - switch ($volume) + switch ($document-id) case 'buildings' return 'buildings' case 'short-history' return 'short-history' case 'timeline' return 'timeline' - default return $publication - default return $publication -let $log := console:log("publication: " || $publication || ", volume: " || $volume) -let $volume := if ($volume) then $volume else $publication-id (: TODO see above - please explain :) -let $xml := pages:load-xml($publication-id, $volume, $id, "div") + default return $url-slug-1 + default return $url-slug-1 + +(: Load XML and generate assets :) + +let $xml := pages:load-xml($publication-id, $document-id, $section-id, "div") return if ($xml) then + ( + + (: Set HTTP Headers - and cut the request short with a 304 status code if possible :) + + local:set-headers($publication-id, $document-id, $section-id), + + (: ... then generate the assets :) + let $odd := if ($publication-id) then map:get($config:PUBLICATIONS, $publication-id)?transform else $config:odd-transform-default let $prev := pages:get-previous($xml) let $next := pages:get-next($xml) let $base-path := if (map:contains(map:get($config:PUBLICATIONS, $publication-id), 'base-path')) then - map:get($config:PUBLICATIONS, $publication-id)?base-path($volume, $id) + map:get($config:PUBLICATIONS, $publication-id)?base-path($document-id, $section-id) else () let $html := if ($xml instance of element(tei:pb)) then - let $href := concat('//', $config:S3_DOMAIN, '/frus/', substring-before(util:document-name($xml), '.xml') (:ACK why is this returning blank?!?! root($xml)/tei:TEI/@xml:id:), '/medium/', $xml/@facs, '.png') + let $href := concat('//', $config:S3_DOMAIN, '/frus/', $document-id, '/medium/', $xml/@facs, '.png') return
@@ -63,7 +119,7 @@ return else pages:process-content($odd, pages:get-content($xml), map { "base-uri": $base-path }) let $html := app:fix-links($html) - let $doc := replace($volume, "^.*/([^/]+)$", "$1") + let $doc := replace($document-id, "^.*/([^/]+)$", "$1") let $model := map { "odd": $odd, "data": $xml @@ -74,12 +130,12 @@ return else pages:deep-section-breadcrumbs(, $model, true()) let $mediaLink := - if (fh:media-exists($volume, $id, ".pdf")) then - fh:pdf-url($volume, $id) + if (fh:media-exists($document-id, $section-id, ".pdf")) then + fh:pdf-url($document-id, $section-id) else () let $head := - if ($id) then + if ($section-id) then if ($xml instance of element(tei:div)) then $xml/tei:head[1] ! functx:remove-elements-deep(., 'note') else @@ -87,7 +143,7 @@ return (: we can't trust pages:load-xml for the purposes of finding a document's title, since it returns the document's first descendant div :) (: allow for pages that don't have $config:PUBLICATIONS?select-document defined :) else if ($publication-id and map:contains(map:get($config:PUBLICATIONS, $publication-id), 'select-document')) then - map:get($config:PUBLICATIONS, $publication-id)?select-document($volume)//tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:title[@type = 'complete'] + map:get($config:PUBLICATIONS, $publication-id)?select-document($document-id)//tei:teiHeader/tei:fileDesc/tei:titleStmt/tei:title[@type = 'complete'] (: allow for pages that don't have an entry in $config:PUBLICATIONS at all :) else () @@ -116,7 +172,6 @@ return "windowTitle": normalize-space(string-join(($head, $doc-title, $publication-title, "Office of the Historian")[. ne ''], " - ")), "toc": if ($toc) then toc:toc($model, $xml, true(), true()) else (), "tocCurrent": $xml/ancestor-or-self::tei:div[@type != "document"][1]/@xml:id/string(), - (: "breadcrumbSection": fh:breadcrumb-heading($model, $xml), :) "breadcrumbSection": $breadcrumbs, "persons": let $persons := @@ -142,5 +197,6 @@ return ) } + ) else map { "error": "Not found" }