From 09bd021541ef8e14be2fa9d428023dafe3d20929 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sat, 13 Feb 2021 04:39:09 -0500 Subject: [PATCH 01/24] Breaking change to storage location & format --- admin.html | 6 +- build.properties | 2 +- collection.xconf | 6 -- controller.xql | 13 ++++- expath-pkg.xml.tmpl | 5 +- login.html | 2 +- modules/app.xql | 12 +++- modules/config.xqm | 14 +++-- modules/feed.xql | 2 +- modules/find.xql | 6 +- modules/list.xql | 4 +- modules/scan.xql | 55 +++++++++--------- modules/update.xql | 25 ++++++--- modules/upgrade-to-v2-storage.xq | 96 ++++++++++++++++++++++++++++++++ post-install.xq | 29 ++++++++++ post-install.xql | 63 --------------------- pre-install.xq | 18 ++++++ pre-install.xql | 47 ---------------- repo.xml | 11 +++- 19 files changed, 239 insertions(+), 177 deletions(-) delete mode 100644 collection.xconf create mode 100644 modules/upgrade-to-v2-storage.xq create mode 100644 post-install.xq delete mode 100644 post-install.xql create mode 100644 pre-install.xq delete mode 100644 pre-install.xql diff --git a/admin.html b/admin.html index fee94df..56cca13 100644 --- a/admin.html +++ b/admin.html @@ -4,8 +4,8 @@

Package Upload

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.

+ available packages immediately upon upload. Instead you will have to trigger an update afterwards, using + the "Publish" button below. This way you can upload multiple packages before publishing them.

@@ -31,7 +31,7 @@

Package Upload

- Return to list + Publish

Publishing will take a while to process apps.

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..dac35bb 100644 --- a/controller.xql +++ b/controller.xql @@ -1,5 +1,6 @@ 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 variable $exist:path external; @@ -53,7 +54,7 @@ else if ($exist:resource eq "update.xql") then 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) = "repo") then @@ -91,6 +92,16 @@ else if (ends-with($exist:resource, ".html")) then +else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".xar")) then + + + + +else if (contains($exist:path, "/public/") and (ends-with($exist:resource, ".png") or ends-with($exist:resource, ".svg"))) then + + + + else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".zip")) then diff --git a/expath-pkg.xml.tmpl b/expath-pkg.xml.tmpl index 067baea..37c4778 100644 --- a/expath-pkg.xml.tmpl +++ b/expath-pkg.xml.tmpl @@ -1,7 +1,8 @@ eXist-db Public Application Repository - - + + + diff --git a/login.html b/login.html index d21fee3..f7568fe 100644 --- a/login.html +++ b/login.html @@ -2,7 +2,7 @@

Administrator Login

- 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.
diff --git a/modules/app.xql b/modules/app.xql index a912e00..2ccbeb3 100644 --- a/modules/app.xql +++ b/modules/app.xql @@ -5,8 +5,14 @@ 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:abs-path-to-apps-xml($node as node(), $model as map(*), $mode as xs:string?) { + let $url := request:get-parameter("app-root-absolute-url", ()) || "/public/apps.xml" + return + {$url} +}; + declare function app:list-packages($node as node(), $model as map(*), $mode as xs:string?) { - for $app in doc($config:apps-meta)//app + for $app in doc($config:apps-meta)//package-group let $show-details := false() order by lower-case($app/title) return @@ -17,7 +23,7 @@ declare function app:view-package($node as node(), $model as map(*), $mode as xs 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 + let $apps := $matching-abbrev/parent::package-group return ( if (count($apps) gt 1) then @@ -80,7 +86,7 @@ declare function app:view-package($node as node(), $model as map(*), $mode as xs ) }; -declare function app:package-to-list-item($app as element(app), $version as xs:string, $show-details as xs:boolean) { +declare function app:package-to-list-item($app as element(package-group), $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 diff --git a/modules/config.xqm b/modules/config.xqm index be7d211..dbf2340 100644 --- a/modules/config.xqm +++ b/modules/config.xqm @@ -25,13 +25,15 @@ declare variable $config:app-root := substring-before($modulePath, "/modules") ; -declare variable $config:public := concat($config:app-root, "/public"); +(: binaries and metadata are stored in the /db/apps/public-repo-data collection :) +declare variable $config:public := concat($config:app-root, "-data/packages"); +declare variable $config:icons := concat($config:app-root, "-data/icons"); +declare variable $config:metadata-collection := concat($config:app-root, "-data/metadata"); -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:apps-doc := "package-groups.xml"; +declare variable $config:apps-meta := concat($config:metadata-collection, "/", $config:apps-doc); +declare variable $config:packages-doc := "raw-packages.xml"; +declare variable $config:packages-meta := concat($config:metadata-collection, "/", $config:packages-doc); (:~ : Returns the repo.xml descriptor for the current application. diff --git a/modules/feed.xql b/modules/feed.xql index 71c1cd5..df635c6 100644 --- a/modules/feed.xql +++ b/modules/feed.xql @@ -6,7 +6,7 @@ declare option exist:serialize "method=xml media-type=application/atom+xml"; 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 $app in doc($config:apps-meta)//package-group let $icon := if ($app/icon) then if ($app/@status) then diff --git a/modules/find.xql b/modules/find.xql index 978cb09..d6114ff 100644 --- a/modules/find.xql +++ b/modules/find.xql @@ -18,9 +18,9 @@ let $procVersion := request:get-parameter("processor", "2.2.0") let $app-root-absolute-url := request:get-parameter("app-root-absolute-url", ()) let $app := if ($name) then - doc($config:apps-meta)//app[name eq $name] + doc($config:apps-meta)//package-group[name eq $name] else - doc($config:apps-meta)//app[abbrev eq $abbrev] + doc($config:apps-meta)//package-group[abbrev eq $abbrev] let $app-versions := ($app, $app/other/version) let $compatible-xar := app:find-version($app-versions, $procVersion, $version, $semVer, $minVersion, $maxVersion) return @@ -28,7 +28,7 @@ return let $abs-public := $app-root-absolute-url || "/public/" return if ($info) then - let $app := doc($config:apps-meta)//(app|version)[@path eq $compatible-xar] + let $app := doc($config:apps-meta)//(package-group|version)[@path eq $compatible-xar] return {$app/@sha256,($app/version,$app/@version)[1] ! attribute version {.},$compatible-xar} else if ($zip) then diff --git a/modules/list.xql b/modules/list.xql index d747ae7..9eac18f 100644 --- a/modules/list.xql +++ b/modules/list.xql @@ -78,8 +78,8 @@ let $version := if (matches($version, "^\d+\.\d+\.\d+-?.*$")) then $version else return { - for $app in doc($config:apps-meta)//app + for $app in doc($config:apps-meta)//package-group return - list:get-app($app, $version) + element app { list:get-app($app, $version) ! (@*, *) } } diff --git a/modules/scan.xql b/modules/scan.xql index f4c13e5..e12cd08 100644 --- a/modules/scan.xql +++ b/modules/scan.xql @@ -20,7 +20,7 @@ declare function scanrepo:is-older-or-same($version1 as xs:string, $version2 as semver:le($version1, $version2, true()) }; -declare function scanrepo:process($apps as element(app)*) { +declare function scanrepo:process($apps as element(package)*) { for $app in $apps order by $app/title group by $name := $app/name @@ -41,7 +41,7 @@ declare function scanrepo:process($apps as element(app)*) { let $older-versions := $sorted-versions => tail() let $abbrevs := distinct-values($app/abbrev) return - + { $newest-version/@*, $newest-version/*, @@ -65,7 +65,7 @@ declare function scanrepo:process($apps as element(app)*) { } } - + }; declare function scanrepo:find-newest($apps as element()*, $newest as element()?, $procVersion as xs:string?) { @@ -145,18 +145,16 @@ declare %private function scanrepo:compare-versions($installed as xs:string*, $a $compare(head($available), head($installed)) }; -declare function scanrepo:handle-icon ($path as xs:string, $data as item()?, $param as item()*) as element(icon) { +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) - + let $stored := xmldb:store($config:icons, $name, $data) return { $name } }; -declare function scanrepo:handle-expath-package ($root as element(expath:package)) as element()* { +declare function scanrepo:handle-expath-package($root as element(expath:package)) as element()* { {$root/@name/string()}, {$root/expath:title/text()}, {$root/@abbrev/string()}, @@ -167,7 +165,7 @@ declare function scanrepo:handle-expath-package ($root as element(expath:package () }; -declare function scanrepo:handle-repo-meta ($root as element(repo:meta)) as element()+ { +declare function scanrepo:handle-repo-meta($root as element(repo:meta)) as element()+ { for $author in $root/repo:author return {$author/text()} @@ -222,14 +220,12 @@ declare function scanrepo:entry-filter($path as xs:anyURI, $type as xs:string, $ $path = ("repo.xml", "expath-pkg.xml") }; -declare function scanrepo:extract-metadata ($resource as xs:string) as element(app) { +declare function scanrepo:extract-metadata($resource as xs:string) as element(package) { let $xar := concat($config:public, "/", $resource) let $data := util:binary-doc($xar) - let $hash := crypto:hash($data, "sha256", "hex") - return - + { compression:unzip( $data, @@ -239,7 +235,7 @@ declare function scanrepo:extract-metadata ($resource as xs:string) as element(a $resource ) } - + }; declare function scanrepo:scan-all() { @@ -250,32 +246,33 @@ declare function scanrepo:scan-all() { }; declare function scanrepo:scan() { - let $data := doc($config:packages-meta)//app - let $processed := { scanrepo:process($data) } + let $data := doc($config:packages-meta)//package + let $processed := { scanrepo:process($data) } let $store := xmldb:store($config:metadata-collection, $config:apps-doc, $processed) - - return $processed + return + $processed }; declare function scanrepo:rebuild-package-meta() as xs:string { xmldb:store($config:metadata-collection, $config:packages-doc, - { scanrepo:scan-all() }) + { 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] - +declare function scanrepo:add-package-meta($meta as element(package)) { + let $packages := doc($config:packages-meta)/raw-packages + let $node-to-update := $packages/package[@path = $meta/@path] return - if (exists($node-to-update)) - then (update replace $node-to-update with $meta) - else (update insert $meta into $packages) + 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 + let $meta := + $xar => scanrepo:extract-metadata() => scanrepo:add-package-meta() - - return scanrepo:scan() + return + scanrepo:scan() }; diff --git a/modules/update.xql b/modules/update.xql index ed02740..7fe692d 100644 --- a/modules/update.xql +++ b/modules/update.xql @@ -1,11 +1,22 @@ -xquery version "1.0"; +xquery version "3.1"; + +(: rebuild metadata for all packages :) import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xql"; -scanrepo:rebuild-package-meta() -=> scanrepo:scan() +declare namespace sm="http://exist-db.org/xquery/securitymanager"; -(: $file -=> scanrepo:extract-metadata() -=> scanrepo:add-package-meta() -=> scanrepo:scan() :) +if (sm:id()/sm:id/sm:real/sm:groups/sm:group = "repo") then + ( + scanrepo:rebuild-package-meta(), + scanrepo:scan() + ) +else + system:as-user( + "repo", + "repo", + ( + scanrepo:rebuild-package-meta(), + scanrepo:scan() + ) + ) \ No newline at end of file diff --git a/modules/upgrade-to-v2-storage.xq b/modules/upgrade-to-v2-storage.xq new file mode 100644 index 0000000..48b68f4 --- /dev/null +++ b/modules/upgrade-to-v2-storage.xq @@ -0,0 +1,96 @@ +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 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 + (), + + (: move public-repo-1.1.0 metadata into new collections and names :) + if (xmldb:collection-available("/db/apps/public-repo/meta")) then + ( + xmldb:store("/db/apps/public-repo-data/metadata", "packages-raw.xml", element packages-raw { doc("/db/apps/public-repo/meta/packages.xml")/packages/app ! element package { ./@*, ./* } }) ! element packages-raw { . }, + xmldb:store("/db/apps/public-repo-data/metadata", "package-groups.xml", element package-groups { doc("/db/apps/public-repo/meta/apps.xml")/apps/app ! element package-group { ./@*, ./* } } ) ! element package-group { . } , + for $doc in ("/db/apps/public-repo-data/metadata/packages-raw.xml", "/db/apps/public-repo-data/metadata/package-groups.xml") + return + local:chgrp-repo($doc) + ) + else + (), + + (: uninstall :) + (: + :) + repo:undeploy("http://exist-db.org/apps/public-repo") ! element undeploy-status { . }, + repo:remove("http://exist-db.org/apps/public-repo") ! element remove-status { . } +}; + +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." } + } \ No newline at end of file diff --git a/post-install.xq b/post-install.xq new file mode 100644 index 0000000..5a62e7b --- /dev/null +++ b/post-install.xq @@ -0,0 +1,29 @@ +xquery version "3.1"; + +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.xql"; + +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"; + +declare function local:chgrp-repo($resource) { + if (sm:get-permissions($resource)/*/@group = "repo") then + () + else + ( + sm:chown($resource, "repo"), + sm:chgrp($resource, "repo") + ) +}; + +(: set public-repo-data to "repo" group ownership if needed :) +for $col in ("/db/apps/public-repo-data", xmldb:get-child-collections("/db/apps/public-repo-data") ! ("/db/apps/public-repo-data/" || .)) +return + local:chgrp-repo($col), + +(: build package metadata if missing :) +if (doc-available($config:packages-meta) and doc-available($config:apps-meta)) then + () +else + system:as-user("repo", "repo", (scanrepo:rebuild-package-meta(), scanrepo:scan())) \ 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..bd0f16b --- /dev/null +++ b/pre-install.xq @@ -0,0 +1,18 @@ +xquery version "3.1"; + +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; + +(: create public-repo-data and subcollections :) +xmldb:create-collection("/db/apps", "public-repo-data"), +for $col in ("icons", "metadata", "packages") +return + xmldb:create-collection("/db/apps/public-repo-data", $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/repo.xml b/repo.xml index b879ea7..3c2fd3a 100644 --- a/repo.xml +++ b/repo.xml @@ -8,10 +8,17 @@ true application public-repo - pre-install.xql - post-install.xql + pre-install.xq + post-install.xq + +
    +
  • IMPORTANT: You must first run the upgrade script in TODO_INSERT_URL before installing v2!
  • +
  • Breaking: Move packages storage outside of public-repo app to fix upgrade procedure, which previously deleted all packages, and simplify future backups and upgrades.
  • +
  • Note: Minimum required version of is eXist 5.0.0.
  • +
+
  • New: Avoid costly rescanning of all packages when publishing a new package - #51
  • From 4cf48f02f57ea59b320f9943dc216a413741b65a Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sat, 13 Feb 2021 04:39:31 -0500 Subject: [PATCH 02/24] Remove assets that are no longer needed --- meta/apps.xml | 1 - meta/packages.xml | 1 - modules/auto-update.xql | 16 ---------------- public/collection.xconf.old | 8 -------- 4 files changed, 26 deletions(-) delete mode 100644 meta/apps.xml delete mode 100644 meta/packages.xml delete mode 100644 modules/auto-update.xql delete mode 100644 public/collection.xconf.old 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/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/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 From 8ce2c690634818773bea0f447975ef29c8964ba7 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sat, 13 Feb 2021 12:38:58 -0500 Subject: [PATCH 03/24] Apply XQuery file extension convention .xq for main modules, .xqm for library modules --- admin.html | 2 +- controller.xql | 20 ++++++++++---------- modules/{app.xql => app.xqm} | 2 +- modules/{feed.xql => feed.xq} | 0 modules/{find.xql => find.xq} | 2 +- modules/{list.xql => list.xq} | 0 modules/{scan.xql => scan.xqm} | 0 modules/{update.xql => update.xq} | 2 +- modules/{upload.xql => upload.xq} | 2 +- modules/{view.xql => view.xq} | 2 +- post-install.xq | 2 +- 11 files changed, 17 insertions(+), 17 deletions(-) rename modules/{app.xql => app.xqm} (99%) rename modules/{feed.xql => feed.xq} (100%) rename modules/{find.xql => find.xq} (99%) rename modules/{list.xql => list.xq} (100%) rename modules/{scan.xql => scan.xqm} (100%) rename modules/{update.xql => update.xq} (94%) rename modules/{upload.xql => upload.xq} (97%) rename modules/{view.xql => view.xq} (98%) diff --git a/admin.html b/admin.html index 56cca13..9d36df0 100644 --- a/admin.html +++ b/admin.html @@ -46,7 +46,7 @@

    Package Upload

    $(function () { 'use strict'; $('#fileupload').fileupload({ - url: "modules/upload.xql", + url: "modules/upload.xq", dataType: 'json', done: function (e, data) { $.each(data.result.files, function (index, file) { diff --git a/controller.xql b/controller.xql index dac35bb..92a44ec 100644 --- a/controller.xql +++ b/controller.xql @@ -34,15 +34,15 @@ 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:resource eq "update.xql") then - + - + @@ -57,7 +57,7 @@ else if ($exist:path eq "/admin.html") then if (exists($user) and sm:get-user-groups($user) = "repo") then - + @@ -66,7 +66,7 @@ else if ($exist:path eq "/admin.html") then - + @@ -76,17 +76,17 @@ else if (ends-with($exist:resource, ".html") and starts-with($exist:path, "/pack - + 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 :) - + @@ -109,14 +109,14 @@ else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".zip" else if ($exist:path eq "/find" or ends-with($exist:resource, ".zip")) then - + else if ($exist:resource eq "feed.xml") then - + else if (contains($exist:path, "/$shared/")) then diff --git a/modules/app.xql b/modules/app.xqm similarity index 99% rename from modules/app.xql rename to modules/app.xqm index 2ccbeb3..21a7059 100644 --- a/modules/app.xql +++ b/modules/app.xqm @@ -3,7 +3,7 @@ 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"; +import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm"; declare function app:abs-path-to-apps-xml($node as node(), $model as map(*), $mode as xs:string?) { let $url := request:get-parameter("app-root-absolute-url", ()) || "/public/apps.xml" diff --git a/modules/feed.xql b/modules/feed.xq similarity index 100% rename from modules/feed.xql rename to modules/feed.xq diff --git a/modules/find.xql b/modules/find.xq similarity index 99% rename from modules/find.xql rename to modules/find.xq index d6114ff..9bad709 100644 --- a/modules/find.xql +++ b/modules/find.xq @@ -3,7 +3,7 @@ xquery version "3.1"; import module namespace request="http://exist-db.org/xquery/request"; import module namespace response="http://exist-db.org/xquery/response"; -import module namespace app="http://exist-db.org/xquery/app" at "app.xql"; +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"; let $abbrev := request:get-parameter("abbrev", ()) diff --git a/modules/list.xql b/modules/list.xq similarity index 100% rename from modules/list.xql rename to modules/list.xq diff --git a/modules/scan.xql b/modules/scan.xqm similarity index 100% rename from modules/scan.xql rename to modules/scan.xqm diff --git a/modules/update.xql b/modules/update.xq similarity index 94% rename from modules/update.xql rename to modules/update.xq index 7fe692d..6eea213 100644 --- a/modules/update.xql +++ b/modules/update.xq @@ -2,7 +2,7 @@ xquery version "3.1"; (: rebuild metadata for all packages :) -import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xql"; +import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xq"; declare namespace sm="http://exist-db.org/xquery/securitymanager"; diff --git a/modules/upload.xql b/modules/upload.xq similarity index 97% rename from modules/upload.xql rename to modules/upload.xq index c4cf8a8..7f5664d 100644 --- a/modules/upload.xql +++ b/modules/upload.xq @@ -1,7 +1,7 @@ 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"; +import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm"; declare namespace json="http://www.json.org"; diff --git a/modules/view.xql b/modules/view.xq similarity index 98% rename from modules/view.xql rename to modules/view.xq index 2022d4d..c0e8db4 100644 --- a/modules/view.xql +++ b/modules/view.xq @@ -3,7 +3,7 @@ xquery version "3.0"; 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"; +import module namespace app="http://exist-db.org/xquery/app" at "app.xqm"; declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization"; diff --git a/post-install.xq b/post-install.xq index 5a62e7b..989207f 100644 --- a/post-install.xq +++ b/post-install.xq @@ -1,7 +1,7 @@ xquery version "3.1"; 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.xql"; +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"; From 2ea86dbcb20aaf89f1b69519a40c28f45c40fad7 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Mon, 15 Feb 2021 23:48:23 -0500 Subject: [PATCH 04/24] Clean newlines at end of xq and html files --- admin.html | 2 +- index.html | 2 +- login.html | 2 +- modules/download-xar-zip.xq | 1 - modules/feed.xq | 2 +- modules/update.xq | 2 +- modules/upgrade-to-v2-storage.xq | 2 +- modules/upload.xq | 2 +- modules/view.xq | 2 +- packages.html | 2 +- post-install.xq | 2 +- retrieve.html | 2 +- templates/page.html | 2 +- 13 files changed, 12 insertions(+), 13 deletions(-) diff --git a/admin.html b/admin.html index 9d36df0..4c25b18 100644 --- a/admin.html +++ b/admin.html @@ -68,4 +68,4 @@

    Package Upload

    .parent().addClass($.support.fileInput ? undefined : 'disabled'); }); -
\ No newline at end of file +
diff --git a/index.html b/index.html index 27c906d..b62d051 100644 --- a/index.html +++ b/index.html @@ -36,4 +36,4 @@

Admin

(requires login)

- \ No newline at end of file + diff --git a/login.html b/login.html index f7568fe..a222833 100644 --- a/login.html +++ b/login.html @@ -23,4 +23,4 @@

Administrator Login

- \ No newline at end of file + diff --git a/modules/download-xar-zip.xq b/modules/download-xar-zip.xq index d413684..0c40037 100644 --- a/modules/download-xar-zip.xq +++ b/modules/download-xar-zip.xq @@ -14,4 +14,3 @@ return let $zip := compression:zip($entry, false()) return response:stream-binary($zip, "application/zip") - diff --git a/modules/feed.xq b/modules/feed.xq index df635c6..e533bd5 100644 --- a/modules/feed.xq +++ b/modules/feed.xq @@ -103,4 +103,4 @@ declare function local:feed() { }; -local:feed() \ No newline at end of file +local:feed() diff --git a/modules/update.xq b/modules/update.xq index 6eea213..0152aa9 100644 --- a/modules/update.xq +++ b/modules/update.xq @@ -19,4 +19,4 @@ else scanrepo:rebuild-package-meta(), scanrepo:scan() ) - ) \ No newline at end of file + ) diff --git a/modules/upgrade-to-v2-storage.xq b/modules/upgrade-to-v2-storage.xq index 48b68f4..fc337fd 100644 --- a/modules/upgrade-to-v2-storage.xq +++ b/modules/upgrade-to-v2-storage.xq @@ -93,4 +93,4 @@ if (repo:list() = "http://exist-db.org/apps/public-repo") then else element status { element description { "No action taken. The public-repo app is not installed." } - } \ No newline at end of file + } diff --git a/modules/upload.xq b/modules/upload.xq index 7f5664d..39bd8c2 100644 --- a/modules/upload.xq +++ b/modules/upload.xq @@ -32,4 +32,4 @@ return {$name} {$err:description} - } \ No newline at end of file + } diff --git a/modules/view.xq b/modules/view.xq index c0e8db4..e526e52 100644 --- a/modules/view.xq +++ b/modules/view.xq @@ -24,4 +24,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..8556986 100644 --- a/packages.html +++ b/packages.html @@ -13,4 +13,4 @@

Public Application Repository

- \ No newline at end of file + diff --git a/post-install.xq b/post-install.xq index 989207f..cdd9f61 100644 --- a/post-install.xq +++ b/post-install.xq @@ -26,4 +26,4 @@ return if (doc-available($config:packages-meta) and doc-available($config:apps-meta)) then () else - system:as-user("repo", "repo", (scanrepo:rebuild-package-meta(), scanrepo:scan())) \ No newline at end of file + system:as-user("repo", "repo", (scanrepo:rebuild-package-meta(), scanrepo:scan())) diff --git a/retrieve.html b/retrieve.html index e4cd8ba..5c105cf 100644 --- a/retrieve.html +++ b/retrieve.html @@ -1,4 +1,4 @@
  • -
\ No newline at end of file + diff --git a/templates/page.html b/templates/page.html index 3a2f8fe..48c9a88 100644 --- a/templates/page.html +++ b/templates/page.html @@ -166,4 +166,4 @@ - \ No newline at end of file + From 5afa0b49c355990baadd1b4d5a85d15248c0f93f Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Tue, 16 Feb 2021 00:23:14 -0500 Subject: [PATCH 05/24] Remove dependency on expath crypto library --- expath-pkg.xml.tmpl | 1 - modules/scan.xqm | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/expath-pkg.xml.tmpl b/expath-pkg.xml.tmpl index 37c4778..b03a6bb 100644 --- a/expath-pkg.xml.tmpl +++ b/expath-pkg.xml.tmpl @@ -2,7 +2,6 @@ eXist-db Public Application Repository - diff --git a/modules/scan.xqm b/modules/scan.xqm index e12cd08..d1da879 100644 --- a/modules/scan.xqm +++ b/modules/scan.xqm @@ -51,11 +51,10 @@ declare function scanrepo:process($apps as element(package)*) { { for $older in $older-versions let $xar := concat($config:public, "/", $older/@path) - let $hash := crypto:hash( - util:binary-doc($xar), - "sha256", - "hex" - ) + let $hash := + util:binary-doc($xar) + => util:binary-doc-content-digest("SHA-256") + => string() return { $older/@path, @@ -223,7 +222,10 @@ declare function scanrepo:entry-filter($path as xs:anyURI, $type as xs:string, $ declare function scanrepo:extract-metadata($resource as xs:string) as element(package) { let $xar := concat($config:public, "/", $resource) let $data := util:binary-doc($xar) - let $hash := crypto:hash($data, "sha256", "hex") + let $hash := + $data + => util:binary-doc-content-digest("SHA-256") + => string() return { From d6b6b38cc2cc0caf52a0bc8be8861d278cfb1ea7 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Tue, 16 Feb 2021 00:26:22 -0500 Subject: [PATCH 06/24] Set XQuery version to 3.1 for all modules --- modules/app.xqm | 2 +- modules/download-xar-zip.xq | 2 +- modules/feed.xq | 2 +- modules/list.xq | 2 +- modules/upload.xq | 2 +- modules/view.xq | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/app.xqm b/modules/app.xqm index 21a7059..d5b68f0 100644 --- a/modules/app.xqm +++ b/modules/app.xqm @@ -1,4 +1,4 @@ -xquery version "3.0"; +xquery version "3.1"; module namespace app="http://exist-db.org/xquery/app"; diff --git a/modules/download-xar-zip.xq b/modules/download-xar-zip.xq index 0c40037..6e930ad 100644 --- a/modules/download-xar-zip.xq +++ b/modules/download-xar-zip.xq @@ -1,4 +1,4 @@ -xquery version "3.0"; +xquery version "3.1"; import module namespace compression="http://exist-db.org/xquery/compression"; import module namespace response="http://exist-db.org/xquery/response"; diff --git a/modules/feed.xq b/modules/feed.xq index e533bd5..26898a9 100644 --- a/modules/feed.xq +++ b/modules/feed.xq @@ -1,4 +1,4 @@ -xquery version "3.0"; +xquery version "3.1"; import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; diff --git a/modules/list.xq b/modules/list.xq index 9eac18f..be805eb 100644 --- a/modules/list.xq +++ b/modules/list.xq @@ -1,4 +1,4 @@ -xquery version "3.0"; +xquery version "3.1"; declare namespace list="http://exist-db.org/apps/public-repo/list"; declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; diff --git a/modules/upload.xq b/modules/upload.xq index 39bd8c2..aa900b6 100644 --- a/modules/upload.xq +++ b/modules/upload.xq @@ -1,4 +1,4 @@ -xquery version "3.0"; +xquery version "3.1"; 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"; diff --git a/modules/view.xq b/modules/view.xq index e526e52..1ba98df 100644 --- a/modules/view.xq +++ b/modules/view.xq @@ -1,4 +1,4 @@ -xquery version "3.0"; +xquery version "3.1"; 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"; From 90eb6bb9f50df93ba6473a2d866fb2ae2f89d7b8 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Thu, 18 Feb 2021 10:51:20 -0500 Subject: [PATCH 07/24] Toward tackling technical debt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - instead of making only minimal changes to achieve the goal of the PR, this commit comprehensively updates the app to reflect the goal of the PR - apply newly adopted wording for package metadata structures to all functions, so what was “app” is now “package”, “apps” is “package-group”, etc. applied the new naming consistently. - use config.xqm to centrally define the names of collections for metadata storage, as well as to store values like the default eXist version (2.2.0) and processor URI; similarly, read the repo user and group centrally from repo.xml instead of sprinkling hardcoded references to these values where needed. - refactor the publish/upload routing by only updating the package-group of the uploaded apps, instead of regenerating all package groups - only calculate a xar package’s sha-256 hash once instead of twice/repeatedly - restructure package-groups to not store any package-specific information at the top level; instead, only package identifiers (name, abbrev, title) are stored there, and all package-specific information is grouped in a child packages element - fix the problem where a listing of packages compatible with an older version of eXist shows incorrectly package information for the latest (incompatible) version. now all views correctly limit themselves to compatible versions. any incompatible versions that must be shown are - eliminate redundant functions for finding compatible packages by creating a new versions.xqm library module - add comments to every module header and to many functions - add namespaces and module import declarations for all referenced libraries in every module --- admin.html | 8 +- controller.xql | 6 +- modules/app.xqm | 279 +++++++++++--------- modules/config.xqm | 48 +++- modules/download-xar-zip.xq | 24 +- modules/feed.xq | 59 +++-- modules/find.xq | 43 ++-- modules/list.xq | 118 ++++----- modules/scan.xqm | 426 ++++++++++++++----------------- modules/update.xq | 35 ++- modules/upgrade-to-v2-storage.xq | 23 +- modules/upload.xq | 51 ++-- modules/versions.xqm | 143 +++++++++++ modules/view.xq | 12 +- post-install.xq | 48 +++- pre-install.xq | 35 ++- repo.xml | 6 +- 17 files changed, 801 insertions(+), 563 deletions(-) create mode 100644 modules/versions.xqm diff --git a/admin.html b/admin.html index 4c25b18..a4e4750 100644 --- a/admin.html +++ b/admin.html @@ -3,9 +3,8 @@

Package Upload

-

Upload xar packages to the public repository. New packages will not appear in the list of - available packages immediately upon upload. Instead you will have to trigger an update afterwards, using - the "Publish" button below. This way you can upload multiple packages before publishing them.

+

Upload xar packages to the public repository. New packages will appear in the list of + available packages immediately upon upload.

@@ -31,10 +30,9 @@

Package Upload

- Publish + Return to list

-

Publishing will take a while to process apps.

Log out
diff --git a/controller.xql b/controller.xql index 92a44ec..6b9c235 100644 --- a/controller.xql +++ b/controller.xql @@ -92,14 +92,16 @@ else if (ends-with($exist:resource, ".html")) then +(: TODO figure out how to turn the absolute path $config:app-data-parent-col + : into the relative path needed for the forward directive - joewiz :) else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".xar")) then - + else if (contains($exist:path, "/public/") and (ends-with($exist:resource, ".png") or ends-with($exist:resource, ".svg"))) then - + else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".zip")) then diff --git a/modules/app.xqm b/modules/app.xqm index d5b68f0..d044428 100644 --- a/modules/app.xqm +++ b/modules/app.xqm @@ -1,9 +1,16 @@ 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 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 function app:abs-path-to-apps-xml($node as node(), $model as map(*), $mode as xs:string?) { let $url := request:get-parameter("app-root-absolute-url", ()) || "/public/apps.xml" @@ -11,29 +18,37 @@ declare function app:abs-path-to-apps-xml($node as node(), $model as map(*), $mo {$url} }; +(:~ + : 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 $app in doc($config:apps-meta)//package-group + for $package-group in doc($config:package-groups-doc)//package-group let $show-details := false() - order by lower-case($app/title) + order by lower-case($package-group/title) return - app:package-to-list-item($app, $app/version, $show-details) + 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", "2.2.0") - let $matching-abbrev := doc($config:apps-meta)//abbrev[. eq $abbrev] - let $apps := $matching-abbrev/parent::package-group + 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($apps) gt 1) then + 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 $app in $apps - let $name := $app/name + for $package-group in $package-groups + let $name := $package-group/name order by $name return
  1. {$name/string()}
  2. @@ -45,15 +60,17 @@ declare function app:view-package($node as node(), $model as map(*), $mode as xs () , let $listing := - for $app in $apps + 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 ($matching-abbrev/@type eq "legacy") then - let $current-abbrev := $app/abbrev[not(@type eq "legacy")] + 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 $required-exist-version := $app/requires[@processor eq "http://exist-db.org"]/(@version, @semver-min)[1] + 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 @@ -65,16 +82,21 @@ declare function app:view-package($node as node(), $model as map(*), $mode as xs 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 $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 (exists($package)) then - app:package-to-list-item($app, $version, $show-details) + 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 -
  3. 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.
  4. +
  5. 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.
  6. return if (exists($listing)) then $listing @@ -86,34 +108,41 @@ declare function app:view-package($node as node(), $model as map(*), $mode as xs ) }; -declare function app:package-to-list-item($app as element(package-group), $version as xs:string, $show-details as xs:boolean) { +(:~ + : 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 ($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 $path := ($app//version[@version eq $version]/@path, $app/@path)[1] - let $requires := ($app//version[@version eq $version]/requires, $app/requires)[1] + let $path := $newest-package/@path + let $requires := $newest-package/requires let $download-url := concat($repoURL, "public/", $path) - let $required-exist-version := $requires[@processor eq "http://exist-db.org"]/(@version, @semver-min)[1] + let $required-exist-version := $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/", $package-group/abbrev[not(@type eq "legacy")], ".html", if ($required-exist-version) then concat("?eXist-db-min-version=", $required-exist-version) else () ) return -
  7. +
  8. { - switch ($app/type) + switch ($newest-package/type) case ("application") return application case ("library") return @@ -122,86 +151,119 @@ declare function app:package-to-list-item($app as element(package-group), $versi plugin default return () } -

    {$app/title/text()}

    +

    {$package-group/title/string()}

    { if ($show-details) then - - - - - - - - - - - - - { - if ($app/requires) then - - - - - else - () - } - - - - - - - - - - - - - - - - - { - if ($app/website != "") then - - - - + { + if ($newest-package) then + ( + + + + , + + + + , + + + + , + if ($newest-package/requires) then + + + + + else + (), + + + + , + + + + , + + + + , + + + + , + if ($newest-package/website != "") then + + + + + else + (), + + + + + ) else - () +

    No versions of {string-join($package-group/abbrev, " or ")} found that are compatible with eXist-db {$procVersion}+.

    } - - - - { - if ($app/other/version) then + if ($older-packages or $incompatible-packages) then - - + + else () } { - if ($app/changelog/change) then + if (exists($newest-package/changelog/change)) then ( , - for $change in $app/changelog/change + for $change in $newest-package/changelog/change let $version := $change/@version/string() let $comment := $change/node() order by $version descending @@ -216,35 +278,24 @@ declare function app:package-to-list-item($app as element(package-group), $versi
    Description:{ $app/description/text() }
    Version:{ $version }
    Size:{ $app/@size idiv 1024 }k
    Requirement:eXist-db { if ($requires) then app:requires-to-english($requires) else () }
    Short Title:{ $app/abbrev[not(@type)]/text() }
    Package Name (URI):{ $app/name/string() }
    Author(s):{string-join($app/author, ", ")}
    License:{ $app/license/text() }
    Website:{ $app/website/text() }
    Description:{ $newest-package/description/string() }
    Version:{ $newest-package/version/string() }
    Size:{ $newest-package/@size idiv 1024 }k
    Requirement:eXist-db { if ($requires) then app:requires-to-english($requires) else () }
    Short Title:{ $newest-package/abbrev[not(@type)]/string() }
    Package Name (URI):{ $newest-package/name/string() }
    Author(s):{string-join($newest-package/author, ", ")}
    License:{ $newest-package/license/string() }
    Website:{ $newest-package/website/string() }
    Download:{$path/string()}
    Download:{$path/string()}
    Download older versions:{ - 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 () - ) - }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 +
    • + {$package/version/string()} +
    • , + + (: 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 } + { + " (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 + () + ) + || ".)" + } +
    • + }
    +
    Change log:
    else

    - {$app/description/text()} + {$newest-package/description/string()}
    - Version {$version} { + Version {$newest-package/version} { if ($requires) then concat(" (Requires eXist-db ", app:requires-to-english($requires), ".)") else () }
    - Read more information, or download {$app/@path/string()}. + Read more information, or download {$newest-package/@path/string()}.

    }
  9. }; -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 -}; - +(:~ + : 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 @@ -256,5 +307,5 @@ declare function app:requires-to-english($requires as element()) { else if ($requires/@semver-max) then concat(" version ", $requires/@semver-max, " or earlier") else - " version 2.2" + " version " || $config:default-exist-version }; diff --git a/modules/config.xqm b/modules/config.xqm index dbf2340..79a949a 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 system="http://exist-db.org/xquery/system"; + declare namespace repo="http://exist-db.org/xquery/repo"; declare namespace expath="http://expath.org/ns/pkg"; -(: - 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 := @@ -25,15 +29,33 @@ declare variable $config:app-root := substring-before($modulePath, "/modules") ; -(: binaries and metadata are stored in the /db/apps/public-repo-data collection :) -declare variable $config:public := concat($config:app-root, "-data/packages"); -declare variable $config:icons := concat($config:app-root, "-data/icons"); -declare variable $config:metadata-collection := concat($config:app-root, "-data/metadata"); +(: 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:apps-doc := "package-groups.xml"; -declare variable $config:apps-meta := concat($config:metadata-collection, "/", $config:apps-doc); -declare variable $config:packages-doc := "raw-packages.xml"; -declare variable $config:packages-meta := concat($config:metadata-collection, "/", $config:packages-doc); +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:package-groups-doc-name := "package-groups.xml"; +declare variable $config:raw-packages-doc-name := "raw-packages.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; + +(: 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. diff --git a/modules/download-xar-zip.xq b/modules/download-xar-zip.xq index 6e930ad..155b5d1 100644 --- a/modules/download-xar-zip.xq +++ b/modules/download-xar-zip.xq @@ -1,16 +1,20 @@ xquery version "3.1"; -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"; +(:~ + : Allows download of packages via zip extension + : + : Responds to requests like /exist/apps/public-repo/public/eXide-1.0.0.xar.zip + :) 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) +declare namespace compression="http://exist-db.org/xquery/compression"; +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"; + +(: strip .zip from resource name :) +let $xar-filename := fn:replace(request:get-url(), ".*/(.*)\.zip", "$1") +let $xar := util:binary-doc($config:packages-col || "/" || $xar-filename) return - let $entry := - {$xar} - let $zip := compression:zip($entry, false()) - return - response:stream-binary($zip, "application/zip") + response:stream-binary($xar, "application/zip") diff --git a/modules/feed.xq b/modules/feed.xq index 26898a9..aab9ccd 100644 --- a/modules/feed.xq +++ b/modules/feed.xq @@ -1,47 +1,68 @@ 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)//package-group + 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 diff --git a/modules/find.xq b/modules/find.xq index 9bad709..5ddb9af 100644 --- a/modules/find.xq +++ b/modules/find.xq @@ -1,10 +1,17 @@ xquery version "3.1"; -import module namespace request="http://exist-db.org/xquery/request"; -import module namespace response="http://exist-db.org/xquery/response"; +(:~ + : 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", ()) @@ -14,28 +21,34 @@ let $maxVersion := request:get-parameter("semver-max", ()) let $version := request:get-parameter("version", ()) let $zip := request:get-parameter("zip", ()) let $info := request:get-parameter("info", ()) -let $procVersion := request:get-parameter("processor", "2.2.0") +let $procVersion := request:get-parameter("processor", $config:default-exist-version) let $app-root-absolute-url := request:get-parameter("app-root-absolute-url", ()) -let $app := + +let $package-group := if ($name) then - doc($config:apps-meta)//package-group[name eq $name] + doc($config:package-groups-doc)//package-group[name eq $name] else - doc($config:apps-meta)//package-group[abbrev eq $abbrev] -let $app-versions := ($app, $app/other/version) -let $compatible-xar := app:find-version($app-versions, $procVersion, $version, $semVer, $minVersion, $maxVersion) + doc($config:package-groups-doc)//package-group[abbrev eq $abbrev] + +let $compatible-package := versions:find-compatible-packages($package-group//package, $procVersion, $version, $semVer, $minVersion, $maxVersion) + return - if ($compatible-xar) then + if ($compatible-package) then + (: TODO shouldn't we get $abs-public from $config? - joewiz :) let $abs-public := $app-root-absolute-url || "/public/" + let $xar-filename := $compatible-package/@path return if ($info) then - let $app := doc($config:apps-meta)//(package-group|version)[@path eq $compatible-xar] - return - {$app/@sha256,($app/version,$app/@version)[1] ! attribute version {.},$compatible-xar} + element found { + $compatible-package/@sha256, + $compatible-package/version ! attribute version {.}, + $compatible-package/@path + } else if ($zip) then - response:redirect-to(xs:anyURI($abs-public || $compatible-xar || ".zip")) + response:redirect-to(xs:anyURI($abs-public || $xar-filename || ".zip")) else - response:redirect-to(xs:anyURI($abs-public || $compatible-xar)) + response:redirect-to(xs:anyURI($abs-public || $xar-filename)) else ( response:set-status-code(404), -

    Package file {$compatible-xar} not found!

    +

    Package file not found!

    ) diff --git a/modules/list.xq b/modules/list.xq index be805eb..a698bf2 100644 --- a/modules/list.xq +++ b/modules/list.xq @@ -1,85 +1,55 @@ xquery version "3.1"; -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. +(:~ + : 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 :) -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 -}; +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 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 namespace request="http://exist-db.org/xquery/request"; -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) -}; +declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; -let $version := request:get-parameter("version", $list:DEFAULT_VERSION) -let $version := if (matches($version, "^\d+\.\d+\.\d+-?.*$")) then $version else $list:DEFAULT_VERSION +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 - - { - for $app in doc($config:apps-meta)//package-group + 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 - element app { list:get-app($app, $version) ! (@*, *) } + 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/scan.xqm b/modules/scan.xqm index d1da879..441f445 100644 --- a/modules/scan.xqm +++ b/modules/scan.xqm @@ -1,191 +1,82 @@ 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 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"; +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 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(package)*) { - 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 := - util:binary-doc($xar) - => util:binary-doc-content-digest("SHA-256") - => string() - 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 namespace util="http://exist-db.org/xquery/util"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; -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 namespace expath="http://expath.org/ns/pkg"; -declare function scanrepo:handle-icon($path as xs:string, $data as item()?, $param as item()*) as element(icon) { +(:~ + : 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, $name, $data) + let $stored := xmldb:store($config:icons-col, $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 - () + 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 }; -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) - } - +(:~ + : 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) } }; -declare function scanrepo:entry-data($path as xs:anyURI, $type as xs:string, $data as item()?, $param as item()*) as item()* +(:~ + : 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) @@ -194,87 +85,148 @@ declare function scanrepo:entry-data($path as xs:anyURI, $type as xs:string, $da return typeswitch ($root) case element(expath:package) return - scanrepo:handle-expath-package($root) + scanrepo:handle-expath-pkg-metadata($root) case element(repo:meta) return - scanrepo:handle-repo-meta($root) + scanrepo:handle-repo-metadata($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 +(:~ + : 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") }; -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") +(:~ + : 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 }; -declare function scanrepo:extract-metadata($resource as xs:string) as element(package) { - let $xar := concat($config:public, "/", $resource) - let $data := util:binary-doc($xar) - let $hash := - $data - => util:binary-doc-content-digest("SHA-256") - => string() +(:~ + : 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 - - { - compression:unzip( - $data, - scanrepo:entry-filter#3, - (), - scanrepo:entry-data#4, - $resource - ) + 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 } - }; -declare function scanrepo:scan-all() { - for $resource in xmldb:get-child-resources($config:public) - where ends-with($resource, ".xar") +(:~ + : 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:extract-metadata($resource) -}; - -declare function scanrepo:scan() { - let $data := doc($config:packages-meta)//package - let $processed := { scanrepo:process($data) } - let $store := xmldb:store($config:metadata-collection, $config:apps-doc, $processed) + ( + 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() { + 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 - $processed -}; - -declare function scanrepo:rebuild-package-meta() as xs:string { - xmldb:store($config:metadata-collection, $config:packages-doc, - { scanrepo:scan-all() }) + xmldb:store($config:metadata-col, $config:package-groups-doc-name, $package-groups) }; -declare function scanrepo:add-package-meta($meta as element(package)) { - let $packages := doc($config:packages-meta)/raw-packages - let $node-to-update := $packages/package[@path = $meta/@path] +(:~ + : 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 - 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() + xmldb:store($config:metadata-col, $config:raw-packages-doc-name, $raw-packages) }; diff --git a/modules/update.xq b/modules/update.xq index 0152aa9..0791d8d 100644 --- a/modules/update.xq +++ b/modules/update.xq @@ -1,22 +1,29 @@ xquery version "3.1"; -(: rebuild metadata for all packages :) +(:~ + : Rebuild metadata for all packages + :) -import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xq"; +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 sm="http://exist-db.org/xquery/securitymanager"; -if (sm:id()/sm:id/sm:real/sm:groups/sm:group = "repo") then - ( - scanrepo:rebuild-package-meta(), - scanrepo:scan() - ) -else - system:as-user( - "repo", - "repo", +let $permissions := config:repo-descriptor()/repo:permissions +let $repo-user := $permissions/@user +let $repo-group := $permissions/@group +return + if (sm:id()/sm:id/sm:real/sm:groups/sm:group = $repo-group) then ( - scanrepo:rebuild-package-meta(), - scanrepo:scan() + scanrepo:rebuild-raw-packages(), + scanrepo:rebuild-package-groups() + ) + else + system:as-user( + $repo-user, + $repo-group, + ( + scanrepo:rebuild-raw-packages(), + scanrepo:rebuild-package-groups() + ) ) - ) diff --git a/modules/upgrade-to-v2-storage.xq b/modules/upgrade-to-v2-storage.xq index fc337fd..1ea3903 100644 --- a/modules/upgrade-to-v2-storage.xq +++ b/modules/upgrade-to-v2-storage.xq @@ -9,6 +9,9 @@ xquery version "3.1"; 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 @@ -59,25 +62,7 @@ declare function local:upgrade-to-public-repo-2-storage() as element()+ { } ) else - (), - - (: move public-repo-1.1.0 metadata into new collections and names :) - if (xmldb:collection-available("/db/apps/public-repo/meta")) then - ( - xmldb:store("/db/apps/public-repo-data/metadata", "packages-raw.xml", element packages-raw { doc("/db/apps/public-repo/meta/packages.xml")/packages/app ! element package { ./@*, ./* } }) ! element packages-raw { . }, - xmldb:store("/db/apps/public-repo-data/metadata", "package-groups.xml", element package-groups { doc("/db/apps/public-repo/meta/apps.xml")/apps/app ! element package-group { ./@*, ./* } } ) ! element package-group { . } , - for $doc in ("/db/apps/public-repo-data/metadata/packages-raw.xml", "/db/apps/public-repo-data/metadata/package-groups.xml") - return - local:chgrp-repo($doc) - ) - else - (), - - (: uninstall :) - (: - :) - repo:undeploy("http://exist-db.org/apps/public-repo") ! element undeploy-status { . }, - repo:remove("http://exist-db.org/apps/public-repo") ! element remove-status { . } + () }; if (repo:list() = "http://exist-db.org/apps/public-repo") then diff --git a/modules/upload.xq b/modules/upload.xq index aa900b6..a042f77 100644 --- a/modules/upload.xq +++ b/modules/upload.xq @@ -1,35 +1,46 @@ xquery version "3.1"; +(:~ + : Receives uploaded packages and immediately publishes them to the package repository + :) + 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 json="http://www.json.org"; +declare namespace request="http://exist-db.org/xquery/request"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; -declare option exist:serialize "method=json media-type=application/json"; +declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; -declare function local:upload-and-publish($xar as xs:string, $data) { - let $path := xmldb:store($config:public, $xar, $data) - let $scan := scanrepo:publish($xar) +declare option output:method "json"; +declare option output:media-type "application/json"; +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 - - - {$path} - {xmldb:get-mime-type($path)} - {xmldb:size($config:public, $xar)} - - + map { + "files": array { + map { + "name": $xar-filename, + "type": xmldb:get-mime-type($path), + "size": xmldb:size($config:packages-col, $xar-filename) + } + } + } }; -let $name := request:get-uploaded-file-name("files[]") -let $data := request:get-uploaded-file-data("files[]") - +let $xar-filename := request:get-uploaded-file-name("files[]") +let $xar-binary := request:get-uploaded-file-data("files[]") return try { - local:upload-and-publish($name, $data) + local:upload-and-publish($xar-filename, $xar-binary) } catch * { - - {$name} - {$err:description} - + map { + "result": + map { + "name": $xar-filename, + "error": $err:description + } + } } diff --git a/modules/versions.xqm b/modules/versions.xqm new file mode 100644 index 0000000..eaecf7c --- /dev/null +++ b/modules/versions.xqm @@ -0,0 +1,143 @@ +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 specific version of eXist (or higher) and other version number criteria + : + : TODO: find packages with version, semVer, or min/max 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?, + $min as xs:string?, + $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 ($min or $max) then + versions:find-version($packages, $min, $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 + () +}; + +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.xq b/modules/view.xq index 1ba98df..59f6455 100644 --- a/modules/view.xq +++ b/modules/view.xq @@ -1,17 +1,19 @@ 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.xqm"; -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 } diff --git a/post-install.xq b/post-install.xq index cdd9f61..ba425c2 100644 --- a/post-install.xq +++ b/post-install.xq @@ -1,5 +1,12 @@ 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"; @@ -7,23 +14,46 @@ 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"; -declare function local:chgrp-repo($resource) { - if (sm:get-permissions($resource)/*/@group = "repo") then +declare namespace repo="http://exist-db.org/xquery/repo"; + +(: Until https://github.com/eXist-db/exist/issues/3734 is fixed, we hard code the default user and group :) + +declare variable $local:owner-user := + (: config:repo-descriptor()/repo:permissions/@user :) + "repo"; +declare variable $local:owner-group := + (: config:repo-descriptor()/repo:permissions/@group :) + "repo"; + +(:~ + : Set user and group to be owner by values in repo.xml + :) +declare function local:chgrp-repo($resource as xs:string) { + if (sm:get-permissions(xs:anyURI($resource))/sm:permission/@group = $local:owner-group) then () else ( - sm:chown($resource, "repo"), - sm:chgrp($resource, "repo") + sm:chown($resource, $local:owner-user), + sm:chgrp($resource, $local:owner-group) ) }; -(: set public-repo-data to "repo" group ownership if needed :) -for $col in ("/db/apps/public-repo-data", xmldb:get-child-collections("/db/apps/public-repo-data") ! ("/db/apps/public-repo-data/" || .)) +(: 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:chgrp-repo($col), -(: build package metadata if missing :) -if (doc-available($config:packages-meta) and doc-available($config:apps-meta)) then +(: Build package metadata if missing :) + +if (doc-available($config:raw-packages-doc) and doc-available($config:package-groups-doc)) then () else - system:as-user("repo", "repo", (scanrepo:rebuild-package-meta(), scanrepo:scan())) + system:as-user( + $local:owner-user, + $local:owner-group, + ( + scanrepo:rebuild-raw-packages(), + scanrepo:rebuild-package-groups() + ) + ) diff --git a/pre-install.xq b/pre-install.xq index bd0f16b..addfc00 100644 --- a/pre-install.xq +++ b/pre-install.xq @@ -1,5 +1,13 @@ 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 :) @@ -11,8 +19,27 @@ declare variable $dir external; (: the target collection into which the app is deployed :) declare variable $target external; -(: create public-repo-data and subcollections :) -xmldb:create-collection("/db/apps", "public-repo-data"), -for $col in ("icons", "metadata", "packages") +(: 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 return - xmldb:create-collection("/db/apps/public-repo-data", $col) \ No newline at end of file + ( + (: 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) + return + xmldb:create-collection($app-data-col, $col-name) + ) \ No newline at end of file diff --git a/repo.xml b/repo.xml index 3c2fd3a..65fb420 100644 --- a/repo.xml +++ b/repo.xml @@ -10,12 +10,12 @@ public-repo pre-install.xq post-install.xq - +
      -
    • IMPORTANT: You must first run the upgrade script in TODO_INSERT_URL before installing v2!
    • -
    • Breaking: Move packages storage outside of public-repo app to fix upgrade procedure, which previously deleted all packages, and simplify future backups and upgrades.
    • +
    • 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.
    From ffd46198cd134620f56c167ae5871a77ec4bb807 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 19:56:33 -0500 Subject: [PATCH 08/24] Fix port problem with redirects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scheme, domain, and port don’t change and thus aren’t needed in the redirect in find.xql - which is the one file where this variable is used. Closes https://github.com/eXist-db/public-repo/issues/49. --- controller.xql | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/controller.xql b/controller.xql index 6b9c235..166fb57 100644 --- a/controller.xql +++ b/controller.xql @@ -10,10 +10,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 ; From b65a476d16148e5bc7bc6b13bf7d6b70124a5385 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:00:43 -0500 Subject: [PATCH 09/24] Remove hardcoded relative path to icons & packages Instead of trying to construct a relative path in the controller, forward the request to get-icon.xq and get-package.xq, which can retrieve the resources via paths defined in the config module. Also, use get-package.xq to handle requests for xar files as zip files. --- controller.xql | 17 +++++++---------- modules/download-xar-zip.xq | 20 -------------------- modules/get-icon.xq | 30 ++++++++++++++++++++++++++++++ modules/get-package.xq | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 30 deletions(-) delete mode 100644 modules/download-xar-zip.xq create mode 100644 modules/get-icon.xq create mode 100644 modules/get-package.xq diff --git a/controller.xql b/controller.xql index 166fb57..2ad9c66 100644 --- a/controller.xql +++ b/controller.xql @@ -89,21 +89,18 @@ else if (ends-with($exist:resource, ".html")) then -(: TODO figure out how to turn the absolute path $config:app-data-parent-col - : into the relative path needed for the forward directive - joewiz :) -else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".xar")) 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 (contains($exist:path, "/public/") and ends-with($exist:resource, ".zip")) then - - + + + else if ($exist:path eq "/find" or ends-with($exist:resource, ".zip")) then diff --git a/modules/download-xar-zip.xq b/modules/download-xar-zip.xq deleted file mode 100644 index 155b5d1..0000000 --- a/modules/download-xar-zip.xq +++ /dev/null @@ -1,20 +0,0 @@ -xquery version "3.1"; - -(:~ - : Allows download of packages via zip extension - : - : Responds to requests like /exist/apps/public-repo/public/eXide-1.0.0.xar.zip - :) - -import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; - -declare namespace compression="http://exist-db.org/xquery/compression"; -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"; - -(: strip .zip from resource name :) -let $xar-filename := fn:replace(request:get-url(), ".*/(.*)\.zip", "$1") -let $xar := util:binary-doc($config:packages-col || "/" || $xar-filename) -return - response:stream-binary($xar, "application/zip") diff --git a/modules/get-icon.xq b/modules/get-icon.xq new file mode 100644 index 0000000..2122485 --- /dev/null +++ b/modules/get-icon.xq @@ -0,0 +1,30 @@ +xquery version "3.1"; + +(:~ + : Allows download of icons + : + : Responds to requests like: + : - /exist/apps/public-repo/public/eXide-1.0.0.png + : - /exist/apps/public-repo/public/eXide-1.0.0.svg + :) + +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"; + +let $filename := request:get-parameter("filename", ()) +let $path := xs:anyURI($config:icons-col || "/" || $filename) +return + (: svg :) + if (doc-available($path)) then + response:stream(doc($path), "media-type=" || xmldb:get-mime-type($path)) + (: png :) + else if (util:binary-doc-available($path)) then + response:stream-binary(util:binary-doc($path), xmldb:get-mime-type($path)) + else + ( + response:set-status-code(404), +

    Icon file not found!

    + ) diff --git a/modules/get-package.xq b/modules/get-package.xq new file mode 100644 index 0000000..a3d6a05 --- /dev/null +++ b/modules/get-package.xq @@ -0,0 +1,34 @@ +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 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"; + +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) + return + response:stream-binary($xar, "application/zip") + else + ( + response:set-status-code(404), +

    Package file not found!

    + ) From c18ed3d3a27bf02b0283242f24bd87aefd0eda35 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:28:39 -0500 Subject: [PATCH 10/24] Simplify post-install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removes the call to the system:as-user() function, since previously I had conflated the repo group set in repo.xml’s permissions file with the repo user’s permissions. Instead, focus on setting just the group and g+w on the data collection’s resources, so any member of the repo group can write to them. - Also, adds a utility function, scanrepo:rebuild-all-package-metadata(). --- modules/config.xqm | 11 +++++++++++ modules/scan.xqm | 10 +++++++++- post-install.xq | 37 ++++++++++++++----------------------- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/modules/config.xqm b/modules/config.xqm index 79a949a..e3d9af3 100644 --- a/modules/config.xqm +++ b/modules/config.xqm @@ -64,6 +64,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/scan.xqm b/modules/scan.xqm index 441f445..91c0dd1 100644 --- a/modules/scan.xqm +++ b/modules/scan.xqm @@ -199,7 +199,7 @@ declare function scanrepo:publish-package($xar-filename as xs:string) { (:~ : Rebuild the package-groups metadata by merging raw-packages metadata into package-groups :) -declare function scanrepo:rebuild-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 @@ -230,3 +230,11 @@ declare function scanrepo:rebuild-raw-packages() as xs:string { 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/post-install.xq b/post-install.xq index ba425c2..8f6492b 100644 --- a/post-install.xq +++ b/post-install.xq @@ -14,27 +14,22 @@ 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"; -declare namespace repo="http://exist-db.org/xquery/repo"; - -(: Until https://github.com/eXist-db/exist/issues/3734 is fixed, we hard code the default user and group :) - -declare variable $local:owner-user := - (: config:repo-descriptor()/repo:permissions/@user :) - "repo"; -declare variable $local:owner-group := - (: config:repo-descriptor()/repo:permissions/@group :) - "repo"; +(: 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" +; (:~ : Set user and group to be owner by values in repo.xml :) -declare function local:chgrp-repo($resource as xs:string) { - if (sm:get-permissions(xs:anyURI($resource))/sm:permission/@group = $local:owner-group) then +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, $local:owner-user), - sm:chgrp($resource, $local:owner-group) + sm:chgrp($resource, $repo-group), + sm:chmod(xs:anyURI($resource), "rwxrwxr-x") ) }; @@ -42,18 +37,14 @@ declare function local:chgrp-repo($resource as xs:string) { for $col in ($config:app-data-col, xmldb:get-child-collections($config:app-data-col) ! ($config:app-data-col || "/" || .)) return - local:chgrp-repo($col), + 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 - system:as-user( - $local:owner-user, - $local:owner-group, - ( - scanrepo:rebuild-raw-packages(), - scanrepo:rebuild-package-groups() - ) - ) + ( + scanrepo:rebuild-all-package-metadata(), + ($config:raw-packages-doc, $config:package-groups-doc) ! local:set-data-collection-permissions(.) + ) \ No newline at end of file From b35599b824b6915df61886e2e7c3a54ca50dee74 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:29:57 -0500 Subject: [PATCH 11/24] Simplify update.xq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … since permissions set in post-install will carry forward, and since we have the new repo:rebuild-all-package-metadata() function. --- modules/update.xq | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/modules/update.xq b/modules/update.xq index 0791d8d..6bf7f87 100644 --- a/modules/update.xq +++ b/modules/update.xq @@ -4,26 +4,6 @@ xquery version "3.1"; : Rebuild metadata for all packages :) -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 sm="http://exist-db.org/xquery/securitymanager"; - -let $permissions := config:repo-descriptor()/repo:permissions -let $repo-user := $permissions/@user -let $repo-group := $permissions/@group -return - if (sm:id()/sm:id/sm:real/sm:groups/sm:group = $repo-group) then - ( - scanrepo:rebuild-raw-packages(), - scanrepo:rebuild-package-groups() - ) - else - system:as-user( - $repo-user, - $repo-group, - ( - scanrepo:rebuild-raw-packages(), - scanrepo:rebuild-package-groups() - ) - ) +scanrepo:rebuild-all-package-metadata() \ No newline at end of file From 50c64cd7b8f6a0546c804d87f3ddc576b506a16c Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:40:23 -0500 Subject: [PATCH 12/24] Add versions:find-newest-compatible-package() - To simplify the call in find.xq - Also, improve variable names in versions:find-compatible-packages() --- modules/find.xq | 29 +++++++++++++++-------------- modules/versions.xqm | 44 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/modules/find.xq b/modules/find.xq index 5ddb9af..5cce6d9 100644 --- a/modules/find.xq +++ b/modules/find.xq @@ -15,13 +15,13 @@ declare namespace response="http://exist-db.org/xquery/response"; let $abbrev := request:get-parameter("abbrev", ()) let $name := request:get-parameter("name", ()) -let $semVer := request:get-parameter("semver", ()) -let $minVersion := request:get-parameter("semver-min", ()) -let $maxVersion := request:get-parameter("semver-max", ()) +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 $procVersion := request:get-parameter("processor", $config:default-exist-version) let $app-root-absolute-url := request:get-parameter("app-root-absolute-url", ()) let $package-group := @@ -30,25 +30,26 @@ let $package-group := else doc($config:package-groups-doc)//package-group[abbrev eq $abbrev] -let $compatible-package := versions:find-compatible-packages($package-group//package, $procVersion, $version, $semVer, $minVersion, $maxVersion) +let $newest-compatible-package := versions:find-newest-compatible-package($package-group//package, $exist-version-semver, $version, $semver, $semver-min, $semver-max) return - if ($compatible-package) then + 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 := $compatible-package/@path + let $xar-filename := $newest-compatible-package/@path return if ($info) then element found { - $compatible-package/@sha256, - $compatible-package/version ! attribute version {.}, - $compatible-package/@path + $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), -

    Package file not found!

    - ) + else + ( + response:set-status-code(404), +

    Package file not found!

    + ) diff --git a/modules/versions.xqm b/modules/versions.xqm index eaecf7c..e5bdb36 100644 --- a/modules/versions.xqm +++ b/modules/versions.xqm @@ -8,7 +8,6 @@ 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) :) @@ -20,26 +19,26 @@ declare function versions:find-compatible-packages( }; (:~ - : Find all packages compatible with a specific version of eXist (or higher) and other version number criteria + : Find all packages compatible with a version of eXist meeting various version criteria : - : TODO: find packages with version, semVer, or min/max attributes to test those conditions - joewiz + : 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?, - $min as xs:string?, - $max 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) + if ($semver) then + versions:find-version($packages, $semver, $semver) else if ($version) then $packages[version = $version] - else if ($min or $max) then - versions:find-version($packages, $min, $max) + else if ($semver-min and $semver-max) then + versions:find-version($packages, $semver-min, $semver-max) else if ( $exist-version-semver and @@ -51,6 +50,31 @@ declare function versions:find-compatible-packages( () }; +(:~ + : 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?) { From 3bcd270054a2e04f64772cab5e76f5e105049f63 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:42:18 -0500 Subject: [PATCH 13/24] Remove unused resources --- controller.xql | 27 +++--------------- modules/app.xqm | 4 +-- resources/images/ajax-loader.gif | Bin 10819 -> 0 bytes resources/images/body-base.gif | Bin 203 -> 0 bytes resources/images/body.gif | Bin 82 -> 0 bytes resources/images/close.png | Bin 969 -> 0 bytes resources/images/download.png | Bin 4758 -> 0 bytes resources/images/grey-box-bot.gif | Bin 574 -> 0 bytes resources/images/grey-box-rpt.gif | Bin 166 -> 0 bytes resources/images/grey-box-top.gif | Bin 572 -> 0 bytes resources/images/header.gif | Bin 12234 -> 0 bytes resources/images/horizontal.gif | Bin 338 -> 0 bytes resources/images/install.png | Bin 4198 -> 0 bytes .../images/{library2.gif => library.gif} | Bin resources/images/nav-dropdown.gif | Bin 568 -> 0 bytes resources/images/nav-dropdown.png | Bin 1384 -> 0 bytes resources/images/nav.gif | Bin 15319 -> 0 bytes resources/images/{plugin2.gif => plugin.gif} | Bin retrieve.html | 4 --- 19 files changed, 6 insertions(+), 29 deletions(-) delete mode 100644 resources/images/ajax-loader.gif delete mode 100644 resources/images/body-base.gif delete mode 100644 resources/images/body.gif delete mode 100644 resources/images/close.png delete mode 100644 resources/images/download.png delete mode 100644 resources/images/grey-box-bot.gif delete mode 100644 resources/images/grey-box-rpt.gif delete mode 100644 resources/images/grey-box-top.gif delete mode 100644 resources/images/header.gif delete mode 100644 resources/images/horizontal.gif delete mode 100644 resources/images/install.png rename resources/images/{library2.gif => library.gif} (100%) delete mode 100644 resources/images/nav-dropdown.gif delete mode 100644 resources/images/nav-dropdown.png delete mode 100644 resources/images/nav.gif rename resources/images/{plugin2.gif => plugin.gif} (100%) delete mode 100644 retrieve.html diff --git a/controller.xql b/controller.xql index 2ad9c66..905a7e7 100644 --- a/controller.xql +++ b/controller.xql @@ -28,23 +28,11 @@ 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. :) @@ -103,7 +91,7 @@ else if (contains($exist:path, "/public/") and (ends-with($exist:resource, ".png -else if ($exist:path eq "/find" or ends-with($exist:resource, ".zip")) then +else if ($exist:path eq "/find") then @@ -124,16 +112,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/modules/app.xqm b/modules/app.xqm index d044428..7992c37 100644 --- a/modules/app.xqm +++ b/modules/app.xqm @@ -146,9 +146,9 @@ declare function app:package-group-to-list-item($package-group as element(packag case ("application") return application case ("library") return - library + library case ("plugin") return - plugin + plugin default return () }

    {$package-group/title/string()}

    diff --git a/resources/images/ajax-loader.gif b/resources/images/ajax-loader.gif deleted file mode 100644 index 47adbf03dafbdabb57e1c1dbf8633d0347f66956..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10819 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 g|2k@-t6V^D=-lN;QH?)nSWpZ53kcXZ~y=R diff --git a/resources/images/body-base.gif b/resources/images/body-base.gif deleted file mode 100644 index 4f297e072d4a6db069d46aa7557ee09c597503b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 203 zcmZ?wbhEHb diff --git a/resources/images/body.gif b/resources/images/body.gif deleted file mode 100644 index 91c9a3dcf8caa061af217cbb224c7968cf4fb512..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82 zcmZ?wbhEHb_sbydm_b^?$ bW}U9&>Kfr^xrNJTi+eRPa6FAvW3UDQe{~V5 diff --git a/resources/images/close.png b/resources/images/close.png deleted file mode 100644 index 8319264af6f464184425eea0a586e693ada917e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 969 zcmV;)12+7LP)Be`2%QEU8rLX-iAX6RvZ`)>Ox~<<4`;v|B-L4!{O-J+1Z)u=;-h*FE7smek&G>>4MM&fOtnTnS9mV z-Q7?2%?AeureH7_ZEkK3tgo-1fyjuT_W69S+S*!mdwbiwxVZR*owx}^tm*)5ZEaTU zGAYC(*kmTe_duu%1UoUpPRwP*`w{;J@k(7?ok=CcdaR@Z@fC<0FeIu5zyqyhaNOhZ z1cZ1(%>oE@|265nlZZccfR4hlhv4PQQdsGJg@<6kLl9O-oBlY0q5zTe;9 zPv4)mP3{_TZg5O9Sr8$?K zvbNzI4X&-NY0iA50eUHi9|5>ftlEgk07NL80{{f218@%du+Svpy~V0cZgm3UXH!$t zaG|lIDgboYUJmhlnv^GBlPY#EnjH}HG{S3)9h!GSGXRvB6h%o-p!onqFaxQP zNaO{h#J#EjSglqYRc8Y4UuFCM`90I57Vvj^m}6~vn9JAf#4r~QhiBN;G2SbZRRIve zIBnb5*m%b{eMLazj?*-$5i*+|r`ak0-rnAsaaue(GfvNvkWt3zvc>BH$Ur!Ocr75v z_K9s64+6R6kwc$;p~>0_@#2N%GvaR}KCV@R?mhs7opE@gv$L~*Wo1QVeu&KHJ&Xc^ z$~D#A-tLP=qtl2VqlBjPJij8H7$J6c;u%(Y%t%r6r5>;Qc^P`h^#N5!m-vbS$P5EO rb{GJ%!vK&S27v4^0Az;&AUpm7K4eW#pca)k00000NkvXXu0mjfgU-e2 diff --git a/resources/images/download.png b/resources/images/download.png deleted file mode 100644 index 4477b452650f11f758d57aaf49df6adb793af18a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4758 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 diff --git a/resources/images/grey-box-bot.gif b/resources/images/grey-box-bot.gif deleted file mode 100644 index 995a544ff5c75b5a91f67e9f26d22b89ed987356..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 574 zcmV-E0>S-9Nk%w1VekMR0K@`1trfK0mp+x;i>LS65iKx3^+q zVw;z>%A^8LV00000EC2ui0Pp}F000I5U?+~8wq=;7u59bRa4gSsZQppV z?>vlU8x3bfES65Go^r`-I-k(qFE}h(kEp12%k6rjQ=#>0%6`x2w3-{Pj!EU@dQPv~ zUF|m!E5G~yfM7skdLn#*h>40gd4(N^i;0PIA0!cK*7KU z0#rJV`v~A?fB+eiDqYI7sne%Wqe`7hwW`&tShH%KdK4%CAwm}tEt;^b*|TWVs$I*r zt=qS7(75Ck{x3A^Pm@{kM z%(=7Y&!9t#9!cgW6PdRySDAyxO3~?&AYen-@tNk%w1VekM70HOc@_4V~TJ3RFC^!obx_xJbn^YcJJK>z>%A^8LV00000 zEC2ui0Pp|`000APSh@lM)jwBWg1RyMI+dNSz&vb3yczLF4>%Wkk za7Zi~b32vq=LjC3(5RG{>@6A1*mTS7dM(u}3#k2?&uAn{DOQlv@VFclZWrG3yk1{# Uk}BNk%w1VekMR0K@`1trfK0keZeZ0K9qN1Z41ejE#F*>*JySq{J{!LtMaM8Dm KEAM(d1OPkjFA^O9 diff --git a/resources/images/header.gif b/resources/images/header.gif deleted file mode 100644 index 3f21ecaf563f3560a2ab5798b1214b826fa98bcb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12234 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 P#YX=K4-5ID$Y2cs*z$lA diff --git a/resources/images/install.png b/resources/images/install.png deleted file mode 100644 index 02ef39752b7f4e3bbf578a9ab3f71922ba192e51..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4198 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 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 0244f134174dae8a492e98234aca8341d2ab87e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 568 zcmV-80>}MFNk%w1VT=Iu0mKym{Qmxg!{+(?{;SsRCzjK4y5gnN?Woo528PTwoz_~f z-7}okA(PTntlT7(($(ws42jKGt=-t{_YaEB0E5f_|Nj6000000000000000000000 z000000000000000A^8LW000#LEC2ui0E__i0RRR6ASI4uX`X1Ru59bRa4gSsZQppV z?|kq7z@TtQEE41ejE#0PICJXU$+M@= zpFo2O9ZIyQ(W6L{DqYI7sne%Wqe`7hwW`&tShH%~%C)Q4uVBN99ZR;X*|TWVs$I*r zt=qS7(Bfp)X3IYLD1Qe)J zU_g}t0S-_x5K!R&ssj~L7cf;Ib!q?s5Qst$0Dx=P5)yz)@Zh#@00SZ{(6pez0Nb}i G0029dF*Q8^ diff --git a/resources/images/nav-dropdown.png b/resources/images/nav-dropdown.png deleted file mode 100644 index fd1e44337e2ce9f3dd6b79d01c92f0aa8a26591d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1384 zcmeAS@N?(olHy`uVBq!ia0vp^3m6y}zi_YtS-Y02RRAf;64!{5;QX|b^2DN42FH~A zq*MjZ+{E|lPGiGWBQphw<^+4>S<4py>(Yc zcj1$FOIoDG-+3+iw&ae*ggJ9|cn4+uXLj9YaVn#KlE&8jjG1xgH}~G!HZ$`{>g5`{ zui5wSRW83<_xbJ2Z9yWgFI3Kdj-P(j!d_~7Jo}65myYgz>8|UieR|Wn^SK+eu1#hT zRA6FZRB{j?m15ywa%oU-Af4)9aBScZU?G)ajAEp(g+LbyDsTueHjzq^5tz`>Cp|Es zkw9u-LPMW)zd-|oJTJO5gf);Fm=swEO4#IuDH#bA9HxKx8Mi%pqAb1DtKRH-c+}5J zdCR8=fB5@j*{rjdrpMm&(tdyH4_Dj_S@~^|zb@saE6G z+`A$D^@O*VE+6r*T@n^#oXYm2bVbz8ny)8j@Ls;Wb@6TIoK!v2;IxL5XM>HE%k|&f zPdi;Q<(5b9k<-$D_bmtJ_~YWAY~Ght?+R@(-yT=Pb$xz4&*`;?u6e4*&9Jr+)036q z{aB=SFKb<$?deTHeg~FF)*rkv)p>FGyt#K}`QCh)|8apguZ^v-WRUR6-mM>})-tUt z)01oXK5tJfSO0A9#E+kgDt=d#Wzp$PzT;DAK{ diff --git a/resources/images/nav.gif b/resources/images/nav.gif deleted file mode 100644 index 2f756cf474f4de1d2580c9ed88bc22de3f9186b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15319 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 -
      -
    • -
    From 241c7a5950e34fc4f7579f1e22fd7def5c1e9ad8 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:42:44 -0500 Subject: [PATCH 14/24] Avoid hardcoding group name --- controller.xql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controller.xql b/controller.xql index 905a7e7..83e95da 100644 --- a/controller.xql +++ b/controller.xql @@ -39,7 +39,7 @@ else if ($exist:path eq "/public/apps.xml") then else if ($exist:path eq "/admin.html") then let $user := request:get-attribute("org.exist.public-repo.login.user") return - if (exists($user) and sm:get-user-groups($user) = "repo") then + if (exists($user) and sm:get-user-groups($user) = config:repo-permissions()?group) then From 0d21b8578bd940e7a83387481d8e640d1ed05541 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:44:35 -0500 Subject: [PATCH 15/24] Switch to HTML5-style data-template attributes --- admin.html | 2 +- index.html | 4 ++-- login.html | 2 +- packages.html | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/admin.html b/admin.html index a4e4750..bc913c8 100644 --- a/admin.html +++ b/admin.html @@ -1,5 +1,5 @@ -
    +

    Package Upload

    diff --git a/index.html b/index.html index b62d051..8aed177 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ -
    +

    Public Application Repository

    @@ -24,7 +24,7 @@

    Public Application Repository

    Packages

      -
    • +
    diff --git a/login.html b/login.html index a222833..73d1ce5 100644 --- a/login.html +++ b/login.html @@ -1,5 +1,5 @@ -
    +

    Administrator Login

    Please login. The user has to be a member of the "repo" group. diff --git a/packages.html b/packages.html index 8556986..2163cb3 100644 --- a/packages.html +++ b/packages.html @@ -1,5 +1,5 @@ -
    +

    Public Application Repository

    @@ -8,7 +8,7 @@

    Public Application Repository

      -
    • +
    From 064ff9cecf7cb01f2542c2badb9e18cf906cedc5 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:46:41 -0500 Subject: [PATCH 16/24] Add button to rebuild package-groups metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit … in admin page. Reason: in my testing, occasionally there was a problem where multiple versions of a packages were uploaded so quickly that the package-groups metadata file ended up with multiple copies. This button just cleans up the package-groups metadata - and does not trigger a costly new scan of xars. --- admin.html | 8 +++++++- modules/app.xqm | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/admin.html b/admin.html index bc913c8..add348e 100644 --- a/admin.html +++ b/admin.html @@ -4,7 +4,8 @@

    Package Upload

    Upload xar packages to the public repository. New packages will appear in the list of - available packages immediately upon upload.

    + available packages immediately upon upload.

    +

    diff --git a/modules/app.xqm b/modules/app.xqm index 7992c37..020ccf4 100644 --- a/modules/app.xqm +++ b/modules/app.xqm @@ -7,15 +7,22 @@ xquery version "3.1"; 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 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 function app:abs-path-to-apps-xml($node as node(), $model as map(*), $mode as xs:string?) { - let $url := request:get-parameter("app-root-absolute-url", ()) || "/public/apps.xml" - return - {$url} +(:~ + : 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 + () }; (:~ @@ -237,7 +244,7 @@ declare function app:package-group-to-list-item($package-group as element(packag let $requires := $package/requires return
  10. - { $package/version } + { $package/version/string() } { " (Note: Requires eXist-db " || app:requires-to-english($requires) @@ -280,7 +287,7 @@ declare function app:package-group-to-list-item($package-group as element(packag

    {$newest-package/description/string()}
    - Version {$newest-package/version} { + Version {$newest-package/version/string()} { if ($requires) then concat(" (Requires eXist-db ", app:requires-to-english($requires), ".)") else From 666db6e3cbdfaddc868d45a2176c21cd3b8fab66 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:46:52 -0500 Subject: [PATCH 17/24] Use HTTPS for exist-db.org URLs --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 8aed177..8e612c9 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@

    Public Application Repository

    Package Manager app.
  11. 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.")
  12. From 295d46792101785ff205b95df61acaf7fc6e4ebe Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:49:55 -0500 Subject: [PATCH 18/24] Allow config:app-root to return correct values When config is imported in eXide to test config:repo-descriptor(), $config:app-root returned xmldb:exist://null/db/apps/public-repo/modules, causing config:repo-descriptor to fail with an error. This accounts for that case, ensuring that $config:app returns the correct app-root of /db/apps/public-repo. --- modules/config.xqm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/config.xqm b/modules/config.xqm index e3d9af3..09867b5 100644 --- a/modules/config.xqm +++ b/modules/config.xqm @@ -21,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 From a31ea245655e51cb988efe51f980820afde93383 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Fri, 19 Feb 2021 20:50:09 -0500 Subject: [PATCH 19/24] Form control is not needed in the login form --- login.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/login.html b/login.html index 73d1ce5..5cff621 100644 --- a/login.html +++ b/login.html @@ -8,13 +8,13 @@

    Administrator Login

    - +
    - +
    From 4ad679ec1407c41b82236357ad2948674627c5b1 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Sat, 20 Feb 2021 13:50:34 -0500 Subject: [PATCH 20/24] Define the package upload endpoint in controller MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This is the final endpoint that was called directly via modules/upload.xq; now it’s mediated by controller.xql - And add an authentication check; I placed this in the query itself, but I imagine this will go into a rule that roaster will enforce - TODO: admin.html doesn’t display error messages, so uploads may fail silently, with no warning to the user. --- admin.html | 2 +- controller.xql | 8 +++++++ modules/{upload.xq => put-package.xq} | 31 ++++++++++++++++++--------- 3 files changed, 30 insertions(+), 11 deletions(-) rename modules/{upload.xq => put-package.xq} (62%) diff --git a/admin.html b/admin.html index add348e..1d2b082 100644 --- a/admin.html +++ b/admin.html @@ -50,7 +50,7 @@

    Package Upload

    $(function () { 'use strict'; $('#fileupload').fileupload({ - url: "modules/upload.xq", + url: "put-package", dataType: 'json', done: function (e, data) { $.each(data.result.files, function (index, file) { diff --git a/controller.xql b/controller.xql index 83e95da..c471488 100644 --- a/controller.xql +++ b/controller.xql @@ -3,6 +3,8 @@ 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; @@ -57,6 +59,12 @@ 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 diff --git a/modules/upload.xq b/modules/put-package.xq similarity index 62% rename from modules/upload.xq rename to modules/put-package.xq index a042f77..9714477 100644 --- a/modules/upload.xq +++ b/modules/put-package.xq @@ -8,6 +8,7 @@ import module namespace config="http://exist-db.org/xquery/apps/config" at "conf 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"; @@ -32,15 +33,25 @@ declare function local:upload-and-publish($xar-filename as xs:string, $xar-binar 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 - try { - local:upload-and-publish($xar-filename, $xar-binary) - } catch * { - map { - "result": - map { - "name": $xar-filename, - "error": $err:description - } + if (exists($user) and sm:get-user-groups($user) = $required-group) then + try { + local:upload-and-publish($xar-filename, $xar-binary) + } 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." + } + ) From 9741de91e1232240677ee2ff84dfd9daae4b354c Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Mon, 22 Feb 2021 03:02:27 -0500 Subject: [PATCH 21/24] [feature] package event log & statistics facility Inspired by https://github.com/eXist-db/public-repo/issues/18: - log each time a package is downloaded - log when a package is published - only info logged is the package URI, version number, and date stamp - no personal info is collected about requester - to put these statistics to basic use, the admin page now contains 2 basic reports: (1) top 5 downloads, (2) date each package was uploaded, along with eXist version requirements --- admin.html | 80 ++++++++++++++++++------ modules/app.xqm | 134 +++++++++++++++++++++++++++++++++++++++++ modules/config.xqm | 8 ++- modules/get-package.xq | 26 ++++++++ modules/put-package.xq | 27 ++++++++- post-install.xq | 19 +++++- pre-install.xq | 45 ++++++++++++-- 7 files changed, 312 insertions(+), 27 deletions(-) diff --git a/admin.html b/admin.html index 1d2b082..e0de4d0 100644 --- a/admin.html +++ b/admin.html @@ -2,14 +2,70 @@
    -

    Package Upload

    -

    Upload xar packages to the public repository. New packages will appear in the list of - available packages immediately upon upload.

    -

    +

    Admin

    +

    View administrative information about packages and upload new ones.

    -
    +
    +
    +

    Statistics

    +

    Top 5 packages

    +
      +
    1. + +
    2. +
    +
    +
    +

    All package groups

    +
    +

    +
    +
    Abbrev
    +
    +
    Name
    +
    +
    Title
    +
    +
    + + + + + + + + + + + + + + + +
    VersioneXist RequirementDate Published
    +
    +
    +
    +
    +

    + + Return to list + +

    +

    + + Rebuild package-groups metadata + +

    + + Log out +

    Upload Packages

    +

    Upload xar packages to the public repository. New packages will appear in the list of + available packages immediately upon upload.

    +

    + Select files... @@ -28,20 +84,6 @@

    Package Upload

    -