Created
June 4, 2018 00:53
-
-
Save grantmacken/d37982cda61d241ed41932af5d7534b5 to your computer and use it in GitHub Desktop.
xqRest router
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
xquery version "3.1"; | |
(:~ main app routes | |
@author Grant MacKenzie | |
@version 0.1 | |
this app is proxied behind openresty/nginx | |
the data for the app is separate from the app | |
------------------------------------------- | |
GET routes deliver a hyperlinked website | |
website: | |
- render docs in named collections - pages | |
- render docs in date archive - posts | |
- | |
pages/home/index.html - home-page | |
pages/about/index.html about-page | |
pages/tags/index.html list named tags | |
pages/tags/{tag-name} list entries tagged as | |
posts/{ID}.html a dated entry | |
posts/latest latest dated entries | |
posts/2016/10/01 this days entries | |
posts/2016/10 this months entries | |
posts/2016 this years entries | |
posts/ all posts | |
------------------------------------------- | |
:) | |
module namespace router = "http://example.com/#router"; | |
import module namespace templates="http://exist-db.org/xquery/templates"; | |
import module namespace system="http://exist-db.org/xquery/system"; | |
import module namespace util="http://exist-db.org/xquery/util"; | |
import module namespace map="http://www.w3.org/2005/xpath-functions/map"; | |
import module namespace req="http://exquery.org/ns/request"; | |
(: include my modules here vim-note: `gf` to go to at files below | |
import module namespace tt="http://example.com/#tt" at "lib/tt.xqm"; | |
import module namespace archive="http://example.com/#archive" at "../lib/archive.xqm"; | |
import module namespace note="http://example.com/#note" at "../lib/note.xqm"; | |
import module namespace site="http://example.com/#site" at "../render/site.xqm"; | |
import module namespace feed="http://example.com/#feed" at "../render/feed.xqm"; | |
import module namespace entry="http://example.com/#entry" at "../render/entry.xqm"; | |
import module namespace muURL="http://example.com/#muURL" at "../lib/muURL.xqm"; | |
import module namespace muUtility="http://example.com/#muUtility" at "../lib/muUtility.xqm"; | |
:) | |
(:############################################################################:) | |
declare namespace repo="http://exist-db.org/xquery/repo"; | |
declare namespace pkg="http://expath.org/ns/pkg"; | |
declare namespace rest="http://exquery.org/ns/restxq"; | |
declare namespace http="http://expath.org/ns/http-client"; | |
declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; | |
(: declare option output:method "html5"; :) | |
(: declare option output:html-version "5"; :) | |
(: declare option output:media-type "text/html"; :) | |
(: declare option output:indent "yes"; :) | |
(:Determine the application base from the current module load path :) | |
declare variable $router:base := | |
substring-before( system:get-module-load-path(), '/modules'); | |
declare variable $router:domain := | |
substring-after( $router:base, repo:get-root() ); | |
declare variable $router:root := substring-before( $router:base,'/apps/'); | |
declare variable $router:dPath := $router:root || '/data/' || $router:domain; | |
declare variable $router:dDocs := $router:dPath || '/docs'; | |
declare variable $router:dRecyle := $router:dPath || '/docs/recycle'; | |
declare variable $router:dPosts := $router:dPath || '/docs/posts'; | |
declare variable $router:dPages := $router:dPath || '/docs/pages'; | |
declare variable $router:dUploads := $router:dPath || '/docs/uploads'; | |
declare variable $router:dMentions := $router:dPath || '/docs/mentions'; | |
declare variable $router:dMedia := $router:dPath || '/media'; | |
declare variable $router:repo := doc($router:base || "/repo.xml"); | |
declare variable $router:pkg := doc($router:base || "/expath-pkg.xml"); | |
(: templates :) | |
declare variable $router:tPosts := $router:base || "/templates/posts"; | |
declare variable $router:tPages := $router:base || "/templates/pages"; | |
declare variable $router:tTags := $router:base || "/templates/tags"; | |
declare variable $router:nl := " "; | |
declare variable $router:wmValue := | |
"<https://" || $router:domain || "/webmention> rel='webmention'"; | |
declare variable $router:wmLink := | |
<http:header name="Link" value="{$router:wmValue}"/>; | |
declare variable $router:map := map { | |
'domain' := $router:domain, | |
'author' := $router:repo//repo:author/string(), | |
'website' := $router:repo//repo:website/string(), | |
'gravatar' := 'http://0.gravatar.com/avatar/0650d3fbdb61ed5d8709eda6b80c3e47', | |
'description' := $router:repo//repo:description/string(), | |
'version' := $router:pkg/pkg:package/@version/string(), | |
'title' := $router:pkg//pkg:title/string(), | |
'data-posts' := $router:dPosts, | |
'data-pages' := $router:dPages, | |
'data-mentions' := $router:dMentions, | |
'data-media' := $router:dMedia | |
}; | |
declare variable $router:config := map { | |
$templates:CONFIG_APP_ROOT := $router:base, | |
$templates:CONFIG_STOP_ON_ERROR := true(), | |
$templates:CONFIG_PARAM_RESOLVER := function($param as xs:string) as xs:string* {req:parameter($param)} | |
}; | |
declare variable $router:error := map { | |
'notFound' := QName( 'http://example.com/#router','documentNotAvailable'), | |
'wmError' : QName( 'http://example.com/#router','webmentionError') | |
}; | |
declare variable $router:lookup :=function($functionName as xs:string, $arity as xs:int) { | |
try { | |
function-lookup(xs:QName($functionName), $arity) | |
} catch * {()} | |
}; | |
declare | |
%rest:GET | |
%rest:path( "/example.com/pages/home.html") | |
%output:media-type("text/html") | |
%output:method("html") | |
function router:home() { | |
try { | |
let $templatePath := $router:tPages || "/" || "home.html" | |
let $template := | |
if (doc-available($templatePath)) then (doc($templatePath)) else ( | |
fn:error( | |
$router:error('notFound'), | |
'template not available on path: ' || substring-after( $templatePath , $router:base || '/') | |
) | |
) | |
return ( | |
<rest:response> | |
<http:response status="200" message="OK"> | |
{$router:wmLink} | |
</http:response> | |
</rest:response>, | |
templates:apply($template, $router:lookup, $router:map, $router:config ) | |
) | |
} | |
catch * {( | |
<rest:response> | |
<http:response status="200" message="OK"> | |
</http:response> | |
</rest:response>, | |
<div> | |
<h1> TODO! </h1> | |
<p>error code - {$err:code}</p> | |
<p>error description - {$err:description}</p> | |
<p>error line number- {$err:line-number}</p> | |
<p>error module - {$err:module}</p> | |
</div> | |
)} | |
}; | |
(: | |
posts templates | |
--------------- | |
html template based on 'kind of post' | |
- postType: entry/@kind/string() | |
gf: templates/posts/note.html | |
:) | |
declare | |
%rest:GET | |
%rest:path("/example.com/posts/{$id}") | |
%output:media-type("text/html") | |
%output:method("html5") | |
function router:posts($id as xs:string) { | |
try { | |
let $uid := substring-before($id,'.') | |
let $kindOfPost := | |
switch( substring( $id,1,1) ) | |
case 'n' return 'note' | |
case 'r' return 'reply' | |
case 'a' return 'article' | |
case 'p' return 'photo' | |
default return 'note' | |
let $docsPath := $router:dPosts || '/' || $uid | |
let $data := | |
if (doc-available($docsPath)) then (doc($docsPath)) else ( | |
fn:error($router:error('notFound'), $docsPath || ' : data doc not available on path' ) | |
) | |
let $templatePath := $router:tPosts || "/" || $kindOfPost || ".html" | |
let $template := | |
if (doc-available($templatePath)) then (doc($templatePath)) else ( | |
fn:error($router:error('notFound'),'template doc not available on path' ) | |
) | |
(: | |
create an new map by combining router:map ( site-wide stuff ) | |
:) | |
let $dataMap := map { | |
'kind' := $kindOfPost, | |
'id' := $data/entry/uid/string(), | |
'url' := $data/entry/url/string(), | |
'published' := $data/entry/published/string(), | |
'category' := if( $data/entry/category/text()) then ($data/entry/category/string()) else(), | |
'in-reply-to' := if( $data/entry/in-reply-to/text()) then ($data/entry/in-reply-to/string()) else(), | |
'syndicate-to' := if( $data/entry/syndicate-to/text()) then ($data/entry/syndicate-to/string()) else(), | |
'photo' := if( $data/entry/photo/text()) then ($data/entry/photo/string()) else(), | |
'content' := if( $data/entry/content ) then ($data/entry/content) else () | |
} | |
(: content is a node everything else a string or a sequence:) | |
let $map := map:new(( $router:map, $dataMap )) | |
return ( | |
<rest:response> | |
<http:response status="200" message="OK"> | |
{$router:wmLink} | |
</http:response> | |
</rest:response>, | |
templates:apply($template, $router:lookup, $map, $router:config ) | |
) | |
} | |
catch * { | |
if ( xs:string($err:code) eq 'router:documentNotAvailable' ) then ( | |
<rest:response> | |
<http:response status="404"/> | |
</rest:response>, | |
templates:apply( | |
doc($router:tPages || '/not-found.html'), | |
$router:lookup, | |
map:new(( $router:map, map { | |
'id' := substring-before($id, '.html'), | |
'module' := $err:module, | |
'code' := $err:code, | |
'line-number' := $err:line-number, | |
'description' := $err:description | |
} )), | |
$router:config | |
)) | |
else | |
( | |
<rest:response> | |
<http:response status="404"/> | |
</rest:response>, | |
<div> | |
<h1> TODO! </h1> | |
<p>{$id}</p> | |
<p>error code - {$err:code}</p> | |
<p>error description - {$err:description}</p> | |
<p>error line number- {$err:line-number}</p> | |
<p>error module - {$err:module}</p> | |
</div> | |
) | |
} | |
}; | |
declare | |
%rest:GET | |
%rest:path( "/example.com/tags/{$id}") | |
%output:media-type("text/html") | |
%output:method("html5") | |
function router:tags($id as xs:string) { | |
try { | |
let $templatePath := $router:tPosts || "/tags.html" | |
let $template := | |
if (doc-available($templatePath)) then (doc($templatePath)) else ( | |
fn:error($router:error('notFound'),'template doc not available on path' ) | |
) | |
let $sID := substring-before($id, '.html') | |
let $dataMap := map { | |
'tag' := $sID | |
} | |
let $map := map:new(( $router:map, $dataMap )) | |
return | |
templates:apply($template, $router:lookup, $map, $router:config ) | |
} | |
catch * {( | |
<rest:response> | |
<http:response status="404"/> | |
</rest:response>, | |
<div> | |
<h1> xx TODO! </h1> | |
<p>{$id}</p> | |
<p>error code - {$err:code}</p> | |
<p>error description - {$err:description}</p> | |
<p>error line number- {$err:line-number}</p> | |
<p>error module - {$err:module}</p> | |
</div> | |
)} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment