Upload xar packages to the public repository. New packages will not appear in the list of
- available packages immediately. Instead you will have to trigger an update afterwards, using
- the "publish" button below. This way you can upload multiple packages before publishing them.
+
Admin
+
View administrative information about packages and upload new ones.
diff --git a/build.properties b/build.properties
index c4db381..a4da2f2 100644
--- a/build.properties
+++ b/build.properties
@@ -4,4 +4,4 @@
#
project.name=public-repo
-project.version=1.1.0
+project.version=2.0.0
diff --git a/collection.xconf b/collection.xconf
deleted file mode 100644
index ee9f748..0000000
--- a/collection.xconf
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/controller.xql b/controller.xql
index f66166f..c471488 100644
--- a/controller.xql
+++ b/controller.xql
@@ -1,7 +1,10 @@
xquery version "3.1";
+import module namespace config="http://exist-db.org/xquery/apps/config" at "modules/config.xqm";
import module namespace login="http://exist-db.org/xquery/login" at "resource:org/exist/xquery/modules/persistentlogin/login.xql";
+declare namespace sm="http://exist-db.org/xquery/securitymanager";
+
declare variable $exist:path external;
declare variable $exist:resource external;
declare variable $exist:controller external;
@@ -9,10 +12,7 @@ declare variable $exist:prefix external;
declare variable $exist:root external;
declare variable $app-root-absolute-url :=
- (request:get-header("X-Forwarded-Proto"), request:get-scheme())[1]
- || "://"
- || (request:get-header("X-Forwarded-Server"), request:get-server-name())[1]
- || request:get-context-path()
+ request:get-context-path()
|| $exist:prefix
|| $exist:controller
;
@@ -30,33 +30,21 @@ else if ($exist:path eq "/") then
-else if ($exist:path eq "/public/apps.xml") then (
- response:set-header('Content-Type', 'application/xml'),
+else if ($exist:path eq "/public/apps.xml") then
-
+
-)
-else if ($exist:resource eq "update.xql") then
-
-
-
-
-
-
-
-
-
-
+
(: Protected resource: user is required to log in with valid credentials.
If the login fails or no credentials were provided, the request is redirected
to the login.html page. :)
else if ($exist:path eq "/admin.html") then
let $user := request:get-attribute("org.exist.public-repo.login.user")
return
- if ($user and (sm:is-dba($user) or "repo" = sm:get-user-groups($user))) then
+ if (exists($user) and sm:get-user-groups($user) = config:repo-permissions()?group) then
-
+
@@ -65,47 +53,62 @@ else if ($exist:path eq "/admin.html") then
-
+
+(: Protected resource :)
+else if ($exist:path eq "/put-package") then
+
+
+
+
else if (ends-with($exist:resource, ".html") and starts-with($exist:path, "/packages")) then
-
+
else if (ends-with($exist:resource, ".html")) then
- (: the html page is run through view.xql to expand templates :)
+ (: the html page is run through view.xq to expand templates :)
-
+
-else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".zip")) then
+else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".xar") or ends-with($exist:resource, ".zip")) then
+
+
+
+
+
+
+else if (contains($exist:path, "/public/") and (ends-with($exist:resource, ".png") or ends-with($exist:resource, ".svg"))) then
-
+
+
+
-else if ($exist:path eq "/find" or ends-with($exist:resource, ".zip")) then
+else if ($exist:path eq "/find") then
-
+
else if ($exist:resource eq "feed.xml") then
-
+
else if (contains($exist:path, "/$shared/")) then
@@ -117,16 +120,9 @@ else if (contains($exist:path, "/$shared/")) then
else if (contains($exist:path, "/resources/")) then
-
-
-
+
-else if (ends-with($exist:path, ".xml")) then
-
-
-
-
else
(: everything else is passed through :)
diff --git a/expath-pkg.xml.tmpl b/expath-pkg.xml.tmpl
index 067baea..b03a6bb 100644
--- a/expath-pkg.xml.tmpl
+++ b/expath-pkg.xml.tmpl
@@ -1,7 +1,7 @@
eXist-db Public Application Repository
-
-
+
+
diff --git a/index.html b/index.html
index 27c906d..8e612c9 100644
--- a/index.html
+++ b/index.html
@@ -1,5 +1,5 @@
-
+
Public Application Repository
@@ -10,7 +10,7 @@
Public Application Repository
Package Manager app.
Install the package directly from the Dashboard's Package Manager app. (By default the
Package Manager is configured to access the eXist-db public application repository feed,
- at http://demo.exist-db.org/exist/apps/public-repo/public/apps.xml. To check or
+ at https://exist-db.org/exist/apps/public-repo/public/apps.xml. To check or
edit the repository URL your Package Manager is using, go to your Dashboard, click on
the gear icon beneath the eXist-db icon, and you will see a field, "Public Repository
URL.")
- Please login. The user has to be a member of the "repo" or "dba" group.
+ Please login. The user has to be a member of the "repo" group.
-
\ No newline at end of file
+
diff --git a/meta/apps.xml b/meta/apps.xml
deleted file mode 100644
index 2a67653..0000000
--- a/meta/apps.xml
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/meta/packages.xml b/meta/packages.xml
deleted file mode 100644
index 82d2b8d..0000000
--- a/meta/packages.xml
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/modules/app.xql b/modules/app.xql
deleted file mode 100644
index a912e00..0000000
--- a/modules/app.xql
+++ /dev/null
@@ -1,254 +0,0 @@
-xquery version "3.0";
-
-module namespace app="http://exist-db.org/xquery/app";
-
-import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
-import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xql";
-
-declare function app:list-packages($node as node(), $model as map(*), $mode as xs:string?) {
- for $app in doc($config:apps-meta)//app
- let $show-details := false()
- order by lower-case($app/title)
- return
- app:package-to-list-item($app, $app/version, $show-details)
-};
-
-declare function app:view-package($node as node(), $model as map(*), $mode as xs:string?) {
- let $abbrev := request:get-parameter("abbrev", ())
- let $procVersion := request:get-parameter("eXist-db-min-version", "2.2.0")
- let $matching-abbrev := doc($config:apps-meta)//abbrev[. eq $abbrev]
- let $apps := $matching-abbrev/parent::app
- return
- (
- if (count($apps) gt 1) then
-
More than one package matches the requested convenient short
- name, {$abbrev}. The unique package names that
- match this short name are:
-
- {
- for $app in $apps
- let $name := $app/name
- order by $name
- return
-
{$name/string()}
- }
-
- The packages with versions available that are compatible with eXist {$procVersion} or higher are as follows:
-
- else
- ()
- ,
- let $listing :=
- for $app in $apps
- return
- (: catch requests for a package using its legacy "abbrev" and redirect
- : them to a URL using the app's current abbrev, to encourage use of the
- : current abbrev :)
- if ($matching-abbrev/@type eq "legacy") then
- let $current-abbrev := $app/abbrev[not(@type eq "legacy")]
- let $repoURL := concat(substring-before(request:get-uri(), "public-repo/"), "public-repo/")
- let $required-exist-version := $app/requires[@processor eq "http://exist-db.org"]/(@version, @semver-min)[1]
- let $info-url :=
- concat($repoURL, "packages/", $current-abbrev, ".html",
- if ($required-exist-version) then
- concat("?eXist-db-min-version=", $required-exist-version)
- else
- ()
- )
- return
- response:redirect-to(xs:anyURI($info-url))
- (: view current package info :)
- else
- let $app-versions := ($app, $app/other/version)
- let $compatible-xar := app:find-version($app-versions, $procVersion, (), (), (), ())
- let $package := $app-versions[@path eq $compatible-xar]
- let $version := ($package/@version, $package/version)[1]
- let $show-details := true()
- return
- if (exists($package)) then
- app:package-to-list-item($app, $version, $show-details)
- else
-
The package with convenient short name {$abbrev} and with unique package name {$app/name} is not compatible with eXist {$procVersion} and requires a newer version of eXist.
- return
- if (exists($listing)) then
- $listing
- else
- (
- response:set-status-code(404),
-
No package {$abbrev} is available.
- )
- )
-};
-
-declare function app:package-to-list-item($app as element(app), $version as xs:string, $show-details as xs:boolean) {
- let $repoURL := concat(substring-before(request:get-uri(), "public-repo/"), "public-repo/")
- let $icon :=
- if ($app/icon) then
- if ($app/@status) then
- $app/icon[1]
- else
- $repoURL || "public/" || $app/icon[1]
- else
- $repoURL || "resources/images/package.png"
- let $path := ($app//version[@version eq $version]/@path, $app/@path)[1]
- let $requires := ($app//version[@version eq $version]/requires, $app/requires)[1]
- let $download-url := concat($repoURL, "public/", $path)
- let $required-exist-version := $requires[@processor eq "http://exist-db.org"]/(@version, @semver-min)[1]
- let $info-url :=
- concat($repoURL, "packages/", $app/abbrev[not(@type eq "legacy")], ".html",
- if ($required-exist-version) then
- concat("?eXist-db-min-version=", $required-exist-version)
- else
- ()
- )
- return
-
-
-
-
- {
- switch ($app/type)
- case ("application") return
-
- case ("library") return
-
- case ("plugin") return
-
- default return ()
- }
-
{
- let $versions := reverse($app/other/version)
- for $version at $n in $versions
- let $download-version-url := concat($repoURL, "public/", $version/@path)
- return
- (
- {$version/@version/string()}
- ,
- if ($n lt count($versions)) then ", " else ()
- )
- }
-
- else ()
- }
- {
- if ($app/changelog/change) then
- (
-
-
Change log:
-
- ,
- for $change in $app/changelog/change
- let $version := $change/@version/string()
- let $comment := $change/node()
- order by $version descending
- return
-
-
{$version}
-
{$comment}
-
- )
- else ()
- }
-
- else
-
- {$app/description/text()}
-
- Version {$version} {
- if ($requires) then
- concat(" (Requires eXist-db ", app:requires-to-english($requires), ".)")
- else
- ()
- }
-
- Read more information, or download {$app/@path/string()}.
-
- }
-
-};
-
-declare function app:find-version($apps as element()*, $procVersion as xs:string, $version as xs:string?, $semVer as xs:string?, $min as xs:string?, $max as xs:string?) {
- if (empty($apps)) then
- ()
- else
- if ($semVer) then
- scanrepo:find-version($apps, $semVer, $semVer)/@path
- else if ($version) then
- $apps[version = $version]/@path | $apps[@version = $version]/@path
- else if ($min or $max) then
- scanrepo:find-version($apps, $min, $max)/@path
- else
- scanrepo:find-newest($apps, (), $procVersion)/@path
-};
-
-declare function app:requires-to-english($requires as element()) {
- (: we assume @processor="http://exist.db-org/" :)
- if ($requires/@version) then
- concat(" version ", $requires/@version)
- else if ($requires/@semver) then
- concat(" version ", $requires/@semver)
- else if ($requires/@semver-min) then
- concat(" version ", $requires/@semver-min, " or later")
- else if ($requires/@semver-max) then
- concat(" version ", $requires/@semver-max, " or earlier")
- else
- " version 2.2"
-};
diff --git a/modules/app.xqm b/modules/app.xqm
new file mode 100644
index 0000000..200f1ec
--- /dev/null
+++ b/modules/app.xqm
@@ -0,0 +1,452 @@
+xquery version "3.1";
+
+(:~
+ : HTML templating functions for populating web views of the public-repo
+ :)
+
+module namespace app="http://exist-db.org/xquery/app";
+
+import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
+import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm";
+import module namespace templates="http://exist-db.org/xquery/templates";
+import module namespace versions="http://exist-db.org/apps/public-repo/versions" at "versions.xqm";
+
+declare namespace request="http://exist-db.org/xquery/request";
+declare namespace response="http://exist-db.org/xquery/response";
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+
+(:~
+ : Load the package groups document for the admin page's package-groups section
+ :)
+declare %templates:wrap function app:load-package-groups($node as node(), $model as map(*)) {
+ map {
+ "package-groups":
+ for $package-group in doc($config:package-groups-doc)//package-group
+ order by $package-group/abbrev[not(@type)]
+ return
+ $package-group,
+ "dates-published":
+ for $put in collection($config:logs-col)//event[type eq "put-package"]
+ group by $package-name := $put/package-name/string()
+ return
+ map {
+ "package-name": $package-name,
+ "versions": $put ! map { "version": ./package-version/string(), "date-published": ./dateTime cast as xs:dateTime }
+ }
+ }
+};
+
+(:~
+ : Load the package title for the admin page's package-groups section
+ :)
+declare function app:package-group-title($node as node(), $model as map(*)) {
+ $model?package-group/title/string()
+};
+
+(:~
+ : Load the package title for the admin page's package-groups section
+ :)
+declare function app:package-group-abbrev($node as node(), $model as map(*)) {
+ $model?package-group/abbrev/string()
+};
+
+(:~
+ : Load the package title for the admin page's package-groups section
+ :)
+declare function app:package-group-name($node as node(), $model as map(*)) {
+ $model?package-group/name/string()
+};
+
+(:~
+ : Load the packages for each package-group
+ :)
+declare %templates:wrap function app:load-packages($node as node(), $model as map(*)) {
+ map { "packages": $model?package-group//package }
+};
+
+
+(:~
+ : Load the package version
+ :)
+declare function app:package-version($node as node(), $model as map(*)) {
+ $model?package/version
+};
+
+(:~
+ : Load the package version
+ :)
+declare function app:package-requires($node as node(), $model as map(*)) {
+ let $requires := $model?package/requires[@processor eq "http://exist-db.org"]
+ return
+ ($requires/@* except $requires/@processor) ! (./name() || ": " || ./string())
+};
+
+(:~
+ : Load the package version
+ :)
+declare function app:package-date-published($node as node(), $model as map(*)) {
+ let $date-published := $model?dates-published[?package-name eq $model?package/name]?versions[?version eq $model?package/version]?date-published => head()
+ return
+ if (exists($date-published)) then
+ if (xs:date($date-published) = current-date()) then
+ format-dateTime($date-published, "Today [H00]:[m00]:[s00]")
+ else
+ format-dateTime($date-published, "[M00]/[D00]/[Y0000] [H00]:[m00]:[s00]")
+ else
+ "- (Predates logging)"
+};
+
+(:~
+ : Load the get-package logs for the admin section's table
+ :)
+declare %templates:wrap function app:load-get-package-logs-for-admin-table($node as node(), $model as map(*), $top-n as xs:integer) {
+ let $package-logs :=
+ for $event in collection($config:logs-col)//event[type eq "get-package"]
+ group by $package-name := $event/package-name/string()
+ let $count := count($event)
+ order by $count descending
+ return
+ map {
+ "package-name": $package-name,
+ "count": $count
+ }
+ return
+ map { "package-logs":
+ subsequence($package-logs, 1, $top-n)
+ }
+};
+
+(:~
+ : Load the package title for the admin section's table
+ :)
+declare function app:get-package-stats($node as node(), $model as map(*)) {
+ $model?package-log?package-name || " (" || $model?package-log?count || ")"
+};
+
+(:~
+ : Rebuild the package-groups metadata
+ :)
+declare function app:rebuild-package-groups-metadata($node as node(), $model as map(*), $rebuild-package-groups-metadata as xs:boolean?) {
+ if ($rebuild-package-groups-metadata) then
+ let $_ := scanrepo:rebuild-package-groups()
+ return
+
The package-groups metadata has been rebuilt.
+ else
+ ()
+};
+
+(:~
+ : Landing page - show the compact version of all package groups
+ :)
+declare function app:list-packages($node as node(), $model as map(*), $mode as xs:string?) {
+ for $package-group in doc($config:package-groups-doc)//package-group
+ let $show-details := false()
+ order by lower-case($package-group/title)
+ return
+ app:package-group-to-list-item($package-group, (), (), $show-details)
+};
+
+(:~
+ : Single package group view - show the full version of this package group
+ :
+ : Package is found via the abbrev URL parameter, with an optional eXist version parameter.
+ : If the eXist version parameter is missing, eXist 2.2.0 is assumed (see config.xqm).
+ :)
+declare function app:view-package($node as node(), $model as map(*), $mode as xs:string?) {
+ let $abbrev := request:get-parameter("abbrev", ())
+ let $procVersion := request:get-parameter("eXist-db-min-version", $config:default-exist-version)
+ let $package-groups := doc($config:package-groups-doc)//package-group[abbrev eq $abbrev]
+ return
+ (
+ if (count($package-groups) gt 1) then
+
More than one package matches the requested convenient short
+ name, {$abbrev}. The unique package names that
+ match this short name are:
+
+ {
+ for $package-group in $package-groups
+ let $name := $package-group/name
+ order by $name
+ return
+
{$name/string()}
+ }
+
+ The packages with versions available that are compatible with eXist {$procVersion} or higher are as follows:
+
+ else
+ ()
+ ,
+ let $listing :=
+ for $package-group in $package-groups
+ return
+ (: catch requests for a package using its legacy "abbrev" and redirect
+ : them to a URL using the app's current abbrev, to encourage use of the
+ : current abbrev :)
+ if ($package-group/abbrev[. eq $abbrev]/@type eq "legacy") then
+ let $current-abbrev := $package-group/abbrev[not(@type eq "legacy")]
+ (: TODO shouldn't we get $repoURL from $config? - joewiz :)
+ let $repoURL := concat(substring-before(request:get-uri(), "public-repo/"), "public-repo/")
+ let $newest-compatible-package := head($package-group//package[abbrev eq $abbrev])
+ let $required-exist-version := $newest-compatible-package/requires[@processor eq $config:exist-processor-name]/(@version, @semver-min)[1]
+ let $info-url :=
+ concat($repoURL, "packages/", $current-abbrev, ".html",
+ if ($required-exist-version) then
+ concat("?eXist-db-min-version=", $required-exist-version)
+ else
+ ()
+ )
+ return
+ response:redirect-to(xs:anyURI($info-url))
+ (: view current package info :)
+ else
+ let $packages := $package-group//package
+ let $compatible-packages := versions:find-compatible-packages($packages, $procVersion)
+ let $incompatible-packages := $packages except $compatible-packages
+ let $show-details := true()
+ return
+ if ($compatible-packages or $incompatible-packages) then
+ let $trimmed-package-group :=
+ element package-group {
+ $package-group/(title, name, abbrev),
+ $compatible-packages
+ }
+ return
+ app:package-group-to-list-item($trimmed-package-group, $incompatible-packages, $procVersion, $show-details)
+ else
+
The package with convenient short name {$abbrev} and with unique package name {$package-group/name} is not compatible with eXist {$procVersion} and requires a newer version of eXist.
+ return
+ if (exists($listing)) then
+ $listing
+ else
+ (
+ response:set-status-code(404),
+
No package {$abbrev} is available.
+ )
+ )
+};
+
+(:~
+ : Used by all HTML listings of packages - landing page views of all packages and interior views of individual package groups
+ :)
+declare function app:package-group-to-list-item($package-group as element(package-group), $incompatible-packages as element(package)*, $procVersion as xs:string?, $show-details as xs:boolean) {
+ (: TODO shouldn't we get $repoURL from $config? - joewiz :)
+ let $repoURL := concat(substring-before(request:get-uri(), "public-repo/"), "public-repo/")
+ let $packages := $package-group//package
+ let $newest-package := head($packages)
+ let $older-packages := tail($packages)
+ let $icon :=
+ if ($newest-package/icon) then
+ if ($newest-package/@status) then
+ $newest-package/icon[1]
+ else
+ $repoURL || "public/" || $newest-package/icon[1]
+ else
+ $repoURL || "resources/images/package.png"
+ let $path := $newest-package/@path
+ let $requires := $newest-package/requires
+ let $download-url := concat($repoURL, "public/", $path)
+ let $required-exist-version := $requires[@processor eq $config:exist-processor-name]/(@version, @semver-min)[1]
+ let $info-url :=
+ concat($repoURL, "packages/", $package-group/abbrev[not(@type eq "legacy")], ".html",
+ if ($required-exist-version) then
+ concat("?eXist-db-min-version=", $required-exist-version)
+ else
+ ()
+ )
+ return
+
+
+
+
+ {
+ switch ($newest-package/type)
+ case ("application") return
+
+ case ("library") return
+
+ case ("plugin") return
+
+ default return ()
+ }
+
No versions of {string-join($package-group/abbrev, " or ")} found that are compatible with eXist-db {$procVersion}+.
+ }
+ {
+ if ($older-packages or $incompatible-packages) then
+
+
Download other versions:
+
+
{
+ (: show links to older versions of the package that are compatible with the requested version of eXist :)
+ for $package in reverse($older-packages)
+ let $download-version-url := concat($repoURL, "public/", $package/@path)
+ return
+
,
+
+ (: show links to any other version of the package that is compatible with the requested version of eXist,
+ but show the requirement that isn't met. use case: crypto library, whose abbrev has changed and whose
+ eXist requirements changed in odd ways. for example:
+ - 1. crypto@1.0.0 requires eXist-db 5.0.0-RC8+
+ - 2. expath-crypto-exist-lib@5.3.0 requires eXist-db 4.4.0+
+ - 3. both versions share the same package name: http://expath.org/ns/crypto.
+ - so based on package version, #2 is the newest package
+ - but based on eXist version, #1 is more compatible with eXist 5.x
+ - a messy situation, but it's better to show this info than hide packages from view
+ :)
+ for $package in $incompatible-packages
+ let $download-version-url := concat($repoURL, "public/", $package/@path)
+ let $requires := $package/requires
+ return
+
+ { $package/version/string() }
+ {
+ " (Note: Requires eXist-db "
+ || app:requires-to-english($requires)
+ ||
+ (
+ if ($package/abbrev ne $package-group/abbrev[not(@type = "legacy")]) then
+ (". This version's short title is “" || $package/abbrev || "”")
+ else
+ ()
+ )
+ || ".)"
+ }
+
+ }
+
+
+ else ()
+ }
+ {
+ if (exists($newest-package/changelog/change)) then
+ (
+
+
Change log:
+
+ ,
+ for $change in $newest-package/changelog/change
+ let $version := $change/@version/string()
+ let $comment := $change/node()
+ order by $version descending
+ return
+
+
{$version}
+
{$comment}
+
+ )
+ else ()
+ }
+
+ else
+
+ {$newest-package/description/string()}
+
+ Version {$newest-package/version/string()} {
+ if ($requires) then
+ concat(" (Requires eXist-db ", app:requires-to-english($requires), ".)")
+ else
+ ()
+ }
+
+ Read more information, or download {$newest-package/@path/string()}.
+
+ }
+
+};
+
+(:~
+ : Express eXist version requirements in human readable form
+ :)
+declare function app:requires-to-english($requires as element()) {
+ (: we assume @processor="http://exist.db-org/" :)
+ if ($requires/@version) then
+ concat(" version ", $requires/@version)
+ else if ($requires/@semver) then
+ concat(" version ", $requires/@semver)
+ else if ($requires/@semver-min) then
+ concat(" version ", $requires/@semver-min, " or later")
+ else if ($requires/@semver-max) then
+ concat(" version ", $requires/@semver-max, " or earlier")
+ else
+ " version " || $config:default-exist-version
+};
+
+(:~
+ : Utility function for app:mkcol
+ :)
+declare
+ %private
+function app:mkcol-recursive($collection as xs:string, $components as xs:string*) {
+ if (exists($components)) then
+ let $newColl := concat($collection, "/", $components[1])
+ return (
+ xmldb:create-collection($collection, $components[1]),
+ app:mkcol-recursive($newColl, subsequence($components, 2))
+ )
+ else
+ ()
+};
+
+(:~
+ : Recursively create a collection hierarchy
+ :)
+declare function app:mkcol($collection as xs:string, $path as xs:string) {
+ app:mkcol-recursive($collection, tokenize($path, "/"))
+};
+
diff --git a/modules/auto-update.xql b/modules/auto-update.xql
deleted file mode 100644
index d761218..0000000
--- a/modules/auto-update.xql
+++ /dev/null
@@ -1,16 +0,0 @@
-xquery version "3.0";
-
-module namespace trigger="http://exist-db.org/xquery/trigger";
-import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "xmldb:exist:///db/apps/public-repo/modules/scan.xql";
-
-declare function trigger:after-create-document($uri as xs:anyURI) {
- if (ends-with($uri, ".xar")) then scanrepo:scan() else ()
-};
-
-declare function trigger:after-update-document($uri as xs:anyURI) {
- if (ends-with($uri, ".xar")) then scanrepo:scan() else ()
-};
-
-declare function trigger:after-delete-document($uri as xs:anyURI) {
- if (ends-with($uri, ".xar")) then scanrepo:scan() else ()
-};
diff --git a/modules/config.xqm b/modules/config.xqm
index be7d211..d0af083 100644
--- a/modules/config.xqm
+++ b/modules/config.xqm
@@ -1,15 +1,19 @@
+xquery version "3.1";
+
(:~
- : A set of helper functions to access the application context from
- : within a module.
+ : Configuration options for the application and a set of helper functions to access
+ : the application context.
:)
+
module namespace config="http://exist-db.org/xquery/apps/config";
-declare namespace repo="http://exist-db.org/xquery/repo";
+declare namespace system="http://exist-db.org/xquery/system";
+
declare namespace expath="http://expath.org/ns/pkg";
+declare namespace repo="http://exist-db.org/xquery/repo";
+
+(: Determine the application root collection from the current module load path :)
-(:
- Determine the application root collection from the current module load path.
-:)
declare variable $config:app-root :=
let $rawPath := system:get-module-load-path()
let $modulePath :=
@@ -17,6 +21,8 @@ declare variable $config:app-root :=
if (starts-with($rawPath, "xmldb:exist://")) then
if (starts-with($rawPath, "xmldb:exist://embedded-eXist-server")) then
substring($rawPath, 36)
+ else if (starts-with($rawPath, "xmldb:exist://null")) then
+ substring($rawPath, 19)
else
substring($rawPath, 15)
else
@@ -25,13 +31,39 @@ declare variable $config:app-root :=
substring-before($modulePath, "/modules")
;
-declare variable $config:public := concat($config:app-root, "/public");
+(: Default collection and resource names for binary assets and extracted package metadata :)
+
+declare variable $config:app-data-parent-col := "/db/apps";
+declare variable $config:app-data-col-name := "public-repo-data";
+declare variable $config:packages-col-name := "packages";
+declare variable $config:icons-col-name := "icons";
+declare variable $config:metadata-col-name := "metadata";
+declare variable $config:logs-col-name := "logs";
+
+declare variable $config:app-data-col := $config:app-data-parent-col || "/" || $config:app-data-col-name;
+declare variable $config:packages-col := $config:app-data-col || "/" || $config:packages-col-name;
+declare variable $config:icons-col := $config:app-data-col || "/" || $config:icons-col-name;
+declare variable $config:metadata-col := $config:app-data-col || "/" || $config:metadata-col-name;
+declare variable $config:logs-col := $config:app-data-col || "/" || $config:logs-col-name;
+declare function config:log-subcol($date as xs:date) { format-date($date, "[Y]/[M01]") };
+declare function config:log-col($date as xs:date) { $config:logs-col || "/" || config:log-subcol($date) };
-declare variable $config:metadata-collection := concat($config:app-root, "/meta");
-declare variable $config:apps-doc := 'apps.xml';
-declare variable $config:packages-doc := 'packages.xml';
-declare variable $config:packages-meta := concat($config:metadata-collection, '/', $config:packages-doc);
-declare variable $config:apps-meta := concat($config:metadata-collection, '/', $config:apps-doc);
+declare variable $config:package-groups-doc-name := "package-groups.xml";
+declare variable $config:raw-packages-doc-name := "raw-packages.xml";
+declare function config:log-doc-name($date as xs:date) { "public-repo-log-" || format-date($date, "[Y]-[M01]-[D01]") || ".xml" };
+
+declare variable $config:package-groups-doc := $config:metadata-col || "/" || $config:package-groups-doc-name;
+declare variable $config:raw-packages-doc := $config:metadata-col || "/" || $config:raw-packages-doc-name;
+declare function config:log-doc($date as xs:date) { config:log-col($date) || "/" || config:log-doc-name($date) };
+
+(: The default version number here is assumed when a client does not send a version parameter.
+ It is set to 2.2.0 because this version was the last one known to work with most older packages
+ before packages began to declare their version constraints in their package metadata.
+ So this should stay as 2.2.0 until we (a) no longer have 2.2-era clients or (b) no longer have
+ packages that we care to offer compatibility with 2.2.
+ :)
+declare variable $config:default-exist-version := "2.2.0";
+declare variable $config:exist-processor-name := "http://exist-db.org";
(:~
: Returns the repo.xml descriptor for the current application.
@@ -40,6 +72,17 @@ declare function config:repo-descriptor() as element(repo:meta) {
doc(concat($config:app-root, "/repo.xml"))/repo:meta
};
+(:~
+ : Returns the user and group from the repo.xml descriptor.
+ :)
+declare function config:repo-permissions() as map(*) {
+ config:repo-descriptor()/repo:permissions !
+ map {
+ "user": ./@user/string(),
+ "group": ./@group/string()
+ }
+};
+
(:~
: Returns the expath-pkg.xml descriptor for the current application.
:)
diff --git a/modules/download-xar-zip.xq b/modules/download-xar-zip.xq
deleted file mode 100644
index d413684..0000000
--- a/modules/download-xar-zip.xq
+++ /dev/null
@@ -1,17 +0,0 @@
-xquery version "3.0";
-
-import module namespace compression="http://exist-db.org/xquery/compression";
-import module namespace response="http://exist-db.org/xquery/response";
-import module namespace util="http://exist-db.org/xquery/util";
-
-import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
-
-let $pkg-file-name := fn:replace(request:get-url(), ".*/(.*)\.zip", "$1")
-let $xar := util:binary-doc($config:app-root || "/public/" || $pkg-file-name)
-return
- let $entry :=
- {$xar}
- let $zip := compression:zip($entry, false())
- return
- response:stream-binary($zip, "application/zip")
-
diff --git a/modules/feed.xql b/modules/feed.xq
similarity index 63%
rename from modules/feed.xql
rename to modules/feed.xq
index 71c1cd5..aab9ccd 100644
--- a/modules/feed.xql
+++ b/modules/feed.xq
@@ -1,47 +1,68 @@
-xquery version "3.0";
+xquery version "3.1";
+
+(:~
+ : Generate an Atom feed of packages, with an entry for the newest version of each package
+ :)
import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
-declare option exist:serialize "method=xml media-type=application/atom+xml";
+declare namespace request="http://exist-db.org/xquery/request";
+declare namespace util="http://exist-db.org/xquery/util";
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+
+declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
+
+declare option output:method "xml";
+declare option output:media-type "application/atom+xml";
+
+declare function local:add-xhtml-ns($nodes) {
+ for $node in $nodes
+ return
+ if ($node instance of element()) then
+ element { QName("http://www.w3.org/1999/xhtml", local-name($node)) } { local:add-xhtml-ns($node/node()) }
+ else
+ $node
+};
declare function local:feed-entries() {
let $repoURL := concat(substring-before(request:get-url(), "public-repo/"), "public-repo/")
- for $app in doc($config:apps-meta)//app
+ for $package-group in doc($config:package-groups-doc)//package-group
+ let $newest-package := head($package-group//package)
let $icon :=
- if ($app/icon) then
- if ($app/@status) then
- $app/icon[1]
+ if ($newest-package/icon) then
+ if ($newest-package/@status) then
+ $newest-package/icon[1]
else
- $repoURL || "public/" || $app/icon[1]
+ $repoURL || "public/" || $newest-package/icon[1]
else
$repoURL || "resources/images/package.png"
- let $required-exist-version := $app/requires[@processor eq "http://exist-db.org"]/(@version, @semver-min)[1]
+ let $required-exist-version := $newest-package/requires[@processor eq $config:exist-processor-name]/(@version, @semver-min)[1]
let $info-url :=
- concat($repoURL, "packages/", $app/abbrev[not(@type eq "legacy")], ".html",
+ concat($repoURL, "packages/", $newest-package/abbrev[not(@type eq "legacy")], ".html",
if ($required-exist-version) then
concat("?eXist-db-min-version=", $required-exist-version)
else
()
)
- let $title := $app/title
- let $version := $app/version
- let $authors := $app/author
- let $description := $app/description
- let $license := $app/license
- let $website := $app/website
- let $has-changelog := $app/changelog/*
+ let $title := $newest-package/title
+ let $version := $newest-package/version
+ let $authors := $newest-package/author
+ let $description := $newest-package/description
+ let $license := $newest-package/license
+ let $website := $newest-package/website
+ let $has-changelog := $newest-package/changelog/*
let $changes :=
if ($has-changelog) then
- for $change in $app/changelog/change
+ for $change in $newest-package/changelog/change
let $version := $change/@version
- let $comment := $change/node()
+ let $comment := local:add-xhtml-ns($change/node())
return
(
Version { $version/string() }
,
{ $comment }
)
else ()
- let $updated := xmldb:last-modified($config:public, $app/@path)
+ let $updated := xmldb:last-modified($config:packages-col, $newest-package/@path)
let $content :=
@@ -90,7 +111,7 @@ declare function local:feed() {
let $subtitle := "Repository for apps and libraries on eXist-db.org."
let $self-href := request:get-url()
let $id := "urn:uuid:" || util:uuid("existdb-public-package-repository-feed")
- let $updated := xmldb:last-modified($config:public, "apps.xml")
+ let $updated := xmldb:last-modified($config:packages-col, "apps.xml")
let $feed-entries := local:feed-entries()
return
@@ -103,4 +124,4 @@ declare function local:feed() {
};
-local:feed()
\ No newline at end of file
+local:feed()
diff --git a/modules/find.xq b/modules/find.xq
new file mode 100644
index 0000000..5cce6d9
--- /dev/null
+++ b/modules/find.xq
@@ -0,0 +1,55 @@
+xquery version "3.1";
+
+(:~
+ : Respond to eXist build requests for packages using various identifier and version number criteria
+ :
+ : The info parameter can be used for troubleshooting
+ :)
+
+import module namespace app="http://exist-db.org/xquery/app" at "app.xqm";
+import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
+import module namespace versions="http://exist-db.org/apps/public-repo/versions" at "versions.xqm";
+
+declare namespace request="http://exist-db.org/xquery/request";
+declare namespace response="http://exist-db.org/xquery/response";
+
+let $abbrev := request:get-parameter("abbrev", ())
+let $name := request:get-parameter("name", ())
+let $exist-version-semver := request:get-parameter("processor", $config:default-exist-version)
+let $version := request:get-parameter("version", ())
+let $semver := request:get-parameter("semver", ())
+let $semver-min := request:get-parameter("semver-min", ())
+let $semver-max := request:get-parameter("semver-max", ())
+let $zip := request:get-parameter("zip", ())
+let $info := request:get-parameter("info", ())
+let $app-root-absolute-url := request:get-parameter("app-root-absolute-url", ())
+
+let $package-group :=
+ if ($name) then
+ doc($config:package-groups-doc)//package-group[name eq $name]
+ else
+ doc($config:package-groups-doc)//package-group[abbrev eq $abbrev]
+
+let $newest-compatible-package := versions:find-newest-compatible-package($package-group//package, $exist-version-semver, $version, $semver, $semver-min, $semver-max)
+
+return
+ if ($newest-compatible-package) then
+ (: TODO shouldn't we get $abs-public from $config? - joewiz :)
+ let $abs-public := $app-root-absolute-url || "/public/"
+ let $xar-filename := $newest-compatible-package/@path
+ return
+ if ($info) then
+ element found {
+ $newest-compatible-package/@sha256,
+ $newest-compatible-package/version ! attribute version {.},
+ $newest-compatible-package/@path
+ }
+ else if ($zip) then
+ response:redirect-to(xs:anyURI($abs-public || $xar-filename || ".zip"))
+ else
+ response:redirect-to(xs:anyURI($abs-public || $xar-filename))
+ else
+ (
+ response:set-status-code(404),
+
+ )
diff --git a/modules/get-package.xq b/modules/get-package.xq
new file mode 100644
index 0000000..db09a08
--- /dev/null
+++ b/modules/get-package.xq
@@ -0,0 +1,60 @@
+xquery version "3.1";
+
+(:~
+ : Allows download of packages
+ :
+ : Responds to requests like:
+ : - /exist/apps/public-repo/public/eXide-1.0.0.xar
+ : - /exist/apps/public-repo/public/eXide-1.0.0.xar.zip
+ :)
+
+import module namespace app="http://exist-db.org/xquery/app" at "app.xqm";
+import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
+
+declare namespace request="http://exist-db.org/xquery/request";
+declare namespace response="http://exist-db.org/xquery/response";
+declare namespace util="http://exist-db.org/xquery/util";
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+
+declare function local:log-get-package-event($filename as xs:string) as empty-sequence() {
+ let $package := doc($config:raw-packages-doc)//package[@path eq $filename]
+ let $today := current-date()
+ let $log-doc := config:log-doc($today)
+ let $event :=
+ element event {
+ element dateTime { current-dateTime() },
+ element type { "get-package" },
+ element package-name { $package/name/string() },
+ element package-version { $package/version/string() }
+ }
+ let $update-log :=
+ if (doc-available($log-doc)) then
+ update insert $event into doc($log-doc)/public-repo-log
+ else
+ (
+ app:mkcol($config:logs-col, config:log-subcol($today)),
+ xmldb:store(config:log-col($today), config:log-doc-name($today), element public-repo-log { $event } )
+ )
+ return
+ ()
+};
+
+let $filename := request:get-parameter("filename", ())
+let $xar-filename :=
+ (: strip .zip from resource name :)
+ if (ends-with($filename, ".zip")) then
+ replace($filename, ".zip$", "")
+ else
+ $filename
+let $path := $config:packages-col || "/" || $xar-filename
+return
+ if (util:binary-doc-available($path)) then
+ let $xar := util:binary-doc($config:packages-col || "/" || $xar-filename)
+ let $log := local:log-get-package-event($filename)
+ return
+ response:stream-binary($xar, "application/zip")
+ else
+ (
+ response:set-status-code(404),
+
Package file not found!
+ )
diff --git a/modules/list.xq b/modules/list.xq
new file mode 100644
index 0000000..a698bf2
--- /dev/null
+++ b/modules/list.xq
@@ -0,0 +1,55 @@
+xquery version "3.1";
+
+(:~
+ : Filter all package groups, returning a list of only the compatible versions.
+ :
+ : The format of the results preserves compatibility with the package-repo v1.x API
+ :)
+
+import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
+import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm";
+import module namespace semver="http://exist-db.org/xquery/semver";
+import module namespace versions="http://exist-db.org/apps/public-repo/versions" at "versions.xqm";
+
+declare namespace request="http://exist-db.org/xquery/request";
+
+declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
+
+declare option output:method "xml";
+declare option output:media-type "application/xml";
+
+let $exist-version := request:get-parameter("version", ())
+let $basic-semver-regex := "^\d+\.\d+\.\d+-?.*$"
+let $exist-version-semver :=
+ if (matches($exist-version, $basic-semver-regex)) then
+ $exist-version
+ else
+ $config:default-exist-version
+return
+ element apps {
+ attribute version { $exist-version-semver },
+ for $package-group in doc($config:package-groups-doc)//package-group
+ let $compatible-packages := versions:find-compatible-packages($package-group//package, $exist-version-semver)
+ return
+ if (exists($compatible-packages)) then
+ let $newest-package := head($compatible-packages)
+ let $older-packages := tail($compatible-packages)
+ return
+ element app {
+ $newest-package/@*,
+ $newest-package/*,
+ if (exists($older-packages)) then
+ element older {
+ for $package in $older-packages
+ return
+ element version {
+ $package/@*,
+ $package/requires
+ }
+ }
+ else
+ ()
+ }
+ else
+ ()
+ }
diff --git a/modules/list.xql b/modules/list.xql
deleted file mode 100644
index d747ae7..0000000
--- a/modules/list.xql
+++ /dev/null
@@ -1,85 +0,0 @@
-xquery version "3.0";
-
-declare namespace list="http://exist-db.org/apps/public-repo/list";
-declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
-
-import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
-import module namespace semver = "http://exist-db.org/xquery/semver";
-
-declare option output:method "xml";
-declare option output:media-type "application/xml";
-
-(: The default version number here is assumed when a client does not send a version parameter.
- It is set to 2.2.0 because this version was the last one known to work with most older packages
- before packages began to declare their version constraints in their package metadata.
- So this should stay as 2.2.0 until we (a) no longer have 2.2-era clients or (b) no longer have
- packages that we care to offer compatibility with 2.2.
- :)
-declare variable $list:DEFAULT_VERSION := "2.2.0";
-
-declare function list:is-newer-or-same($version1 as xs:string, $version2 as xs:string?) {
- empty($version2) or
- semver:ge($version1, $version2, true())
-};
-
-declare function list:is-older-or-same($version1 as xs:string, $version2 as xs:string?) {
- empty($version2) or
- semver:le($version1, $version2, true())
-};
-
-declare function list:get-app($app as element(), $version as xs:string) {
- if ($app/requires) then
- let $min := $app/requires/@semver-min
- let $max := $app/requires/@semver-max
- return
- if ($min or $max) then
- if (list:is-newer-or-same($version, $app/requires/@semver-min) and
- list:is-older-or-same($version, $app/requires/@semver-max)) then
- $app
- else
- (: get older version :)
- list:versions($app, $version)
- else
- $app
- else
- $app
-};
-
-declare function list:versions($app as element(), $version as xs:string) {
- let $v := list:find-version($app/other/version, $version)
- return
- if ($v) then
-
- { $app/@* except $app/@path }
- { $v/@version/string() }
- { $app/* except $app/version }
-
- else
- ()
-};
-
-declare function list:find-version($versions as element()*, $version as xs:string) {
- if (empty($versions)) then
- ()
- else
- let $v := head($versions)
- let $req := $v/requires
- return
- if (list:is-newer-or-same($version, $req/@semver-min) and
- list:is-older-or-same($version, $req/@semver-max)) then
- $v
- else
- list:find-version(tail($versions), $version)
-};
-
-let $version := request:get-parameter("version", $list:DEFAULT_VERSION)
-let $version := if (matches($version, "^\d+\.\d+\.\d+-?.*$")) then $version else $list:DEFAULT_VERSION
-
-return
-
- {
- for $app in doc($config:apps-meta)//app
- return
- list:get-app($app, $version)
- }
-
diff --git a/modules/put-package.xq b/modules/put-package.xq
new file mode 100644
index 0000000..09be5dd
--- /dev/null
+++ b/modules/put-package.xq
@@ -0,0 +1,82 @@
+xquery version "3.1";
+
+(:~
+ : Receives uploaded packages and immediately publishes them to the package repository
+ :)
+
+import module namespace app="http://exist-db.org/xquery/app" at "app.xqm";
+import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
+import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm";
+
+declare namespace request="http://exist-db.org/xquery/request";
+declare namespace sm="http://exist-db.org/xquery/securitymanager";
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+
+declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
+
+declare option output:method "json";
+declare option output:media-type "application/json";
+
+declare function local:log-put-package-event($filename as xs:string) as empty-sequence() {
+ let $package := doc($config:raw-packages-doc)//package[@path eq $filename]
+ let $today := current-date()
+ let $log-doc := config:log-doc($today)
+ let $event :=
+ element event {
+ element dateTime { current-dateTime() },
+ element type { "put-package" },
+ element package-name { $package/name/string() },
+ element package-version { $package/version/string() }
+ }
+ let $update-log :=
+ if (doc-available($log-doc)) then
+ update insert $event into doc($log-doc)/public-repo-log
+ else
+ (
+ app:mkcol($config:logs-col, config:log-subcol($today)),
+ xmldb:store(config:log-col($today), config:log-doc-name($today), element public-repo-log { $event } )
+ )
+ return
+ ()
+};
+
+declare function local:upload-and-publish($xar-filename as xs:string, $xar-binary as xs:base64Binary) {
+ let $path := xmldb:store($config:packages-col, $xar-filename, $xar-binary)
+ let $publish := scanrepo:publish-package($xar-filename)
+ return
+ map {
+ "files": array {
+ map {
+ "name": $xar-filename,
+ "type": xmldb:get-mime-type($path),
+ "size": xmldb:size($config:packages-col, $xar-filename)
+ }
+ }
+ }
+};
+
+let $xar-filename := request:get-uploaded-file-name("files[]")
+let $xar-binary := request:get-uploaded-file-data("files[]")
+let $user := request:get-attribute("org.exist.public-repo.login.user")
+let $required-group := config:repo-permissions()?group
+return
+ if (exists($user) and sm:get-user-groups($user) = $required-group) then
+ try {
+ local:upload-and-publish($xar-filename, $xar-binary),
+ local:log-put-package-event($xar-filename)
+ } catch * {
+ map {
+ "result":
+ map {
+ "name": $xar-filename,
+ "error": $err:description
+ }
+ }
+ }
+ else
+ (
+ response:set-status-code(401),
+ map {
+ "error": "User must be a member of the " || $required-group || " group."
+ }
+ )
diff --git a/modules/scan.xql b/modules/scan.xql
deleted file mode 100644
index f4c13e5..0000000
--- a/modules/scan.xql
+++ /dev/null
@@ -1,281 +0,0 @@
-xquery version "3.1";
-
-module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo";
-
-import module namespace config = "http://exist-db.org/xquery/apps/config" at "config.xqm";
-import module namespace crypto = "http://expath.org/ns/crypto";
-import module namespace semver = "http://exist-db.org/xquery/semver";
-import module namespace util = "http://exist-db.org/xquery/util";
-
-declare namespace repo="http://exist-db.org/xquery/repo";
-declare namespace expath="http://expath.org/ns/pkg";
-
-declare function scanrepo:is-newer-or-same($version1 as xs:string, $version2 as xs:string?) {
- empty($version2) or
- semver:ge($version1, $version2, true())
-};
-
-declare function scanrepo:is-older-or-same($version1 as xs:string, $version2 as xs:string?) {
- empty($version2) or
- semver:le($version1, $version2, true())
-};
-
-declare function scanrepo:process($apps as element(app)*) {
- for $app in $apps
- order by $app/title
- group by $name := $app/name
- return
- (: Identify newest version of the package; sort previous versions newest to oldest; use SemVer 2.0 rules, coercing where needed :)
- let $versions := $app/version
- let $version-maps :=
- $versions ! map:merge((
- map:entry("semver", semver:coerce(.) => semver:serialize()),
- map:entry("version", .)
- ))
- let $sorted-semvers := semver:sort($version-maps?semver) => reverse()
- let $sorted-versions :=
- for $semver in $sorted-semvers
- return
- $version-maps[?semver eq $semver]?version/..
- let $newest-version := $sorted-versions => head()
- let $older-versions := $sorted-versions => tail()
- let $abbrevs := distinct-values($app/abbrev)
- return
-
- {
- $newest-version/@*,
- $newest-version/*,
- $abbrevs[not(. = $newest-version/abbrev)] ! element abbrev { attribute type { "legacy" }, . }
- }
-
- {
- for $older in $older-versions
- let $xar := concat($config:public, "/", $older/@path)
- let $hash := crypto:hash(
- util:binary-doc($xar),
- "sha256",
- "hex"
- )
- return
- {
- $older/@path,
- attribute size { xmldb:size($config:public, $older/@path) },
- attribute sha256 { $hash },
- $older/requires
- }
- }
-
-
-};
-
-declare function scanrepo:find-newest($apps as element()*, $newest as element()?, $procVersion as xs:string?) {
- if (empty($apps)) then
- $newest
- else
- let $app := head($apps)
- let $newer :=
- if ($procVersion and
- not(scanrepo:is-newer-or-same($procVersion, $app/requires/@semver-min) and
- scanrepo:is-older-or-same($procVersion, $app/requires/@semver-max))) then
- $newest
- else if (empty($newest) or scanrepo:is-newer(($app/version, $app/@version), ($newest/version, $newest/@version))) then
- $app
- else
- $newest
- return
- scanrepo:find-newest(tail($apps), $newer, $procVersion)
-};
-
-declare function scanrepo:find-version($apps as element()*, $minVersion as xs:string?, $maxVersion as xs:string?) {
- let $minVersion := if ($minVersion) then $minVersion else "0"
- let $maxVersion := if ($maxVersion) then $maxVersion else "9999"
- return
- scanrepo:find-version($apps, $minVersion, $maxVersion, ())
-};
-
-declare %private function scanrepo:find-version($apps as element()*, $minVersion as xs:string, $maxVersion as xs:string, $newest as element()?) {
- if (empty($apps)) then
- $newest
- else
- let $app := head($apps)
- let $appVersion := $app/version | $app/@version
- let $newer :=
- if (
- (empty($newest) or scanrepo:is-newer($appVersion, ($newest/version, $newest/@version))) and
- scanrepo:is-newer($appVersion, $minVersion) and
- scanrepo:is-older($appVersion, $maxVersion)
- ) then
- $app
- else
- $newest
- return
- scanrepo:find-version(tail($apps), $minVersion, $maxVersion, $newer)
-};
-
-declare %private function scanrepo:is-newer($available as xs:string, $installed as xs:string) as xs:boolean {
- let $verInstalled := tokenize($installed, "\.")
- let $verAvailable := tokenize($available, "\.")
- return
- scanrepo:compare-versions($verInstalled, $verAvailable, function($version1, $version2) {
- number($version1) >= number($version2)
- })
-};
-
-declare %private function scanrepo:is-older($available as xs:string, $installed as xs:string) as xs:boolean {
- let $verInstalled := tokenize($installed, "\.")
- let $verAvailable := tokenize($available, "\.")
- return
- scanrepo:compare-versions($verInstalled, $verAvailable, function($version1, $version2) {
- number($version1) <= number($version2)
- })
-};
-
-declare %private function scanrepo:compare-versions($installed as xs:string*, $available as xs:string*,
- $compare as function(*)) as xs:boolean {
- if (empty($installed)) then
- exists($available)
- else if (empty($available)) then
- false()
- else if (head($available) = head($installed)) then
- if (count($available) = 1 and count($installed) = 1) then
- true()
- else
- scanrepo:compare-versions(tail($installed), tail($available), $compare)
- else
- $compare(head($available), head($installed))
-};
-
-declare function scanrepo:handle-icon ($path as xs:string, $data as item()?, $param as item()*) as element(icon) {
- let $pkgName := substring-before($param, ".xar")
-
- let $suffix := replace($path, "^.*\.([^\.]+)", "$1")
- let $name := concat($pkgName, ".", $suffix)
- let $stored := xmldb:store($config:public, $name, $data)
-
- return
- { $name }
-};
-
-declare function scanrepo:handle-expath-package ($root as element(expath:package)) as element()* {
- {$root/@name/string()},
- {$root/expath:title/text()},
- {$root/@abbrev/string()},
- {$root/@version/string()},
- if ($root/expath:dependency[starts-with(@processor, "http://exist-db.org")]) then
- { $root/expath:dependency[starts-with(@processor, "http://exist-db.org")]/@* }
- else
- ()
-};
-
-declare function scanrepo:handle-repo-meta ($root as element(repo:meta)) as element()+ {
- for $author in $root/repo:author
- return
- {$author/text()}
- ,
- {$root/repo:description/text()},
- {$root/repo:website/text()},
- {$root/repo:license/text()},
- {$root/repo:type/text()}
- ,
- for $note in $root/repo:note
- return
- {$note/text()}
- ,
-
- {
- scanrepo:copy-changelog($root/repo:changelog/repo:change)
- }
-
-};
-
-declare function scanrepo:entry-data($path as xs:anyURI, $type as xs:string, $data as item()?, $param as item()*) as item()*
-{
- if (starts-with($path, "icon")) then
- scanrepo:handle-icon($path, $data, $param)
- else
- let $root := $data/*
- return
- typeswitch ($root)
- case element(expath:package) return
- scanrepo:handle-expath-package($root)
- case element(repo:meta) return
- scanrepo:handle-repo-meta($root)
- default return
- ()
-};
-
-declare function scanrepo:copy-changelog($nodes as node()*) {
- for $node in $nodes
- return
- typeswitch ($node)
- case element() return
- element { local-name($node) } {
- $node/@*,
- scanrepo:copy-changelog($node/node())
- }
- default return
- $node
-};
-
-declare function scanrepo:entry-filter($path as xs:anyURI, $type as xs:string, $param as item()*) as xs:boolean {
- starts-with($path, "icon.") or
- $path = ("repo.xml", "expath-pkg.xml")
-};
-
-declare function scanrepo:extract-metadata ($resource as xs:string) as element(app) {
- let $xar := concat($config:public, "/", $resource)
- let $data := util:binary-doc($xar)
-
- let $hash := crypto:hash($data, "sha256", "hex")
-
- return
-
- {
- compression:unzip(
- $data,
- scanrepo:entry-filter#3,
- (),
- scanrepo:entry-data#4,
- $resource
- )
- }
-
-};
-
-declare function scanrepo:scan-all() {
- for $resource in xmldb:get-child-resources($config:public)
- where ends-with($resource, ".xar")
- return
- scanrepo:extract-metadata($resource)
-};
-
-declare function scanrepo:scan() {
- let $data := doc($config:packages-meta)//app
- let $processed := { scanrepo:process($data) }
- let $store := xmldb:store($config:metadata-collection, $config:apps-doc, $processed)
-
- return $processed
-};
-
-declare function scanrepo:rebuild-package-meta() as xs:string {
- xmldb:store($config:metadata-collection, $config:packages-doc,
- { scanrepo:scan-all() })
-};
-
-declare function scanrepo:add-package-meta($meta as element(app)) {
- let $packages := doc($config:packages-meta)/packages
- let $node-to-update := $packages/app[@path=$meta/@path]
-
- return
- if (exists($node-to-update))
- then (update replace $node-to-update with $meta)
- else (update insert $meta into $packages)
-};
-
-declare function scanrepo:publish($xar as xs:string) {
- let $meta := $xar
- => scanrepo:extract-metadata()
- => scanrepo:add-package-meta()
-
- return scanrepo:scan()
-};
diff --git a/modules/scan.xqm b/modules/scan.xqm
new file mode 100644
index 0000000..91c0dd1
--- /dev/null
+++ b/modules/scan.xqm
@@ -0,0 +1,240 @@
+xquery version "3.1";
+
+(:~
+ : Functions to extract metadata from packages and populate, update, or rebuild package metadata files
+ :)
+
+module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo";
+
+import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
+import module namespace semver="http://exist-db.org/xquery/semver";
+
+declare namespace compression="http://exist-db.org/xquery/compression";
+declare namespace repo="http://exist-db.org/xquery/repo";
+declare namespace util="http://exist-db.org/xquery/util";
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+
+declare namespace expath="http://expath.org/ns/pkg";
+
+(:~
+ : Helper function to store a package's icon and transform its metadata into the format needed for raw-metadata
+ :)
+declare
+ %private
+function scanrepo:handle-icon($path as xs:string, $data as item()?, $param as item()*) as element(icon) {
+ let $pkgName := substring-before($param, ".xar")
+ let $suffix := replace($path, "^.*\.([^\.]+)", "$1")
+ let $name := concat($pkgName, ".", $suffix)
+ let $stored := xmldb:store($config:icons-col, $name, $data)
+ return
+ element icon { $name }
+};
+
+(:~
+ : Helper function to transform expath-pkg.xml metadata into the format needed for raw-metadata
+ :)
+declare
+ %private
+function scanrepo:handle-expath-pkg-metadata($root as element(expath:package)) as element()* {
+ $root/(@name, expath:title, @abbrev, @version) !
+ element { local-name(.) } { ./string() },
+ $root/expath:dependency[@processor eq $config:exist-processor-name] !
+ element requires { ./@* }
+};
+
+(:~
+ : Helper function to transform repo.xml's changelog metadata into the format needed for raw-metadata
+ :)
+declare
+ %private
+function scanrepo:copy-changelog($nodes as node()*) {
+ for $node in $nodes
+ return
+ typeswitch ($node)
+ case element() return
+ element { local-name($node) } {
+ $node/@*,
+ scanrepo:copy-changelog($node/node())
+ }
+ default return
+ $node
+};
+
+(:~
+ : Helper function to transform repo.xml metadata into the format needed for raw-metadata
+ :)
+declare
+ %private
+function scanrepo:handle-repo-metadata($root as element(repo:meta)) as element()+ {
+ $root/(repo:author, repo:description, repo:website, repo:license, repo:type, repo:note) !
+ element { local-name(.) } { ./string() },
+ element changelog { scanrepo:copy-changelog($root/repo:changelog/repo:change) }
+};
+
+(:~
+ : Helper function to handle transformation of icon and package metadata for extraction from the xar
+ :)
+declare
+ %private
+function scanrepo:entry-data($path as xs:anyURI, $type as xs:string, $data as item()?, $param as item()*) as item()*
+{
+ if (starts-with($path, "icon")) then
+ scanrepo:handle-icon($path, $data, $param)
+ else
+ let $root := $data/*
+ return
+ typeswitch ($root)
+ case element(expath:package) return
+ scanrepo:handle-expath-pkg-metadata($root)
+ case element(repo:meta) return
+ scanrepo:handle-repo-metadata($root)
+ default return
+ ()
+};
+
+(:~
+ : Helper function to select assets from a package for extraction from the xar
+ :)
+declare
+ %private
+function scanrepo:entry-filter($path as xs:anyURI, $type as xs:string, $param as item()*) as xs:boolean {
+ starts-with($path, "icon.") or $path = ("repo.xml", "expath-pkg.xml")
+};
+
+(:~
+ : Take a group of packages with the same package name (a URI) and generate a package-group
+ :)
+declare
+(: %private:)
+function scanrepo:generate-package-group($packages as element(package)*) {
+ if (count(distinct-values($packages/name)) gt 1) then
+ error(QName("scanrepo", "group-error"), "Supplied packages do not have the same name")
+ else
+ (: Identify newest version of the package; sort previous versions newest to oldest; use SemVer 2.0 rules, coercing where needed :)
+ let $versions := $packages/version
+ let $version-maps :=
+ $versions ! map:merge((
+ map:entry("semver", semver:coerce(.) => semver:serialize()),
+ map:entry("version", .)
+ ))
+ let $sorted-semvers := semver:sort($version-maps?semver) => reverse()
+ let $sorted-packages :=
+ for $semver in $sorted-semvers
+ return
+ $version-maps[?semver eq $semver]?version/..
+ let $newest-package := $sorted-packages => head()
+ let $legacy-abbrevs := distinct-values($packages/abbrev)[not(. = $newest-package/abbrev)]
+ return
+ element package-group {
+ $newest-package/(title, name, abbrev),
+ $legacy-abbrevs ! element abbrev { attribute type { "legacy" }, . },
+ element packages { $sorted-packages }
+ }
+};
+
+
+(:~
+ : Update a package group, creating it if necessary
+ :)
+declare function scanrepo:update-package-group($raw-package-name as xs:string) {
+ let $raw-packages := doc($config:raw-packages-doc)/raw-packages
+ let $raw-packages-to-group := $raw-packages/package[name = $raw-package-name]
+ let $package-groups := doc($config:package-groups-doc)/package-groups
+ let $current-package-group := $package-groups/package-group[name eq $raw-package-name]
+ return
+ if (exists($current-package-group)) then
+ update replace $current-package-group with scanrepo:generate-package-group($raw-packages-to-group)
+ else
+ update insert scanrepo:generate-package-group($raw-packages-to-group) into $package-groups
+};
+
+(:~
+ : Add a package's metadata to raw-packages
+ :)
+declare function scanrepo:add-raw-package($raw-package as element(package)) {
+ let $raw-packages := doc($config:raw-packages-doc)/raw-packages
+ let $current-raw-package := $raw-packages/package[@path = $raw-package/@path]
+ return
+ if (exists($current-raw-package)) then
+ update replace $current-raw-package with $raw-package
+ else
+ update insert $raw-package into $raw-packages
+};
+
+(:~
+ : Extract a stored package's raw-package metadata
+ :)
+declare function scanrepo:extract-raw-package($xar-filename as xs:string) as element(package) {
+ let $xar-path := $config:packages-col || "/" || $xar-filename
+ let $xar-binary := util:binary-doc($xar-path)
+ let $package-metadata :=
+ compression:unzip(
+ $xar-binary,
+ scanrepo:entry-filter#3,
+ (),
+ scanrepo:entry-data#4,
+ $xar-filename
+ )
+ return
+ element package {
+ attribute path { $xar-filename },
+ attribute size { xmldb:size($config:packages-col, $xar-filename) },
+ attribute sha256 { util:binary-doc-content-digest($xar-path, "SHA-256") => string() },
+ $package-metadata
+ }
+};
+
+(:~
+ : Publish a stored package by adding it to the raw-packages and package-groups metadata
+ :)
+declare function scanrepo:publish-package($xar-filename as xs:string) {
+ let $package := scanrepo:extract-raw-package($xar-filename)
+ return
+ (
+ scanrepo:add-raw-package($package),
+ scanrepo:update-package-group($package/name)
+ )
+};
+
+(:~
+ : Rebuild the package-groups metadata by merging raw-packages metadata into package-groups
+ :)
+declare function scanrepo:rebuild-package-groups() as xs:string {
+ let $groups :=
+ for $package in doc($config:raw-packages-doc)//package
+ group by $name := $package/name
+ return
+ scanrepo:generate-package-group($package)
+ let $package-groups :=
+ element package-groups {
+ for $group in $groups
+ order by $group/abbrev[not(@type = "legacy")]
+ return
+ $group
+ }
+ return
+ xmldb:store($config:metadata-col, $config:package-groups-doc-name, $package-groups)
+};
+
+(:~
+ : Rebuild the raw-packages metadata from all stored packages
+ :)
+declare function scanrepo:rebuild-raw-packages() as xs:string {
+ let $raw-packages :=
+ element raw-packages {
+ for $package-xar in xmldb:get-child-resources($config:packages-col)[ends-with(., ".xar")]
+ order by $package-xar
+ return
+ scanrepo:extract-raw-package($package-xar)
+ }
+ return
+ xmldb:store($config:metadata-col, $config:raw-packages-doc-name, $raw-packages)
+};
+
+(:~
+ : Rebuild all package metadata
+ :)
+declare function scanrepo:rebuild-all-package-metadata() as xs:string+ {
+ scanrepo:rebuild-raw-packages(),
+ scanrepo:rebuild-package-groups()
+};
diff --git a/modules/update.xq b/modules/update.xq
new file mode 100644
index 0000000..6bf7f87
--- /dev/null
+++ b/modules/update.xq
@@ -0,0 +1,9 @@
+xquery version "3.1";
+
+(:~
+ : Rebuild metadata for all packages
+ :)
+
+import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm";
+
+scanrepo:rebuild-all-package-metadata()
\ No newline at end of file
diff --git a/modules/update.xql b/modules/update.xql
deleted file mode 100644
index ed02740..0000000
--- a/modules/update.xql
+++ /dev/null
@@ -1,11 +0,0 @@
-xquery version "1.0";
-
-import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xql";
-
-scanrepo:rebuild-package-meta()
-=> scanrepo:scan()
-
-(: $file
-=> scanrepo:extract-metadata()
-=> scanrepo:add-package-meta()
-=> scanrepo:scan() :)
diff --git a/modules/upgrade-to-v2-storage.xq b/modules/upgrade-to-v2-storage.xq
new file mode 100644
index 0000000..1ea3903
--- /dev/null
+++ b/modules/upgrade-to-v2-storage.xq
@@ -0,0 +1,81 @@
+xquery version "3.1";
+
+(:~
+ : A script for upgrading public-repo's storage format to v2.
+ :
+ : This script is only needed if you are preparing to upgrade from public-repo v0-1 to public-repo v2+.
+ :)
+
+import module namespace semver = "http://exist-db.org/xquery/semver";
+
+declare namespace pkg="http://expath.org/ns/pkg";
+declare namespace repo="http://exist-db.org/xquery/repo";
+declare namespace sm="http://exist-db.org/xquery/securitymanager";
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+
+declare function local:chgrp-repo($path) {
+ if (sm:get-permissions($path)/*/@group = "repo") then
+ ()
+ else
+ (
+ sm:chown($path, "repo"),
+ sm:chgrp($path, "repo")
+ )
+};
+
+declare function local:upgrade-to-public-repo-2-storage() as element()+ {
+ (: create new collections :)
+ element collections {
+ (
+ xmldb:create-collection("/db/apps", "public-repo-data"),
+ for $subcollection in ("icons", "metadata", "packages")
+ return
+ xmldb:create-collection("/db/apps/public-repo-data", $subcollection)
+ ) !
+ (
+ local:chgrp-repo(.),
+ element collection { . }
+ )
+ },
+
+ (: move xars and icons to new collections :)
+ if (xmldb:collection-available("/db/apps/public-repo/public")) then
+ (
+ element packages {
+ for $package in xmldb:get-child-resources("/db/apps/public-repo/public")[ends-with(., ".xar")]
+ return
+ xmldb:copy-resource("/db/apps/public-repo/public", $package, "/db/apps/public-repo-data/packages", $package, true()) !
+ (
+ local:chgrp-repo(.),
+ element package { . }
+ )
+ },
+ element icons {
+ for $icon in xmldb:get-child-resources("/db/apps/public-repo/public")[not(matches(., "\.(xar|xml)$"))]
+ return
+ xmldb:copy-resource("/db/apps/public-repo/public", $icon, "/db/apps/public-repo-data/icons", $icon, true())
+ !
+ (
+ local:chgrp-repo(.),
+ element icon { . }
+ )
+ }
+ )
+ else
+ ()
+};
+
+if (repo:list() = "http://exist-db.org/apps/public-repo") then
+ if (semver:lt(doc("/db/apps/public-repo/expath-pkg.xml")/pkg:package/@version, "2.0.0")) then
+ element status {
+ element description { "Upgrade of public-repo storage to v2 format is complete. Please install latest public-repo v2+." },
+ local:upgrade-to-public-repo-2-storage()
+ }
+ else
+ element status {
+ element description { "No action taken. A version of public-repo with the v2 format is already installed." }
+ }
+else
+ element status {
+ element description { "No action taken. The public-repo app is not installed." }
+ }
diff --git a/modules/upload.xql b/modules/upload.xql
deleted file mode 100644
index c4cf8a8..0000000
--- a/modules/upload.xql
+++ /dev/null
@@ -1,35 +0,0 @@
-xquery version "3.0";
-
-import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
-import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xql";
-
-declare namespace json="http://www.json.org";
-
-declare option exist:serialize "method=json media-type=application/json";
-
-declare function local:upload-and-publish($xar as xs:string, $data) {
- let $path := xmldb:store($config:public, $xar, $data)
- let $scan := scanrepo:publish($xar)
-
- return
-
-
- {$path}
- {xmldb:get-mime-type($path)}
- {xmldb:size($config:public, $xar)}
-
-
-};
-
-let $name := request:get-uploaded-file-name("files[]")
-let $data := request:get-uploaded-file-data("files[]")
-
-return
- try {
- local:upload-and-publish($name, $data)
- } catch * {
-
- {$name}
- {$err:description}
-
- }
\ No newline at end of file
diff --git a/modules/versions.xqm b/modules/versions.xqm
new file mode 100644
index 0000000..5408535
--- /dev/null
+++ b/modules/versions.xqm
@@ -0,0 +1,167 @@
+xquery version "3.1";
+
+(:~
+ : A library module for finding packages by version number criteria
+ :)
+
+module namespace versions="http://exist-db.org/apps/public-repo/versions";
+
+import module namespace semver="http://exist-db.org/xquery/semver";
+
+(:~
+ : Find all packages compatible with a specific version of eXist (or higher)
+ :)
+declare function versions:find-compatible-packages(
+ $packages as element(package)*,
+ $exist-version-semver as xs:string
+) as element(package)* {
+ versions:find-compatible-packages($packages, $exist-version-semver, (), (), (), ())
+};
+
+(:~
+ : Find all packages compatible with a version of eXist meeting various version criteria
+ :
+ : TODO: find packages with version, semver, or min/max-version attributes to test those conditions - joewiz
+ :)
+declare function versions:find-compatible-packages(
+ $packages as element(package)*,
+ $exist-version-semver as xs:string,
+ $version as xs:string?,
+ $semver as xs:string?,
+ $semver-min as xs:string?,
+ $semver-max as xs:string?
+) as element(package)* {
+ for $package in $packages
+ return
+ if ($semver) then
+ versions:find-version($packages, $semver, $semver)
+ else if ($version) then
+ $packages[version = $version]
+ else if ($semver-min and $semver-max) then
+ versions:find-version($packages, $semver-min, $semver-max)
+ else if
+ (
+ $exist-version-semver and
+ versions:is-newer-or-same($exist-version-semver, $package/requires/@semver-min) and
+ versions:is-older-or-same($exist-version-semver, $package/requires/@semver-max)
+ ) then
+ $package
+ else
+ ()
+};
+
+(:~
+ : Find the newest version of packages compatible with a specific version of eXist (or higher)
+ :)
+declare function versions:find-newest-compatible-package(
+ $packages as element(package)*,
+ $exist-version-semver as xs:string
+) as element(package)? {
+ versions:find-newest-compatible-package($packages, $exist-version-semver, (), (), (), ())
+};
+
+(:~
+ : Find the newest version of packages compatible with a version of eXist meeting various version criteria
+ :)
+declare function versions:find-newest-compatible-package(
+ $packages as element(package)*,
+ $exist-version-semver as xs:string,
+ $version as xs:string?,
+ $semver as xs:string?,
+ $min-version as xs:string?,
+ $max-version as xs:string?
+) as element(package)? {
+ versions:find-compatible-packages($packages, $exist-version-semver, $version, $semver, $min-version, $max-version)
+ => head()
+};
+
+declare
+ %private
+function versions:is-newer-or-same($version1 as xs:string, $version2 as xs:string?) {
+ empty($version2) or semver:ge($version1, $version2, true())
+};
+
+declare
+ %private
+function versions:is-older-or-same($version1 as xs:string, $version2 as xs:string?) {
+ empty($version2) or semver:le($version1, $version2, true())
+};
+
+declare
+ %private
+function versions:find-version($packages as element(package)*, $minVersion as xs:string?, $maxVersion as xs:string?) {
+ let $minVersion := if ($minVersion) then $minVersion else "0"
+ let $maxVersion := if ($maxVersion) then $maxVersion else "9999"
+ return
+ versions:find-version($packages, $minVersion, $maxVersion, ())
+};
+
+declare
+ %private
+function versions:find-version($packages as element(package)*, $minVersion as xs:string, $maxVersion as xs:string, $newest as element()?) {
+ if (empty($packages)) then
+ $newest
+ else
+ let $package := head($packages)
+ let $packageVersion := $package/version | $package/@version
+ let $newer :=
+ if (
+ (
+ empty($newest) or
+ versions:is-newer($packageVersion, ($newest/version, $newest/@version))
+ ) and
+ versions:is-newer($packageVersion, $minVersion) and
+ versions:is-older($packageVersion, $maxVersion)
+ ) then
+ $package
+ else
+ $newest
+ return
+ versions:find-version(tail($packages), $minVersion, $maxVersion, $newer)
+};
+
+declare
+ %private
+function versions:is-newer($available as xs:string, $installed as xs:string) as xs:boolean {
+ let $verInstalled := tokenize($installed, "\.")
+ let $verAvailable := tokenize($available, "\.")
+ return
+ versions:compare-versions(
+ $verInstalled,
+ $verAvailable,
+ function($version1, $version2) {
+ number($version1) >= number($version2)
+ }
+ )
+};
+
+declare
+ %private
+function versions:is-older($available as xs:string, $installed as xs:string) as xs:boolean {
+ let $verInstalled := tokenize($installed, "\.")
+ let $verAvailable := tokenize($available, "\.")
+ return
+ versions:compare-versions(
+ $verInstalled,
+ $verAvailable,
+ function($version1, $version2) {
+ number($version1) <= number($version2)
+ }
+ )
+};
+
+declare
+ %private
+function versions:compare-versions($installed as xs:string*, $available as xs:string*, $compare as function(*)) as xs:boolean {
+ if (empty($installed)) then
+ exists($available)
+ else if (empty($available)) then
+ false()
+ else if (head($available) = head($installed)) then
+ if (count($available) = 1 and count($installed) = 1) then
+ true()
+ else
+ versions:compare-versions(tail($installed), tail($available), $compare)
+ else
+ $compare(head($available), head($installed))
+};
diff --git a/modules/view.xql b/modules/view.xq
similarity index 66%
rename from modules/view.xql
rename to modules/view.xq
index 2022d4d..59f6455 100644
--- a/modules/view.xql
+++ b/modules/view.xq
@@ -1,17 +1,19 @@
-xquery version "3.0";
+xquery version "3.1";
+import module namespace app="http://exist-db.org/xquery/app" at "app.xqm";
import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm";
-import module namespace site="http://exist-db.org/apps/site-utils";
import module namespace templates="http://exist-db.org/xquery/templates";
-import module namespace app="http://exist-db.org/xquery/app" at "app.xql";
-declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization";
+declare namespace site="http://exist-db.org/apps/site-utils";
-declare option exist:timeout "300000";
+declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization";
-declare option output:method "html5";
+declare option output:method "html";
+declare option output:html-version "5";
declare option output:media-type "text/html";
+declare option exist:timeout "300000";
+
let $config := map {
$templates:CONFIG_APP_ROOT : $config:app-root
}
@@ -24,4 +26,4 @@ let $lookup := function($functionName as xs:string, $arity as xs:int) {
}
let $content := request:get-data()
return
- templates:apply($content, $lookup, (), $config)
\ No newline at end of file
+ templates:apply($content, $lookup, (), $config)
diff --git a/packages.html b/packages.html
index 0236145..534e02d 100644
--- a/packages.html
+++ b/packages.html
@@ -1,5 +1,5 @@
-
+
Public Application Repository
@@ -8,9 +8,9 @@
Public Application Repository
-
+
-
\ No newline at end of file
+
diff --git a/post-install.xq b/post-install.xq
new file mode 100644
index 0000000..0609874
--- /dev/null
+++ b/post-install.xq
@@ -0,0 +1,67 @@
+xquery version "3.1";
+
+(:~
+ : This post-install script sets permissions on the package data collection hierarchy.
+ : When pre-install creates the public-repo-data collection, its permissions are admin/dba.
+ : This ensures the collections are owned by the default user and group for the app.
+ : The script also builds the package metadata if it doesn't already exist.
+ :)
+
+import module namespace config="http://exist-db.org/xquery/apps/config" at "modules/config.xqm";
+import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "modules/scan.xqm";
+
+declare namespace sm="http://exist-db.org/xquery/securitymanager";
+declare namespace system="http://exist-db.org/xquery/system";
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+
+(: The following external variables are set by the repo:deploy function :)
+
+(: file path pointing to the exist installation directory :)
+declare variable $home external;
+(: path to the directory containing the unpacked .xar package :)
+declare variable $dir external;
+(: the target collection into which the app is deployed :)
+declare variable $target external;
+
+(: Until https://github.com/eXist-db/exist/issues/3734 is fixed, we hard code the default group name :)
+declare variable $repo-group :=
+ (: config:repo-permissions()?group :)
+ "repo"
+;
+declare variable $repo-user :=
+ (: config:repo-permissions()?user :)
+ "repo"
+;
+
+(:~
+ : Set user and group to be owner by values in repo.xml
+ :)
+declare function local:set-data-collection-permissions($resource as xs:string) {
+ if (sm:get-permissions(xs:anyURI($resource))/sm:permission/@group = $repo-group) then
+ ()
+ else
+ (
+ sm:chown($resource, $repo-user),
+ sm:chgrp($resource, $repo-group),
+ sm:chmod(xs:anyURI($resource), "rwxrwxr-x")
+ )
+};
+
+(: Set user and group ownership on the package data collection hierarchy :)
+
+for $col in ($config:app-data-col, xmldb:get-child-collections($config:app-data-col) ! ($config:app-data-col || "/" || .))
+return
+ local:set-data-collection-permissions($col),
+
+(: Build package metadata if missing :)
+
+if (doc-available($config:raw-packages-doc) and doc-available($config:package-groups-doc)) then
+ ()
+else
+ (
+ scanrepo:rebuild-all-package-metadata(),
+ ($config:raw-packages-doc, $config:package-groups-doc) ! local:set-data-collection-permissions(.)
+ ),
+
+(: execute get-package.xq as repo group, so that it can write to logs :)
+sm:chmod(xs:anyURI($target || "/modules/get-package.xq"), "rwsrwxr-x")
\ No newline at end of file
diff --git a/post-install.xql b/post-install.xql
deleted file mode 100644
index a9be454..0000000
--- a/post-install.xql
+++ /dev/null
@@ -1,63 +0,0 @@
-xquery version "3.1";
-
-import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "modules/scan.xql";
-import module namespace system="http://exist-db.org/xquery/system";
-import module namespace util="http://exist-db.org/xquery/util";
-import module namespace xmldb="http://exist-db.org/xquery/xmldb";
-
-(: The following external variables are set by the repo:deploy function :)
-
-(: file path pointing to the exist installation directory :)
-declare variable $home external;
-(: path to the directory containing the unpacked .xar package :)
-declare variable $dir external;
-(: the target collection into which the app is deployed :)
-declare variable $target external;
-
-(: Handle difference between 4.x.x and 5.x.x releases of eXist :)
-declare variable $local:copy-collection :=
- let $fnNew := function-lookup(xs:QName("xmldb:copy-collection"), 2)
- return
- if (exists($fnNew)) then $fnNew else function-lookup(xs:QName("xmldb:copy"), 2);
-
-declare function local:mkcol-recursive($collection, $components) {
- if (exists($components)) then
- let $newColl := concat($collection, "/", $components[1])
- return (
- xmldb:create-collection($collection, $components[1]),
- local:mkcol-recursive($newColl, subsequence($components, 2))
- )
- else
- ()
-};
-
-(: Helper function to recursively create a collection hierarchy. :)
-declare function local:mkcol($collection, $path) {
- local:mkcol-recursive($collection, tokenize($path, "/"))
-};
-
-declare function local:get-repo-dir() {
- let $home := system:get-exist-home()
- let $pathSep := util:system-property("file.separator")
- return
- if (doc-available(concat("file:///", $home, "/webapp/repo/packages"))) then
- concat($home, "/webapp/repo/packages")
- else if(ends-with($home, "WEB-INF")) then
- concat(substring-before($home, "WEB-INF"), "/repo/packages")
- else
- concat($home, $pathSep, "/webapp/repo/packages")
-};
-
-declare function local:copy-previous-public-from-temp-or-create() {
-if (xmldb:collection-available("/db/temp/public")) then
- let $copy-dummy := $local:copy-collection("/db/temp/public", $target)
- return xmldb:remove("/db/temp/public")
-else
- local:mkcol($target, "public")
-};
-
-system:as-user("repo", "repo", (
- local:copy-previous-public-from-temp-or-create(),
- xmldb:store-files-from-pattern(concat($target, "/public"), local:get-repo-dir(), "*.xar"),
- scanrepo:rebuild-package-meta()
-))
\ No newline at end of file
diff --git a/pre-install.xq b/pre-install.xq
new file mode 100644
index 0000000..07a8ad3
--- /dev/null
+++ b/pre-install.xq
@@ -0,0 +1,80 @@
+xquery version "3.1";
+
+(:~
+ : This pre-install script creates the data collection hierarchy which the app
+ : uses to store packages and package assets. The names of these collections are
+ : defined in the modules/config.xqm module. Since the pre-install script
+ : runs before the package has been stored in the database, we read these values
+ : from the location on disk where the unpacked .xar resides.
+ :)
+
+declare namespace xmldb="http://exist-db.org/xquery/xmldb";
+
+(: The following external variables are set by the repo:deploy function :)
+
+(: File path pointing to the exist installation directory :)
+declare variable $home external;
+(: Path to the directory containing the unpacked .xar package :)
+declare variable $dir external;
+(: The target collection into which the app is deployed :)
+declare variable $target external;
+
+(: Helper function to recursively create a collection hierarchy :)
+declare function local:mkcol-recursive($collection as xs:string, $components as xs:string*) {
+ if (exists($components)) then
+ let $newColl := concat($collection, "/", $components[1])
+ return (
+ xmldb:create-collection($collection, $components[1]),
+ local:mkcol-recursive($newColl, subsequence($components, 2))
+ )
+ else
+ ()
+};
+
+(: Create a collection hierarchy :)
+declare function local:mkcol($collection as xs:string, $path as xs:string) {
+ local:mkcol-recursive($collection, tokenize($path, "/"))
+};
+
+(: Configuration file for the logs collection :)
+declare variable $logs-xconf :=
+
+
+
+
+
+
+ ;
+
+(: Read the collection names from modules/config.xqm :)
+
+let $config-module-ns := "http://exist-db.org/xquery/apps/config"
+let $config-module-variables :=
+ fn:load-xquery-module(
+ $config-module-ns,
+ map { "location-hints": $dir || "/modules/config.xqm" }
+ )?variables
+ => map:for-each(function($name, $value) { map:entry(fn:local-name-from-QName($name), $value) })
+let $app-data-parent-col := $config-module-variables?app-data-parent-col
+let $app-data-col-name := $config-module-variables?app-data-col-name
+let $app-data-col := $config-module-variables?app-data-col
+let $icons-col-name := $config-module-variables?icons-col-name
+let $metadata-col-name := $config-module-variables?metadata-col-name
+let $packages-col-name := $config-module-variables?packages-col-name
+let $logs-col-name := $config-module-variables?logs-col-name
+let $logs-col := $config-module-variables?logs-col
+return
+ (
+ (: Create the data collection hierarchy :)
+
+ xmldb:create-collection($app-data-parent-col, $app-data-col-name),
+ for $col-name in ($icons-col-name, $metadata-col-name, $packages-col-name, $logs-col-name)
+ return
+ xmldb:create-collection($app-data-col, $col-name),
+
+ (: Create log indexes :)
+ (: Store the collection configuration :)
+ local:mkcol("/db/system/config", $config-module-variables?logs-col),
+ xmldb:store("/db/system/config" || $config-module-variables?logs-col, "collection.xconf", $logs-xconf),
+ xmldb:reindex($config-module-variables?logs-col)
+ )
\ No newline at end of file
diff --git a/pre-install.xql b/pre-install.xql
deleted file mode 100644
index 41fa7d6..0000000
--- a/pre-install.xql
+++ /dev/null
@@ -1,47 +0,0 @@
-xquery version "3.1";
-
-import module namespace xmldb="http://exist-db.org/xquery/xmldb";
-
-(: The following external variables are set by the repo:deploy function :)
-
-(: file path pointing to the exist installation directory :)
-declare variable $home external;
-(: path to the directory containing the unpacked .xar package :)
-declare variable $dir external;
-(: the target collection into which the app is deployed :)
-declare variable $target external;
-
-(: Handle difference between 4.x.x and 5.x.x releases of eXist :)
-declare variable $local:copy-collection :=
- let $fnNew := function-lookup(xs:QName("xmldb:copy-collection"), 2)
- return
- if (exists($fnNew)) then $fnNew else function-lookup(xs:QName("xmldb:copy"), 2);
-
-declare function local:mkcol-recursive($collection, $components) {
- if (exists($components)) then
- let $newColl := concat($collection, "/", $components[1])
- return (
- xmldb:create-collection($collection, $components[1]),
- local:mkcol-recursive($newColl, subsequence($components, 2))
- )
- else
- ()
-};
-
-(: Helper function to recursively create a collection hierarchy. :)
-declare function local:mkcol($collection, $path) {
- local:mkcol-recursive($collection, tokenize($path, "/"))
-};
-
-declare function local:copy-current-public-to-temp() {
-local:mkcol("/db/", "temp"),
-if (xmldb:collection-available(concat($target, "/public"))) then
- $local:copy-collection(concat($target, "/public"), "/db/temp/")
-else
- ()
-};
-
-(: store the collection configuration :)
-local:mkcol("/db/system/config", $target),
-xmldb:store-files-from-pattern(concat("/system/config", $target), $dir, "**/*.xconf", "text/xml", true()),
-local:copy-current-public-to-temp()
diff --git a/public/collection.xconf.old b/public/collection.xconf.old
deleted file mode 100644
index be5c699..0000000
--- a/public/collection.xconf.old
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/repo.xml b/repo.xml
index b879ea7..65fb420 100644
--- a/repo.xml
+++ b/repo.xml
@@ -8,10 +8,17 @@
trueapplicationpublic-repo
- pre-install.xql
- post-install.xql
-
+ pre-install.xq
+ post-install.xq
+
+
+
+
IMPORTANT: You must first run the upgrade script in https://github.com/eXist-db/public-repo/blob/master/modules/upgrade-to-v2-storage.xq before installing v2!
+
Breaking: With this new version, packages are now stored outside of the public-repo app, to make upgrades of this app possible without a manual backup.
+
Note: Minimum required version of is eXist 5.0.0.
+
+
New: Avoid costly rescanning of all packages when publishing a new package - #51
diff --git a/resources/images/ajax-loader.gif b/resources/images/ajax-loader.gif
deleted file mode 100644
index 47adbf0..0000000
Binary files a/resources/images/ajax-loader.gif and /dev/null differ
diff --git a/resources/images/body-base.gif b/resources/images/body-base.gif
deleted file mode 100644
index 4f297e0..0000000
Binary files a/resources/images/body-base.gif and /dev/null differ
diff --git a/resources/images/body.gif b/resources/images/body.gif
deleted file mode 100644
index 91c9a3d..0000000
Binary files a/resources/images/body.gif and /dev/null differ
diff --git a/resources/images/close.png b/resources/images/close.png
deleted file mode 100644
index 8319264..0000000
Binary files a/resources/images/close.png and /dev/null differ
diff --git a/resources/images/download.png b/resources/images/download.png
deleted file mode 100644
index 4477b45..0000000
Binary files a/resources/images/download.png and /dev/null differ
diff --git a/resources/images/grey-box-bot.gif b/resources/images/grey-box-bot.gif
deleted file mode 100644
index 995a544..0000000
Binary files a/resources/images/grey-box-bot.gif and /dev/null differ
diff --git a/resources/images/grey-box-rpt.gif b/resources/images/grey-box-rpt.gif
deleted file mode 100644
index 4e17ced..0000000
Binary files a/resources/images/grey-box-rpt.gif and /dev/null differ
diff --git a/resources/images/grey-box-top.gif b/resources/images/grey-box-top.gif
deleted file mode 100644
index 20bc464..0000000
Binary files a/resources/images/grey-box-top.gif and /dev/null differ
diff --git a/resources/images/header.gif b/resources/images/header.gif
deleted file mode 100644
index 3f21eca..0000000
Binary files a/resources/images/header.gif and /dev/null differ
diff --git a/resources/images/horizontal.gif b/resources/images/horizontal.gif
deleted file mode 100644
index 970d0e5..0000000
Binary files a/resources/images/horizontal.gif and /dev/null differ
diff --git a/resources/images/install.png b/resources/images/install.png
deleted file mode 100644
index 02ef397..0000000
Binary files a/resources/images/install.png and /dev/null differ
diff --git a/resources/images/library2.gif b/resources/images/library.gif
similarity index 100%
rename from resources/images/library2.gif
rename to resources/images/library.gif
diff --git a/resources/images/nav-dropdown.gif b/resources/images/nav-dropdown.gif
deleted file mode 100644
index 0244f13..0000000
Binary files a/resources/images/nav-dropdown.gif and /dev/null differ
diff --git a/resources/images/nav-dropdown.png b/resources/images/nav-dropdown.png
deleted file mode 100644
index fd1e443..0000000
Binary files a/resources/images/nav-dropdown.png and /dev/null differ
diff --git a/resources/images/nav.gif b/resources/images/nav.gif
deleted file mode 100644
index 2f756cf..0000000
Binary files a/resources/images/nav.gif and /dev/null differ
diff --git a/resources/images/plugin2.gif b/resources/images/plugin.gif
similarity index 100%
rename from resources/images/plugin2.gif
rename to resources/images/plugin.gif
diff --git a/retrieve.html b/retrieve.html
deleted file mode 100644
index e4cd8ba..0000000
--- a/retrieve.html
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/templates/page-inner.html b/templates/page-inner.html
new file mode 100644
index 0000000..85c557a
--- /dev/null
+++ b/templates/page-inner.html
@@ -0,0 +1,169 @@
+
+
+
+ eXist-db Public App Repository
+
+
+
+
+
+
+
+
+
+
+