diff --git a/admin.html b/admin.html index fee94df..e8e8f1a 100644 --- a/admin.html +++ b/admin.html @@ -1,15 +1,71 @@ -
+
-

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.

+

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,16 +84,6 @@

Package Upload

-
-

- - Return to list - -

-

Publishing will take a while to process apps.

- - Log out -
-
\ No newline at end of file +
diff --git a/build.properties b/build.properties index c4db381..a4da2f2 100644 --- a/build.properties +++ b/build.properties @@ -4,4 +4,4 @@ # project.name=public-repo -project.version=1.1.0 +project.version=2.0.0 diff --git a/collection.xconf b/collection.xconf deleted file mode 100644 index ee9f748..0000000 --- a/collection.xconf +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/controller.xql b/controller.xql index f66166f..c471488 100644 --- a/controller.xql +++ b/controller.xql @@ -1,7 +1,10 @@ xquery version "3.1"; +import module namespace config="http://exist-db.org/xquery/apps/config" at "modules/config.xqm"; import module namespace login="http://exist-db.org/xquery/login" at "resource:org/exist/xquery/modules/persistentlogin/login.xql"; +declare namespace sm="http://exist-db.org/xquery/securitymanager"; + declare variable $exist:path external; declare variable $exist:resource external; declare variable $exist:controller external; @@ -9,10 +12,7 @@ declare variable $exist:prefix external; declare variable $exist:root external; declare variable $app-root-absolute-url := - (request:get-header("X-Forwarded-Proto"), request:get-scheme())[1] - || "://" - || (request:get-header("X-Forwarded-Server"), request:get-server-name())[1] - || request:get-context-path() + request:get-context-path() || $exist:prefix || $exist:controller ; @@ -30,33 +30,21 @@ else if ($exist:path eq "/") then -else if ($exist:path eq "/public/apps.xml") then ( - response:set-header('Content-Type', 'application/xml'), +else if ($exist:path eq "/public/apps.xml") then - + -) -else if ($exist:resource eq "update.xql") then - - - - - - - - - - + (: Protected resource: user is required to log in with valid credentials. If the login fails or no credentials were provided, the request is redirected to the login.html page. :) else if ($exist:path eq "/admin.html") then let $user := request:get-attribute("org.exist.public-repo.login.user") return - if ($user and (sm:is-dba($user) or "repo" = sm:get-user-groups($user))) then + if (exists($user) and sm:get-user-groups($user) = config:repo-permissions()?group) then - + @@ -65,47 +53,62 @@ else if ($exist:path eq "/admin.html") then - + +(: Protected resource :) +else if ($exist:path eq "/put-package") then + + + + else if (ends-with($exist:resource, ".html") and starts-with($exist:path, "/packages")) then - + else if (ends-with($exist:resource, ".html")) then - (: the html page is run through view.xql to expand templates :) + (: the html page is run through view.xq to expand templates :) - + -else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".zip")) then +else if (contains($exist:path, "/public/") and ends-with($exist:resource, ".xar") or ends-with($exist:resource, ".zip")) then + + + + + + +else if (contains($exist:path, "/public/") and (ends-with($exist:resource, ".png") or ends-with($exist:resource, ".svg"))) then - + + + -else if ($exist:path eq "/find" or ends-with($exist:resource, ".zip")) then +else if ($exist:path eq "/find") then - + else if ($exist:resource eq "feed.xml") then - + else if (contains($exist:path, "/$shared/")) then @@ -117,16 +120,9 @@ else if (contains($exist:path, "/$shared/")) then else if (contains($exist:path, "/resources/")) then - - - + -else if (ends-with($exist:path, ".xml")) then - - - - else (: everything else is passed through :) diff --git a/expath-pkg.xml.tmpl b/expath-pkg.xml.tmpl index 067baea..b03a6bb 100644 --- a/expath-pkg.xml.tmpl +++ b/expath-pkg.xml.tmpl @@ -1,7 +1,7 @@ eXist-db Public Application Repository - - + + diff --git a/index.html b/index.html index 27c906d..8e612c9 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ -
+

Public Application Repository

@@ -10,7 +10,7 @@

Public Application Repository

Package Manager app.
  • Install the package directly from the Dashboard's Package Manager app. (By default the Package Manager is configured to access the eXist-db public application repository feed, - at http://demo.exist-db.org/exist/apps/public-repo/public/apps.xml. To check or + at https://exist-db.org/exist/apps/public-repo/public/apps.xml. To check or edit the repository URL your Package Manager is using, go to your Dashboard, click on the gear icon beneath the eXist-db icon, and you will see a field, "Public Repository URL.")
  • @@ -24,7 +24,7 @@

    Public Application Repository

    Packages

      -
    • +
    @@ -36,4 +36,4 @@

    Admin

    (requires login)

    -
    \ No newline at end of file +
    diff --git a/login.html b/login.html index d21fee3..5cff621 100644 --- a/login.html +++ b/login.html @@ -1,20 +1,20 @@ -
    +

    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.
    - +
    - +
    @@ -23,4 +23,4 @@

    Administrator Login

    -
    \ No newline at end of file +
    diff --git a/meta/apps.xml b/meta/apps.xml deleted file mode 100644 index 2a67653..0000000 --- a/meta/apps.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/meta/packages.xml b/meta/packages.xml deleted file mode 100644 index 82d2b8d..0000000 --- a/meta/packages.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/modules/app.xql b/modules/app.xql deleted file mode 100644 index a912e00..0000000 --- a/modules/app.xql +++ /dev/null @@ -1,254 +0,0 @@ -xquery version "3.0"; - -module namespace app="http://exist-db.org/xquery/app"; - -import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; -import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xql"; - -declare function app:list-packages($node as node(), $model as map(*), $mode as xs:string?) { - for $app in doc($config:apps-meta)//app - let $show-details := false() - order by lower-case($app/title) - return - app:package-to-list-item($app, $app/version, $show-details) -}; - -declare function app:view-package($node as node(), $model as map(*), $mode as xs:string?) { - let $abbrev := request:get-parameter("abbrev", ()) - let $procVersion := request:get-parameter("eXist-db-min-version", "2.2.0") - let $matching-abbrev := doc($config:apps-meta)//abbrev[. eq $abbrev] - let $apps := $matching-abbrev/parent::app - return - ( - if (count($apps) gt 1) then -

    More than one package matches the requested convenient short - name, {$abbrev}. The unique package names that - match this short name are: -

      - { - for $app in $apps - let $name := $app/name - order by $name - return -
    1. {$name/string()}
    2. - } -
    - The packages with versions available that are compatible with eXist {$procVersion} or higher are as follows: -

    - else - () - , - let $listing := - for $app in $apps - return - (: catch requests for a package using its legacy "abbrev" and redirect - : them to a URL using the app's current abbrev, to encourage use of the - : current abbrev :) - if ($matching-abbrev/@type eq "legacy") then - let $current-abbrev := $app/abbrev[not(@type eq "legacy")] - let $repoURL := concat(substring-before(request:get-uri(), "public-repo/"), "public-repo/") - let $required-exist-version := $app/requires[@processor eq "http://exist-db.org"]/(@version, @semver-min)[1] - let $info-url := - concat($repoURL, "packages/", $current-abbrev, ".html", - if ($required-exist-version) then - concat("?eXist-db-min-version=", $required-exist-version) - else - () - ) - return - response:redirect-to(xs:anyURI($info-url)) - (: view current package info :) - else - let $app-versions := ($app, $app/other/version) - let $compatible-xar := app:find-version($app-versions, $procVersion, (), (), (), ()) - let $package := $app-versions[@path eq $compatible-xar] - let $version := ($package/@version, $package/version)[1] - let $show-details := true() - return - if (exists($package)) then - app:package-to-list-item($app, $version, $show-details) - else -
  • The package with convenient short name {$abbrev} and with unique package name {$app/name} is not compatible with eXist {$procVersion} and requires a newer version of eXist.
  • - return - if (exists($listing)) then - $listing - else - ( - response:set-status-code(404), -
  • No package {$abbrev} is available.
  • - ) - ) -}; - -declare function app:package-to-list-item($app as element(app), $version as xs:string, $show-details as xs:boolean) { - let $repoURL := concat(substring-before(request:get-uri(), "public-repo/"), "public-repo/") - let $icon := - if ($app/icon) then - if ($app/@status) then - $app/icon[1] - else - $repoURL || "public/" || $app/icon[1] - else - $repoURL || "resources/images/package.png" - let $path := ($app//version[@version eq $version]/@path, $app/@path)[1] - let $requires := ($app//version[@version eq $version]/requires, $app/requires)[1] - let $download-url := concat($repoURL, "public/", $path) - let $required-exist-version := $requires[@processor eq "http://exist-db.org"]/(@version, @semver-min)[1] - let $info-url := - concat($repoURL, "packages/", $app/abbrev[not(@type eq "legacy")], ".html", - if ($required-exist-version) then - concat("?eXist-db-min-version=", $required-exist-version) - else - () - ) - return -
  • -
    - -
    - { - switch ($app/type) - case ("application") return - application - case ("library") return - library - case ("plugin") return - plugin - default return () - } -

    {$app/title/text()}

    - { - if ($show-details) then - - - - - - - - - - - - - - { - if ($app/requires) then - - - - - else - () - } - - - - - - - - - - - - - - - - - { - if ($app/website != "") then - - - - - else - () - } - - - - - { - if ($app/other/version) then - - - - - else () - } - { - if ($app/changelog/change) then - ( - - - - , - for $change in $app/changelog/change - let $version := $change/@version/string() - let $comment := $change/node() - order by $version descending - return - - - - - ) - else () - } -
    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() }
    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 () - ) - }
    Change log:
    {$version}{$comment}
    - else -

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

    - } -
  • -}; - -declare function app:find-version($apps as element()*, $procVersion as xs:string, $version as xs:string?, $semVer as xs:string?, $min as xs:string?, $max as xs:string?) { - if (empty($apps)) then - () - else - if ($semVer) then - scanrepo:find-version($apps, $semVer, $semVer)/@path - else if ($version) then - $apps[version = $version]/@path | $apps[@version = $version]/@path - else if ($min or $max) then - scanrepo:find-version($apps, $min, $max)/@path - else - scanrepo:find-newest($apps, (), $procVersion)/@path -}; - -declare function app:requires-to-english($requires as element()) { - (: we assume @processor="http://exist.db-org/" :) - if ($requires/@version) then - concat(" version ", $requires/@version) - else if ($requires/@semver) then - concat(" version ", $requires/@semver) - else if ($requires/@semver-min) then - concat(" version ", $requires/@semver-min, " or later") - else if ($requires/@semver-max) then - concat(" version ", $requires/@semver-max, " or earlier") - else - " version 2.2" -}; diff --git a/modules/app.xqm b/modules/app.xqm new file mode 100644 index 0000000..200f1ec --- /dev/null +++ b/modules/app.xqm @@ -0,0 +1,452 @@ +xquery version "3.1"; + +(:~ + : HTML templating functions for populating web views of the public-repo + :) + +module namespace app="http://exist-db.org/xquery/app"; + +import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; +import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm"; +import module namespace templates="http://exist-db.org/xquery/templates"; +import module namespace versions="http://exist-db.org/apps/public-repo/versions" at "versions.xqm"; + +declare namespace request="http://exist-db.org/xquery/request"; +declare namespace response="http://exist-db.org/xquery/response"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; + +(:~ + : Load the package groups document for the admin page's package-groups section + :) +declare %templates:wrap function app:load-package-groups($node as node(), $model as map(*)) { + map { + "package-groups": + for $package-group in doc($config:package-groups-doc)//package-group + order by $package-group/abbrev[not(@type)] + return + $package-group, + "dates-published": + for $put in collection($config:logs-col)//event[type eq "put-package"] + group by $package-name := $put/package-name/string() + return + map { + "package-name": $package-name, + "versions": $put ! map { "version": ./package-version/string(), "date-published": ./dateTime cast as xs:dateTime } + } + } +}; + +(:~ + : Load the package title for the admin page's package-groups section + :) +declare function app:package-group-title($node as node(), $model as map(*)) { + $model?package-group/title/string() +}; + +(:~ + : Load the package title for the admin page's package-groups section + :) +declare function app:package-group-abbrev($node as node(), $model as map(*)) { + $model?package-group/abbrev/string() +}; + +(:~ + : Load the package title for the admin page's package-groups section + :) +declare function app:package-group-name($node as node(), $model as map(*)) { + $model?package-group/name/string() +}; + +(:~ + : Load the packages for each package-group + :) +declare %templates:wrap function app:load-packages($node as node(), $model as map(*)) { + map { "packages": $model?package-group//package } +}; + + +(:~ + : Load the package version + :) +declare function app:package-version($node as node(), $model as map(*)) { + $model?package/version +}; + +(:~ + : Load the package version + :) +declare function app:package-requires($node as node(), $model as map(*)) { + let $requires := $model?package/requires[@processor eq "http://exist-db.org"] + return + ($requires/@* except $requires/@processor) ! (./name() || ": " || ./string()) +}; + +(:~ + : Load the package version + :) +declare function app:package-date-published($node as node(), $model as map(*)) { + let $date-published := $model?dates-published[?package-name eq $model?package/name]?versions[?version eq $model?package/version]?date-published => head() + return + if (exists($date-published)) then + if (xs:date($date-published) = current-date()) then + format-dateTime($date-published, "Today [H00]:[m00]:[s00]") + else + format-dateTime($date-published, "[M00]/[D00]/[Y0000] [H00]:[m00]:[s00]") + else + "- (Predates logging)" +}; + +(:~ + : Load the get-package logs for the admin section's table + :) +declare %templates:wrap function app:load-get-package-logs-for-admin-table($node as node(), $model as map(*), $top-n as xs:integer) { + let $package-logs := + for $event in collection($config:logs-col)//event[type eq "get-package"] + group by $package-name := $event/package-name/string() + let $count := count($event) + order by $count descending + return + map { + "package-name": $package-name, + "count": $count + } + return + map { "package-logs": + subsequence($package-logs, 1, $top-n) + } +}; + +(:~ + : Load the package title for the admin section's table + :) +declare function app:get-package-stats($node as node(), $model as map(*)) { + $model?package-log?package-name || " (" || $model?package-log?count || ")" +}; + +(:~ + : Rebuild the package-groups metadata + :) +declare function app:rebuild-package-groups-metadata($node as node(), $model as map(*), $rebuild-package-groups-metadata as xs:boolean?) { + if ($rebuild-package-groups-metadata) then + let $_ := scanrepo:rebuild-package-groups() + return +

    The package-groups metadata has been rebuilt.

    + else + () +}; + +(:~ + : Landing page - show the compact version of all package groups + :) +declare function app:list-packages($node as node(), $model as map(*), $mode as xs:string?) { + for $package-group in doc($config:package-groups-doc)//package-group + let $show-details := false() + order by lower-case($package-group/title) + return + app:package-group-to-list-item($package-group, (), (), $show-details) +}; + +(:~ + : Single package group view - show the full version of this package group + : + : Package is found via the abbrev URL parameter, with an optional eXist version parameter. + : If the eXist version parameter is missing, eXist 2.2.0 is assumed (see config.xqm). + :) +declare function app:view-package($node as node(), $model as map(*), $mode as xs:string?) { + let $abbrev := request:get-parameter("abbrev", ()) + let $procVersion := request:get-parameter("eXist-db-min-version", $config:default-exist-version) + let $package-groups := doc($config:package-groups-doc)//package-group[abbrev eq $abbrev] + return + ( + if (count($package-groups) gt 1) then +

    More than one package matches the requested convenient short + name, {$abbrev}. The unique package names that + match this short name are: +

      + { + for $package-group in $package-groups + let $name := $package-group/name + order by $name + return +
    1. {$name/string()}
    2. + } +
    + The packages with versions available that are compatible with eXist {$procVersion} or higher are as follows: +

    + else + () + , + let $listing := + for $package-group in $package-groups + return + (: catch requests for a package using its legacy "abbrev" and redirect + : them to a URL using the app's current abbrev, to encourage use of the + : current abbrev :) + if ($package-group/abbrev[. eq $abbrev]/@type eq "legacy") then + let $current-abbrev := $package-group/abbrev[not(@type eq "legacy")] + (: TODO shouldn't we get $repoURL from $config? - joewiz :) + let $repoURL := concat(substring-before(request:get-uri(), "public-repo/"), "public-repo/") + let $newest-compatible-package := head($package-group//package[abbrev eq $abbrev]) + let $required-exist-version := $newest-compatible-package/requires[@processor eq $config:exist-processor-name]/(@version, @semver-min)[1] + let $info-url := + concat($repoURL, "packages/", $current-abbrev, ".html", + if ($required-exist-version) then + concat("?eXist-db-min-version=", $required-exist-version) + else + () + ) + return + response:redirect-to(xs:anyURI($info-url)) + (: view current package info :) + else + let $packages := $package-group//package + let $compatible-packages := versions:find-compatible-packages($packages, $procVersion) + let $incompatible-packages := $packages except $compatible-packages + let $show-details := true() + return + if ($compatible-packages or $incompatible-packages) then + let $trimmed-package-group := + element package-group { + $package-group/(title, name, abbrev), + $compatible-packages + } + return + app:package-group-to-list-item($trimmed-package-group, $incompatible-packages, $procVersion, $show-details) + else +
  • The package with convenient short name {$abbrev} and with unique package name {$package-group/name} is not compatible with eXist {$procVersion} and requires a newer version of eXist.
  • + return + if (exists($listing)) then + $listing + else + ( + response:set-status-code(404), +
  • No package {$abbrev} is available.
  • + ) + ) +}; + +(:~ + : Used by all HTML listings of packages - landing page views of all packages and interior views of individual package groups + :) +declare function app:package-group-to-list-item($package-group as element(package-group), $incompatible-packages as element(package)*, $procVersion as xs:string?, $show-details as xs:boolean) { + (: TODO shouldn't we get $repoURL from $config? - joewiz :) + let $repoURL := concat(substring-before(request:get-uri(), "public-repo/"), "public-repo/") + let $packages := $package-group//package + let $newest-package := head($packages) + let $older-packages := tail($packages) + let $icon := + if ($newest-package/icon) then + if ($newest-package/@status) then + $newest-package/icon[1] + else + $repoURL || "public/" || $newest-package/icon[1] + else + $repoURL || "resources/images/package.png" + let $path := $newest-package/@path + let $requires := $newest-package/requires + let $download-url := concat($repoURL, "public/", $path) + let $required-exist-version := $requires[@processor eq $config:exist-processor-name]/(@version, @semver-min)[1] + let $info-url := + concat($repoURL, "packages/", $package-group/abbrev[not(@type eq "legacy")], ".html", + if ($required-exist-version) then + concat("?eXist-db-min-version=", $required-exist-version) + else + () + ) + return +
  • +
    + +
    + { + switch ($newest-package/type) + case ("application") return + application + case ("library") return + library + case ("plugin") return + plugin + default return () + } +

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

    + { + if ($show-details) 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 ($older-packages or $incompatible-packages) then + + + + + else () + } + { + if (exists($newest-package/changelog/change)) then + ( + + + + , + for $change in $newest-package/changelog/change + let $version := $change/@version/string() + let $comment := $change/node() + order by $version descending + return + + + + + ) + else () + } +
    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 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/string() } + { + " (Note: Requires eXist-db " + || app:requires-to-english($requires) + || + ( + if ($package/abbrev ne $package-group/abbrev[not(@type = "legacy")]) then + (". This version's short title is “" || $package/abbrev || "”") + else + () + ) + || ".)" + } +
    • + }
    +
    Change log:
    {$version}{$comment}
    + else +

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

    + } +
  • +}; + +(:~ + : Express eXist version requirements in human readable form + :) +declare function app:requires-to-english($requires as element()) { + (: we assume @processor="http://exist.db-org/" :) + if ($requires/@version) then + concat(" version ", $requires/@version) + else if ($requires/@semver) then + concat(" version ", $requires/@semver) + else if ($requires/@semver-min) then + concat(" version ", $requires/@semver-min, " or later") + else if ($requires/@semver-max) then + concat(" version ", $requires/@semver-max, " or earlier") + else + " version " || $config:default-exist-version +}; + +(:~ + : Utility function for app:mkcol + :) +declare + %private +function app:mkcol-recursive($collection as xs:string, $components as xs:string*) { + if (exists($components)) then + let $newColl := concat($collection, "/", $components[1]) + return ( + xmldb:create-collection($collection, $components[1]), + app:mkcol-recursive($newColl, subsequence($components, 2)) + ) + else + () +}; + +(:~ + : Recursively create a collection hierarchy + :) +declare function app:mkcol($collection as xs:string, $path as xs:string) { + app:mkcol-recursive($collection, tokenize($path, "/")) +}; + diff --git a/modules/auto-update.xql b/modules/auto-update.xql deleted file mode 100644 index d761218..0000000 --- a/modules/auto-update.xql +++ /dev/null @@ -1,16 +0,0 @@ -xquery version "3.0"; - -module namespace trigger="http://exist-db.org/xquery/trigger"; -import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "xmldb:exist:///db/apps/public-repo/modules/scan.xql"; - -declare function trigger:after-create-document($uri as xs:anyURI) { - if (ends-with($uri, ".xar")) then scanrepo:scan() else () -}; - -declare function trigger:after-update-document($uri as xs:anyURI) { - if (ends-with($uri, ".xar")) then scanrepo:scan() else () -}; - -declare function trigger:after-delete-document($uri as xs:anyURI) { - if (ends-with($uri, ".xar")) then scanrepo:scan() else () -}; diff --git a/modules/config.xqm b/modules/config.xqm index be7d211..d0af083 100644 --- a/modules/config.xqm +++ b/modules/config.xqm @@ -1,15 +1,19 @@ +xquery version "3.1"; + (:~ - : A set of helper functions to access the application context from - : within a module. + : Configuration options for the application and a set of helper functions to access + : the application context. :) + module namespace config="http://exist-db.org/xquery/apps/config"; -declare namespace repo="http://exist-db.org/xquery/repo"; +declare namespace system="http://exist-db.org/xquery/system"; + declare namespace expath="http://expath.org/ns/pkg"; +declare namespace repo="http://exist-db.org/xquery/repo"; + +(: Determine the application root collection from the current module load path :) -(: - Determine the application root collection from the current module load path. -:) declare variable $config:app-root := let $rawPath := system:get-module-load-path() let $modulePath := @@ -17,6 +21,8 @@ declare variable $config:app-root := if (starts-with($rawPath, "xmldb:exist://")) then if (starts-with($rawPath, "xmldb:exist://embedded-eXist-server")) then substring($rawPath, 36) + else if (starts-with($rawPath, "xmldb:exist://null")) then + substring($rawPath, 19) else substring($rawPath, 15) else @@ -25,13 +31,39 @@ declare variable $config:app-root := substring-before($modulePath, "/modules") ; -declare variable $config:public := concat($config:app-root, "/public"); +(: Default collection and resource names for binary assets and extracted package metadata :) + +declare variable $config:app-data-parent-col := "/db/apps"; +declare variable $config:app-data-col-name := "public-repo-data"; +declare variable $config:packages-col-name := "packages"; +declare variable $config:icons-col-name := "icons"; +declare variable $config:metadata-col-name := "metadata"; +declare variable $config:logs-col-name := "logs"; + +declare variable $config:app-data-col := $config:app-data-parent-col || "/" || $config:app-data-col-name; +declare variable $config:packages-col := $config:app-data-col || "/" || $config:packages-col-name; +declare variable $config:icons-col := $config:app-data-col || "/" || $config:icons-col-name; +declare variable $config:metadata-col := $config:app-data-col || "/" || $config:metadata-col-name; +declare variable $config:logs-col := $config:app-data-col || "/" || $config:logs-col-name; +declare function config:log-subcol($date as xs:date) { format-date($date, "[Y]/[M01]") }; +declare function config:log-col($date as xs:date) { $config:logs-col || "/" || config:log-subcol($date) }; -declare variable $config:metadata-collection := concat($config:app-root, "/meta"); -declare variable $config:apps-doc := 'apps.xml'; -declare variable $config:packages-doc := 'packages.xml'; -declare variable $config:packages-meta := concat($config:metadata-collection, '/', $config:packages-doc); -declare variable $config:apps-meta := concat($config:metadata-collection, '/', $config:apps-doc); +declare variable $config:package-groups-doc-name := "package-groups.xml"; +declare variable $config:raw-packages-doc-name := "raw-packages.xml"; +declare function config:log-doc-name($date as xs:date) { "public-repo-log-" || format-date($date, "[Y]-[M01]-[D01]") || ".xml" }; + +declare variable $config:package-groups-doc := $config:metadata-col || "/" || $config:package-groups-doc-name; +declare variable $config:raw-packages-doc := $config:metadata-col || "/" || $config:raw-packages-doc-name; +declare function config:log-doc($date as xs:date) { config:log-col($date) || "/" || config:log-doc-name($date) }; + +(: The default version number here is assumed when a client does not send a version parameter. + It is set to 2.2.0 because this version was the last one known to work with most older packages + before packages began to declare their version constraints in their package metadata. + So this should stay as 2.2.0 until we (a) no longer have 2.2-era clients or (b) no longer have + packages that we care to offer compatibility with 2.2. + :) +declare variable $config:default-exist-version := "2.2.0"; +declare variable $config:exist-processor-name := "http://exist-db.org"; (:~ : Returns the repo.xml descriptor for the current application. @@ -40,6 +72,17 @@ declare function config:repo-descriptor() as element(repo:meta) { doc(concat($config:app-root, "/repo.xml"))/repo:meta }; +(:~ + : Returns the user and group from the repo.xml descriptor. + :) +declare function config:repo-permissions() as map(*) { + config:repo-descriptor()/repo:permissions ! + map { + "user": ./@user/string(), + "group": ./@group/string() + } +}; + (:~ : Returns the expath-pkg.xml descriptor for the current application. :) diff --git a/modules/download-xar-zip.xq b/modules/download-xar-zip.xq deleted file mode 100644 index d413684..0000000 --- a/modules/download-xar-zip.xq +++ /dev/null @@ -1,17 +0,0 @@ -xquery version "3.0"; - -import module namespace compression="http://exist-db.org/xquery/compression"; -import module namespace response="http://exist-db.org/xquery/response"; -import module namespace util="http://exist-db.org/xquery/util"; - -import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; - -let $pkg-file-name := fn:replace(request:get-url(), ".*/(.*)\.zip", "$1") -let $xar := util:binary-doc($config:app-root || "/public/" || $pkg-file-name) -return - let $entry := - {$xar} - let $zip := compression:zip($entry, false()) - return - response:stream-binary($zip, "application/zip") - diff --git a/modules/feed.xql b/modules/feed.xq similarity index 63% rename from modules/feed.xql rename to modules/feed.xq index 71c1cd5..aab9ccd 100644 --- a/modules/feed.xql +++ b/modules/feed.xq @@ -1,47 +1,68 @@ -xquery version "3.0"; +xquery version "3.1"; + +(:~ + : Generate an Atom feed of packages, with an entry for the newest version of each package + :) import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; -declare option exist:serialize "method=xml media-type=application/atom+xml"; +declare namespace request="http://exist-db.org/xquery/request"; +declare namespace util="http://exist-db.org/xquery/util"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; + +declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; + +declare option output:method "xml"; +declare option output:media-type "application/atom+xml"; + +declare function local:add-xhtml-ns($nodes) { + for $node in $nodes + return + if ($node instance of element()) then + element { QName("http://www.w3.org/1999/xhtml", local-name($node)) } { local:add-xhtml-ns($node/node()) } + else + $node +}; declare function local:feed-entries() { let $repoURL := concat(substring-before(request:get-url(), "public-repo/"), "public-repo/") - for $app in doc($config:apps-meta)//app + for $package-group in doc($config:package-groups-doc)//package-group + let $newest-package := head($package-group//package) let $icon := - if ($app/icon) then - if ($app/@status) then - $app/icon[1] + if ($newest-package/icon) then + if ($newest-package/@status) then + $newest-package/icon[1] else - $repoURL || "public/" || $app/icon[1] + $repoURL || "public/" || $newest-package/icon[1] else $repoURL || "resources/images/package.png" - let $required-exist-version := $app/requires[@processor eq "http://exist-db.org"]/(@version, @semver-min)[1] + let $required-exist-version := $newest-package/requires[@processor eq $config:exist-processor-name]/(@version, @semver-min)[1] let $info-url := - concat($repoURL, "packages/", $app/abbrev[not(@type eq "legacy")], ".html", + concat($repoURL, "packages/", $newest-package/abbrev[not(@type eq "legacy")], ".html", if ($required-exist-version) then concat("?eXist-db-min-version=", $required-exist-version) else () ) - let $title := $app/title - let $version := $app/version - let $authors := $app/author - let $description := $app/description - let $license := $app/license - let $website := $app/website - let $has-changelog := $app/changelog/* + let $title := $newest-package/title + let $version := $newest-package/version + let $authors := $newest-package/author + let $description := $newest-package/description + let $license := $newest-package/license + let $website := $newest-package/website + let $has-changelog := $newest-package/changelog/* let $changes := if ($has-changelog) then - for $change in $app/changelog/change + for $change in $newest-package/changelog/change let $version := $change/@version - let $comment := $change/node() + let $comment := local:add-xhtml-ns($change/node()) return (
    Version { $version/string() }
    ,
    { $comment }
    ) else () - let $updated := xmldb:last-modified($config:public, $app/@path) + let $updated := xmldb:last-modified($config:packages-col, $newest-package/@path) let $content :=
    @@ -90,7 +111,7 @@ declare function local:feed() { let $subtitle := "Repository for apps and libraries on eXist-db.org." let $self-href := request:get-url() let $id := "urn:uuid:" || util:uuid("existdb-public-package-repository-feed") - let $updated := xmldb:last-modified($config:public, "apps.xml") + let $updated := xmldb:last-modified($config:packages-col, "apps.xml") let $feed-entries := local:feed-entries() return @@ -103,4 +124,4 @@ declare function local:feed() { }; -local:feed() \ No newline at end of file +local:feed() diff --git a/modules/find.xq b/modules/find.xq new file mode 100644 index 0000000..5cce6d9 --- /dev/null +++ b/modules/find.xq @@ -0,0 +1,55 @@ +xquery version "3.1"; + +(:~ + : Respond to eXist build requests for packages using various identifier and version number criteria + : + : The info parameter can be used for troubleshooting + :) + +import module namespace app="http://exist-db.org/xquery/app" at "app.xqm"; +import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; +import module namespace versions="http://exist-db.org/apps/public-repo/versions" at "versions.xqm"; + +declare namespace request="http://exist-db.org/xquery/request"; +declare namespace response="http://exist-db.org/xquery/response"; + +let $abbrev := request:get-parameter("abbrev", ()) +let $name := request:get-parameter("name", ()) +let $exist-version-semver := request:get-parameter("processor", $config:default-exist-version) +let $version := request:get-parameter("version", ()) +let $semver := request:get-parameter("semver", ()) +let $semver-min := request:get-parameter("semver-min", ()) +let $semver-max := request:get-parameter("semver-max", ()) +let $zip := request:get-parameter("zip", ()) +let $info := request:get-parameter("info", ()) +let $app-root-absolute-url := request:get-parameter("app-root-absolute-url", ()) + +let $package-group := + if ($name) then + doc($config:package-groups-doc)//package-group[name eq $name] + else + doc($config:package-groups-doc)//package-group[abbrev eq $abbrev] + +let $newest-compatible-package := versions:find-newest-compatible-package($package-group//package, $exist-version-semver, $version, $semver, $semver-min, $semver-max) + +return + if ($newest-compatible-package) then + (: TODO shouldn't we get $abs-public from $config? - joewiz :) + let $abs-public := $app-root-absolute-url || "/public/" + let $xar-filename := $newest-compatible-package/@path + return + if ($info) then + element found { + $newest-compatible-package/@sha256, + $newest-compatible-package/version ! attribute version {.}, + $newest-compatible-package/@path + } + else if ($zip) then + response:redirect-to(xs:anyURI($abs-public || $xar-filename || ".zip")) + else + response:redirect-to(xs:anyURI($abs-public || $xar-filename)) + else + ( + response:set-status-code(404), +

    Package file not found!

    + ) diff --git a/modules/find.xql b/modules/find.xql deleted file mode 100644 index 978cb09..0000000 --- a/modules/find.xql +++ /dev/null @@ -1,41 +0,0 @@ -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 config="http://exist-db.org/xquery/apps/config" at "config.xqm"; - -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 $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 $app-root-absolute-url := request:get-parameter("app-root-absolute-url", ()) -let $app := - if ($name) then - doc($config:apps-meta)//app[name eq $name] - else - doc($config:apps-meta)//app[abbrev eq $abbrev] -let $app-versions := ($app, $app/other/version) -let $compatible-xar := app:find-version($app-versions, $procVersion, $version, $semVer, $minVersion, $maxVersion) -return - if ($compatible-xar) then - let $abs-public := $app-root-absolute-url || "/public/" - return - if ($info) then - let $app := doc($config:apps-meta)//(app|version)[@path eq $compatible-xar] - return - {$app/@sha256,($app/version,$app/@version)[1] ! attribute version {.},$compatible-xar} - else if ($zip) then - response:redirect-to(xs:anyURI($abs-public || $compatible-xar || ".zip")) - else - response:redirect-to(xs:anyURI($abs-public || $compatible-xar)) - else ( - response:set-status-code(404), -

    Package file {$compatible-xar} not found!

    - ) 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..db09a08 --- /dev/null +++ b/modules/get-package.xq @@ -0,0 +1,60 @@ +xquery version "3.1"; + +(:~ + : Allows download of packages + : + : Responds to requests like: + : - /exist/apps/public-repo/public/eXide-1.0.0.xar + : - /exist/apps/public-repo/public/eXide-1.0.0.xar.zip + :) + +import module namespace app="http://exist-db.org/xquery/app" at "app.xqm"; +import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; + +declare namespace request="http://exist-db.org/xquery/request"; +declare namespace response="http://exist-db.org/xquery/response"; +declare namespace util="http://exist-db.org/xquery/util"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; + +declare function local:log-get-package-event($filename as xs:string) as empty-sequence() { + let $package := doc($config:raw-packages-doc)//package[@path eq $filename] + let $today := current-date() + let $log-doc := config:log-doc($today) + let $event := + element event { + element dateTime { current-dateTime() }, + element type { "get-package" }, + element package-name { $package/name/string() }, + element package-version { $package/version/string() } + } + let $update-log := + if (doc-available($log-doc)) then + update insert $event into doc($log-doc)/public-repo-log + else + ( + app:mkcol($config:logs-col, config:log-subcol($today)), + xmldb:store(config:log-col($today), config:log-doc-name($today), element public-repo-log { $event } ) + ) + return + () +}; + +let $filename := request:get-parameter("filename", ()) +let $xar-filename := + (: strip .zip from resource name :) + if (ends-with($filename, ".zip")) then + replace($filename, ".zip$", "") + else + $filename +let $path := $config:packages-col || "/" || $xar-filename +return + if (util:binary-doc-available($path)) then + let $xar := util:binary-doc($config:packages-col || "/" || $xar-filename) + let $log := local:log-get-package-event($filename) + return + response:stream-binary($xar, "application/zip") + else + ( + response:set-status-code(404), +

    Package file not found!

    + ) diff --git a/modules/list.xq b/modules/list.xq new file mode 100644 index 0000000..a698bf2 --- /dev/null +++ b/modules/list.xq @@ -0,0 +1,55 @@ +xquery version "3.1"; + +(:~ + : Filter all package groups, returning a list of only the compatible versions. + : + : The format of the results preserves compatibility with the package-repo v1.x API + :) + +import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; +import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm"; +import module namespace semver="http://exist-db.org/xquery/semver"; +import module namespace versions="http://exist-db.org/apps/public-repo/versions" at "versions.xqm"; + +declare namespace request="http://exist-db.org/xquery/request"; + +declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; + +declare option output:method "xml"; +declare option output:media-type "application/xml"; + +let $exist-version := request:get-parameter("version", ()) +let $basic-semver-regex := "^\d+\.\d+\.\d+-?.*$" +let $exist-version-semver := + if (matches($exist-version, $basic-semver-regex)) then + $exist-version + else + $config:default-exist-version +return + element apps { + attribute version { $exist-version-semver }, + for $package-group in doc($config:package-groups-doc)//package-group + let $compatible-packages := versions:find-compatible-packages($package-group//package, $exist-version-semver) + return + if (exists($compatible-packages)) then + let $newest-package := head($compatible-packages) + let $older-packages := tail($compatible-packages) + return + element app { + $newest-package/@*, + $newest-package/*, + if (exists($older-packages)) then + element older { + for $package in $older-packages + return + element version { + $package/@*, + $package/requires + } + } + else + () + } + else + () + } diff --git a/modules/list.xql b/modules/list.xql deleted file mode 100644 index d747ae7..0000000 --- a/modules/list.xql +++ /dev/null @@ -1,85 +0,0 @@ -xquery version "3.0"; - -declare namespace list="http://exist-db.org/apps/public-repo/list"; -declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; - -import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; -import module namespace semver = "http://exist-db.org/xquery/semver"; - -declare option output:method "xml"; -declare option output:media-type "application/xml"; - -(: The default version number here is assumed when a client does not send a version parameter. - It is set to 2.2.0 because this version was the last one known to work with most older packages - before packages began to declare their version constraints in their package metadata. - So this should stay as 2.2.0 until we (a) no longer have 2.2-era clients or (b) no longer have - packages that we care to offer compatibility with 2.2. - :) -declare variable $list:DEFAULT_VERSION := "2.2.0"; - -declare function list:is-newer-or-same($version1 as xs:string, $version2 as xs:string?) { - empty($version2) or - semver:ge($version1, $version2, true()) -}; - -declare function list:is-older-or-same($version1 as xs:string, $version2 as xs:string?) { - empty($version2) or - semver:le($version1, $version2, true()) -}; - -declare function list:get-app($app as element(), $version as xs:string) { - if ($app/requires) then - let $min := $app/requires/@semver-min - let $max := $app/requires/@semver-max - return - if ($min or $max) then - if (list:is-newer-or-same($version, $app/requires/@semver-min) and - list:is-older-or-same($version, $app/requires/@semver-max)) then - $app - else - (: get older version :) - list:versions($app, $version) - else - $app - else - $app -}; - -declare function list:versions($app as element(), $version as xs:string) { - let $v := list:find-version($app/other/version, $version) - return - if ($v) then - - { $app/@* except $app/@path } - { $v/@version/string() } - { $app/* except $app/version } - - else - () -}; - -declare function list:find-version($versions as element()*, $version as xs:string) { - if (empty($versions)) then - () - else - let $v := head($versions) - let $req := $v/requires - return - if (list:is-newer-or-same($version, $req/@semver-min) and - list:is-older-or-same($version, $req/@semver-max)) then - $v - else - list:find-version(tail($versions), $version) -}; - -let $version := request:get-parameter("version", $list:DEFAULT_VERSION) -let $version := if (matches($version, "^\d+\.\d+\.\d+-?.*$")) then $version else $list:DEFAULT_VERSION - -return - - { - for $app in doc($config:apps-meta)//app - return - list:get-app($app, $version) - } - diff --git a/modules/put-package.xq b/modules/put-package.xq new file mode 100644 index 0000000..09be5dd --- /dev/null +++ b/modules/put-package.xq @@ -0,0 +1,82 @@ +xquery version "3.1"; + +(:~ + : Receives uploaded packages and immediately publishes them to the package repository + :) + +import module namespace app="http://exist-db.org/xquery/app" at "app.xqm"; +import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; +import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm"; + +declare namespace request="http://exist-db.org/xquery/request"; +declare namespace sm="http://exist-db.org/xquery/securitymanager"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; + +declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; + +declare option output:method "json"; +declare option output:media-type "application/json"; + +declare function local:log-put-package-event($filename as xs:string) as empty-sequence() { + let $package := doc($config:raw-packages-doc)//package[@path eq $filename] + let $today := current-date() + let $log-doc := config:log-doc($today) + let $event := + element event { + element dateTime { current-dateTime() }, + element type { "put-package" }, + element package-name { $package/name/string() }, + element package-version { $package/version/string() } + } + let $update-log := + if (doc-available($log-doc)) then + update insert $event into doc($log-doc)/public-repo-log + else + ( + app:mkcol($config:logs-col, config:log-subcol($today)), + xmldb:store(config:log-col($today), config:log-doc-name($today), element public-repo-log { $event } ) + ) + return + () +}; + +declare function local:upload-and-publish($xar-filename as xs:string, $xar-binary as xs:base64Binary) { + let $path := xmldb:store($config:packages-col, $xar-filename, $xar-binary) + let $publish := scanrepo:publish-package($xar-filename) + return + map { + "files": array { + map { + "name": $xar-filename, + "type": xmldb:get-mime-type($path), + "size": xmldb:size($config:packages-col, $xar-filename) + } + } + } +}; + +let $xar-filename := request:get-uploaded-file-name("files[]") +let $xar-binary := request:get-uploaded-file-data("files[]") +let $user := request:get-attribute("org.exist.public-repo.login.user") +let $required-group := config:repo-permissions()?group +return + if (exists($user) and sm:get-user-groups($user) = $required-group) then + try { + local:upload-and-publish($xar-filename, $xar-binary), + local:log-put-package-event($xar-filename) + } catch * { + map { + "result": + map { + "name": $xar-filename, + "error": $err:description + } + } + } + else + ( + response:set-status-code(401), + map { + "error": "User must be a member of the " || $required-group || " group." + } + ) diff --git a/modules/scan.xql b/modules/scan.xql deleted file mode 100644 index f4c13e5..0000000 --- a/modules/scan.xql +++ /dev/null @@ -1,281 +0,0 @@ -xquery version "3.1"; - -module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo"; - -import module namespace config = "http://exist-db.org/xquery/apps/config" at "config.xqm"; -import module namespace crypto = "http://expath.org/ns/crypto"; -import module namespace semver = "http://exist-db.org/xquery/semver"; -import module namespace util = "http://exist-db.org/xquery/util"; - -declare namespace repo="http://exist-db.org/xquery/repo"; -declare namespace expath="http://expath.org/ns/pkg"; - -declare function scanrepo:is-newer-or-same($version1 as xs:string, $version2 as xs:string?) { - empty($version2) or - semver:ge($version1, $version2, true()) -}; - -declare function scanrepo:is-older-or-same($version1 as xs:string, $version2 as xs:string?) { - empty($version2) or - semver:le($version1, $version2, true()) -}; - -declare function scanrepo:process($apps as element(app)*) { - for $app in $apps - order by $app/title - group by $name := $app/name - return - (: Identify newest version of the package; sort previous versions newest to oldest; use SemVer 2.0 rules, coercing where needed :) - let $versions := $app/version - let $version-maps := - $versions ! map:merge(( - map:entry("semver", semver:coerce(.) => semver:serialize()), - map:entry("version", .) - )) - let $sorted-semvers := semver:sort($version-maps?semver) => reverse() - let $sorted-versions := - for $semver in $sorted-semvers - return - $version-maps[?semver eq $semver]?version/.. - let $newest-version := $sorted-versions => head() - let $older-versions := $sorted-versions => tail() - let $abbrevs := distinct-values($app/abbrev) - return - - { - $newest-version/@*, - $newest-version/*, - $abbrevs[not(. = $newest-version/abbrev)] ! element abbrev { attribute type { "legacy" }, . } - } - - { - for $older in $older-versions - let $xar := concat($config:public, "/", $older/@path) - let $hash := crypto:hash( - util:binary-doc($xar), - "sha256", - "hex" - ) - return - { - $older/@path, - attribute size { xmldb:size($config:public, $older/@path) }, - attribute sha256 { $hash }, - $older/requires - } - } - - -}; - -declare function scanrepo:find-newest($apps as element()*, $newest as element()?, $procVersion as xs:string?) { - if (empty($apps)) then - $newest - else - let $app := head($apps) - let $newer := - if ($procVersion and - not(scanrepo:is-newer-or-same($procVersion, $app/requires/@semver-min) and - scanrepo:is-older-or-same($procVersion, $app/requires/@semver-max))) then - $newest - else if (empty($newest) or scanrepo:is-newer(($app/version, $app/@version), ($newest/version, $newest/@version))) then - $app - else - $newest - return - scanrepo:find-newest(tail($apps), $newer, $procVersion) -}; - -declare function scanrepo:find-version($apps as element()*, $minVersion as xs:string?, $maxVersion as xs:string?) { - let $minVersion := if ($minVersion) then $minVersion else "0" - let $maxVersion := if ($maxVersion) then $maxVersion else "9999" - return - scanrepo:find-version($apps, $minVersion, $maxVersion, ()) -}; - -declare %private function scanrepo:find-version($apps as element()*, $minVersion as xs:string, $maxVersion as xs:string, $newest as element()?) { - if (empty($apps)) then - $newest - else - let $app := head($apps) - let $appVersion := $app/version | $app/@version - let $newer := - if ( - (empty($newest) or scanrepo:is-newer($appVersion, ($newest/version, $newest/@version))) and - scanrepo:is-newer($appVersion, $minVersion) and - scanrepo:is-older($appVersion, $maxVersion) - ) then - $app - else - $newest - return - scanrepo:find-version(tail($apps), $minVersion, $maxVersion, $newer) -}; - -declare %private function scanrepo:is-newer($available as xs:string, $installed as xs:string) as xs:boolean { - let $verInstalled := tokenize($installed, "\.") - let $verAvailable := tokenize($available, "\.") - return - scanrepo:compare-versions($verInstalled, $verAvailable, function($version1, $version2) { - number($version1) >= number($version2) - }) -}; - -declare %private function scanrepo:is-older($available as xs:string, $installed as xs:string) as xs:boolean { - let $verInstalled := tokenize($installed, "\.") - let $verAvailable := tokenize($available, "\.") - return - scanrepo:compare-versions($verInstalled, $verAvailable, function($version1, $version2) { - number($version1) <= number($version2) - }) -}; - -declare %private function scanrepo:compare-versions($installed as xs:string*, $available as xs:string*, - $compare as function(*)) as xs:boolean { - if (empty($installed)) then - exists($available) - else if (empty($available)) then - false() - else if (head($available) = head($installed)) then - if (count($available) = 1 and count($installed) = 1) then - true() - else - scanrepo:compare-versions(tail($installed), tail($available), $compare) - else - $compare(head($available), head($installed)) -}; - -declare function scanrepo:handle-icon ($path as xs:string, $data as item()?, $param as item()*) as element(icon) { - let $pkgName := substring-before($param, ".xar") - - let $suffix := replace($path, "^.*\.([^\.]+)", "$1") - let $name := concat($pkgName, ".", $suffix) - let $stored := xmldb:store($config:public, $name, $data) - - return - { $name } -}; - -declare function scanrepo:handle-expath-package ($root as element(expath:package)) as element()* { - {$root/@name/string()}, - {$root/expath:title/text()}, - {$root/@abbrev/string()}, - {$root/@version/string()}, - if ($root/expath:dependency[starts-with(@processor, "http://exist-db.org")]) then - { $root/expath:dependency[starts-with(@processor, "http://exist-db.org")]/@* } - else - () -}; - -declare function scanrepo:handle-repo-meta ($root as element(repo:meta)) as element()+ { - for $author in $root/repo:author - return - {$author/text()} - , - {$root/repo:description/text()}, - {$root/repo:website/text()}, - {$root/repo:license/text()}, - {$root/repo:type/text()} - , - for $note in $root/repo:note - return - {$note/text()} - , - - { - scanrepo:copy-changelog($root/repo:changelog/repo:change) - } - -}; - -declare function scanrepo:entry-data($path as xs:anyURI, $type as xs:string, $data as item()?, $param as item()*) as item()* -{ - if (starts-with($path, "icon")) then - scanrepo:handle-icon($path, $data, $param) - else - let $root := $data/* - return - typeswitch ($root) - case element(expath:package) return - scanrepo:handle-expath-package($root) - case element(repo:meta) return - scanrepo:handle-repo-meta($root) - default return - () -}; - -declare function scanrepo:copy-changelog($nodes as node()*) { - for $node in $nodes - return - typeswitch ($node) - case element() return - element { local-name($node) } { - $node/@*, - scanrepo:copy-changelog($node/node()) - } - default return - $node -}; - -declare function scanrepo:entry-filter($path as xs:anyURI, $type as xs:string, $param as item()*) as xs:boolean { - starts-with($path, "icon.") or - $path = ("repo.xml", "expath-pkg.xml") -}; - -declare function scanrepo:extract-metadata ($resource as xs:string) as element(app) { - let $xar := concat($config:public, "/", $resource) - let $data := util:binary-doc($xar) - - let $hash := crypto:hash($data, "sha256", "hex") - - return - - { - compression:unzip( - $data, - scanrepo:entry-filter#3, - (), - scanrepo:entry-data#4, - $resource - ) - } - -}; - -declare function scanrepo:scan-all() { - for $resource in xmldb:get-child-resources($config:public) - where ends-with($resource, ".xar") - return - scanrepo:extract-metadata($resource) -}; - -declare function scanrepo:scan() { - let $data := doc($config:packages-meta)//app - let $processed := { scanrepo:process($data) } - let $store := xmldb:store($config:metadata-collection, $config:apps-doc, $processed) - - return $processed -}; - -declare function scanrepo:rebuild-package-meta() as xs:string { - xmldb:store($config:metadata-collection, $config:packages-doc, - { scanrepo:scan-all() }) -}; - -declare function scanrepo:add-package-meta($meta as element(app)) { - let $packages := doc($config:packages-meta)/packages - let $node-to-update := $packages/app[@path=$meta/@path] - - return - if (exists($node-to-update)) - then (update replace $node-to-update with $meta) - else (update insert $meta into $packages) -}; - -declare function scanrepo:publish($xar as xs:string) { - let $meta := $xar - => scanrepo:extract-metadata() - => scanrepo:add-package-meta() - - return scanrepo:scan() -}; diff --git a/modules/scan.xqm b/modules/scan.xqm new file mode 100644 index 0000000..91c0dd1 --- /dev/null +++ b/modules/scan.xqm @@ -0,0 +1,240 @@ +xquery version "3.1"; + +(:~ + : Functions to extract metadata from packages and populate, update, or rebuild package metadata files + :) + +module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo"; + +import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; +import module namespace semver="http://exist-db.org/xquery/semver"; + +declare namespace compression="http://exist-db.org/xquery/compression"; +declare namespace repo="http://exist-db.org/xquery/repo"; +declare namespace util="http://exist-db.org/xquery/util"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; + +declare namespace expath="http://expath.org/ns/pkg"; + +(:~ + : Helper function to store a package's icon and transform its metadata into the format needed for raw-metadata + :) +declare + %private +function scanrepo:handle-icon($path as xs:string, $data as item()?, $param as item()*) as element(icon) { + let $pkgName := substring-before($param, ".xar") + let $suffix := replace($path, "^.*\.([^\.]+)", "$1") + let $name := concat($pkgName, ".", $suffix) + let $stored := xmldb:store($config:icons-col, $name, $data) + return + element icon { $name } +}; + +(:~ + : Helper function to transform expath-pkg.xml metadata into the format needed for raw-metadata + :) +declare + %private +function scanrepo:handle-expath-pkg-metadata($root as element(expath:package)) as element()* { + $root/(@name, expath:title, @abbrev, @version) ! + element { local-name(.) } { ./string() }, + $root/expath:dependency[@processor eq $config:exist-processor-name] ! + element requires { ./@* } +}; + +(:~ + : Helper function to transform repo.xml's changelog metadata into the format needed for raw-metadata + :) +declare + %private +function scanrepo:copy-changelog($nodes as node()*) { + for $node in $nodes + return + typeswitch ($node) + case element() return + element { local-name($node) } { + $node/@*, + scanrepo:copy-changelog($node/node()) + } + default return + $node +}; + +(:~ + : Helper function to transform repo.xml metadata into the format needed for raw-metadata + :) +declare + %private +function scanrepo:handle-repo-metadata($root as element(repo:meta)) as element()+ { + $root/(repo:author, repo:description, repo:website, repo:license, repo:type, repo:note) ! + element { local-name(.) } { ./string() }, + element changelog { scanrepo:copy-changelog($root/repo:changelog/repo:change) } +}; + +(:~ + : Helper function to handle transformation of icon and package metadata for extraction from the xar + :) +declare + %private +function scanrepo:entry-data($path as xs:anyURI, $type as xs:string, $data as item()?, $param as item()*) as item()* +{ + if (starts-with($path, "icon")) then + scanrepo:handle-icon($path, $data, $param) + else + let $root := $data/* + return + typeswitch ($root) + case element(expath:package) return + scanrepo:handle-expath-pkg-metadata($root) + case element(repo:meta) return + scanrepo:handle-repo-metadata($root) + default return + () +}; + +(:~ + : Helper function to select assets from a package for extraction from the xar + :) +declare + %private +function scanrepo:entry-filter($path as xs:anyURI, $type as xs:string, $param as item()*) as xs:boolean { + starts-with($path, "icon.") or $path = ("repo.xml", "expath-pkg.xml") +}; + +(:~ + : Take a group of packages with the same package name (a URI) and generate a package-group + :) +declare +(: %private:) +function scanrepo:generate-package-group($packages as element(package)*) { + if (count(distinct-values($packages/name)) gt 1) then + error(QName("scanrepo", "group-error"), "Supplied packages do not have the same name") + else + (: Identify newest version of the package; sort previous versions newest to oldest; use SemVer 2.0 rules, coercing where needed :) + let $versions := $packages/version + let $version-maps := + $versions ! map:merge(( + map:entry("semver", semver:coerce(.) => semver:serialize()), + map:entry("version", .) + )) + let $sorted-semvers := semver:sort($version-maps?semver) => reverse() + let $sorted-packages := + for $semver in $sorted-semvers + return + $version-maps[?semver eq $semver]?version/.. + let $newest-package := $sorted-packages => head() + let $legacy-abbrevs := distinct-values($packages/abbrev)[not(. = $newest-package/abbrev)] + return + element package-group { + $newest-package/(title, name, abbrev), + $legacy-abbrevs ! element abbrev { attribute type { "legacy" }, . }, + element packages { $sorted-packages } + } +}; + + +(:~ + : Update a package group, creating it if necessary + :) +declare function scanrepo:update-package-group($raw-package-name as xs:string) { + let $raw-packages := doc($config:raw-packages-doc)/raw-packages + let $raw-packages-to-group := $raw-packages/package[name = $raw-package-name] + let $package-groups := doc($config:package-groups-doc)/package-groups + let $current-package-group := $package-groups/package-group[name eq $raw-package-name] + return + if (exists($current-package-group)) then + update replace $current-package-group with scanrepo:generate-package-group($raw-packages-to-group) + else + update insert scanrepo:generate-package-group($raw-packages-to-group) into $package-groups +}; + +(:~ + : Add a package's metadata to raw-packages + :) +declare function scanrepo:add-raw-package($raw-package as element(package)) { + let $raw-packages := doc($config:raw-packages-doc)/raw-packages + let $current-raw-package := $raw-packages/package[@path = $raw-package/@path] + return + if (exists($current-raw-package)) then + update replace $current-raw-package with $raw-package + else + update insert $raw-package into $raw-packages +}; + +(:~ + : Extract a stored package's raw-package metadata + :) +declare function scanrepo:extract-raw-package($xar-filename as xs:string) as element(package) { + let $xar-path := $config:packages-col || "/" || $xar-filename + let $xar-binary := util:binary-doc($xar-path) + let $package-metadata := + compression:unzip( + $xar-binary, + scanrepo:entry-filter#3, + (), + scanrepo:entry-data#4, + $xar-filename + ) + return + element package { + attribute path { $xar-filename }, + attribute size { xmldb:size($config:packages-col, $xar-filename) }, + attribute sha256 { util:binary-doc-content-digest($xar-path, "SHA-256") => string() }, + $package-metadata + } +}; + +(:~ + : Publish a stored package by adding it to the raw-packages and package-groups metadata + :) +declare function scanrepo:publish-package($xar-filename as xs:string) { + let $package := scanrepo:extract-raw-package($xar-filename) + return + ( + scanrepo:add-raw-package($package), + scanrepo:update-package-group($package/name) + ) +}; + +(:~ + : Rebuild the package-groups metadata by merging raw-packages metadata into package-groups + :) +declare function scanrepo:rebuild-package-groups() as xs:string { + let $groups := + for $package in doc($config:raw-packages-doc)//package + group by $name := $package/name + return + scanrepo:generate-package-group($package) + let $package-groups := + element package-groups { + for $group in $groups + order by $group/abbrev[not(@type = "legacy")] + return + $group + } + return + xmldb:store($config:metadata-col, $config:package-groups-doc-name, $package-groups) +}; + +(:~ + : Rebuild the raw-packages metadata from all stored packages + :) +declare function scanrepo:rebuild-raw-packages() as xs:string { + let $raw-packages := + element raw-packages { + for $package-xar in xmldb:get-child-resources($config:packages-col)[ends-with(., ".xar")] + order by $package-xar + return + scanrepo:extract-raw-package($package-xar) + } + return + xmldb:store($config:metadata-col, $config:raw-packages-doc-name, $raw-packages) +}; + +(:~ + : Rebuild all package metadata + :) +declare function scanrepo:rebuild-all-package-metadata() as xs:string+ { + scanrepo:rebuild-raw-packages(), + scanrepo:rebuild-package-groups() +}; diff --git a/modules/update.xq b/modules/update.xq new file mode 100644 index 0000000..6bf7f87 --- /dev/null +++ b/modules/update.xq @@ -0,0 +1,9 @@ +xquery version "3.1"; + +(:~ + : Rebuild metadata for all packages + :) + +import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xqm"; + +scanrepo:rebuild-all-package-metadata() \ No newline at end of file diff --git a/modules/update.xql b/modules/update.xql deleted file mode 100644 index ed02740..0000000 --- a/modules/update.xql +++ /dev/null @@ -1,11 +0,0 @@ -xquery version "1.0"; - -import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xql"; - -scanrepo:rebuild-package-meta() -=> scanrepo:scan() - -(: $file -=> scanrepo:extract-metadata() -=> scanrepo:add-package-meta() -=> scanrepo:scan() :) diff --git a/modules/upgrade-to-v2-storage.xq b/modules/upgrade-to-v2-storage.xq new file mode 100644 index 0000000..1ea3903 --- /dev/null +++ b/modules/upgrade-to-v2-storage.xq @@ -0,0 +1,81 @@ +xquery version "3.1"; + +(:~ + : A script for upgrading public-repo's storage format to v2. + : + : This script is only needed if you are preparing to upgrade from public-repo v0-1 to public-repo v2+. + :) + +import module namespace semver = "http://exist-db.org/xquery/semver"; + +declare namespace pkg="http://expath.org/ns/pkg"; +declare namespace repo="http://exist-db.org/xquery/repo"; +declare namespace sm="http://exist-db.org/xquery/securitymanager"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; + +declare function local:chgrp-repo($path) { + if (sm:get-permissions($path)/*/@group = "repo") then + () + else + ( + sm:chown($path, "repo"), + sm:chgrp($path, "repo") + ) +}; + +declare function local:upgrade-to-public-repo-2-storage() as element()+ { + (: create new collections :) + element collections { + ( + xmldb:create-collection("/db/apps", "public-repo-data"), + for $subcollection in ("icons", "metadata", "packages") + return + xmldb:create-collection("/db/apps/public-repo-data", $subcollection) + ) ! + ( + local:chgrp-repo(.), + element collection { . } + ) + }, + + (: move xars and icons to new collections :) + if (xmldb:collection-available("/db/apps/public-repo/public")) then + ( + element packages { + for $package in xmldb:get-child-resources("/db/apps/public-repo/public")[ends-with(., ".xar")] + return + xmldb:copy-resource("/db/apps/public-repo/public", $package, "/db/apps/public-repo-data/packages", $package, true()) ! + ( + local:chgrp-repo(.), + element package { . } + ) + }, + element icons { + for $icon in xmldb:get-child-resources("/db/apps/public-repo/public")[not(matches(., "\.(xar|xml)$"))] + return + xmldb:copy-resource("/db/apps/public-repo/public", $icon, "/db/apps/public-repo-data/icons", $icon, true()) + ! + ( + local:chgrp-repo(.), + element icon { . } + ) + } + ) + else + () +}; + +if (repo:list() = "http://exist-db.org/apps/public-repo") then + if (semver:lt(doc("/db/apps/public-repo/expath-pkg.xml")/pkg:package/@version, "2.0.0")) then + element status { + element description { "Upgrade of public-repo storage to v2 format is complete. Please install latest public-repo v2+." }, + local:upgrade-to-public-repo-2-storage() + } + else + element status { + element description { "No action taken. A version of public-repo with the v2 format is already installed." } + } +else + element status { + element description { "No action taken. The public-repo app is not installed." } + } diff --git a/modules/upload.xql b/modules/upload.xql deleted file mode 100644 index c4cf8a8..0000000 --- a/modules/upload.xql +++ /dev/null @@ -1,35 +0,0 @@ -xquery version "3.0"; - -import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; -import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "scan.xql"; - -declare namespace json="http://www.json.org"; - -declare option exist:serialize "method=json media-type=application/json"; - -declare function local:upload-and-publish($xar as xs:string, $data) { - let $path := xmldb:store($config:public, $xar, $data) - let $scan := scanrepo:publish($xar) - - return - - - {$path} - {xmldb:get-mime-type($path)} - {xmldb:size($config:public, $xar)} - - -}; - -let $name := request:get-uploaded-file-name("files[]") -let $data := request:get-uploaded-file-data("files[]") - -return - try { - local:upload-and-publish($name, $data) - } catch * { - - {$name} - {$err:description} - - } \ No newline at end of file diff --git a/modules/versions.xqm b/modules/versions.xqm new file mode 100644 index 0000000..5408535 --- /dev/null +++ b/modules/versions.xqm @@ -0,0 +1,167 @@ +xquery version "3.1"; + +(:~ + : A library module for finding packages by version number criteria + :) + +module namespace versions="http://exist-db.org/apps/public-repo/versions"; + +import module namespace semver="http://exist-db.org/xquery/semver"; + +(:~ + : Find all packages compatible with a specific version of eXist (or higher) + :) +declare function versions:find-compatible-packages( + $packages as element(package)*, + $exist-version-semver as xs:string +) as element(package)* { + versions:find-compatible-packages($packages, $exist-version-semver, (), (), (), ()) +}; + +(:~ + : Find all packages compatible with a version of eXist meeting various version criteria + : + : TODO: find packages with version, semver, or min/max-version attributes to test those conditions - joewiz + :) +declare function versions:find-compatible-packages( + $packages as element(package)*, + $exist-version-semver as xs:string, + $version as xs:string?, + $semver as xs:string?, + $semver-min as xs:string?, + $semver-max as xs:string? +) as element(package)* { + for $package in $packages + return + if ($semver) then + versions:find-version($packages, $semver, $semver) + else if ($version) then + $packages[version = $version] + else if ($semver-min and $semver-max) then + versions:find-version($packages, $semver-min, $semver-max) + else if + ( + $exist-version-semver and + versions:is-newer-or-same($exist-version-semver, $package/requires/@semver-min) and + versions:is-older-or-same($exist-version-semver, $package/requires/@semver-max) + ) then + $package + else + () +}; + +(:~ + : Find the newest version of packages compatible with a specific version of eXist (or higher) + :) +declare function versions:find-newest-compatible-package( + $packages as element(package)*, + $exist-version-semver as xs:string +) as element(package)? { + versions:find-newest-compatible-package($packages, $exist-version-semver, (), (), (), ()) +}; + +(:~ + : Find the newest version of packages compatible with a version of eXist meeting various version criteria + :) +declare function versions:find-newest-compatible-package( + $packages as element(package)*, + $exist-version-semver as xs:string, + $version as xs:string?, + $semver as xs:string?, + $min-version as xs:string?, + $max-version as xs:string? +) as element(package)? { + versions:find-compatible-packages($packages, $exist-version-semver, $version, $semver, $min-version, $max-version) + => head() +}; + +declare + %private +function versions:is-newer-or-same($version1 as xs:string, $version2 as xs:string?) { + empty($version2) or semver:ge($version1, $version2, true()) +}; + +declare + %private +function versions:is-older-or-same($version1 as xs:string, $version2 as xs:string?) { + empty($version2) or semver:le($version1, $version2, true()) +}; + +declare + %private +function versions:find-version($packages as element(package)*, $minVersion as xs:string?, $maxVersion as xs:string?) { + let $minVersion := if ($minVersion) then $minVersion else "0" + let $maxVersion := if ($maxVersion) then $maxVersion else "9999" + return + versions:find-version($packages, $minVersion, $maxVersion, ()) +}; + +declare + %private +function versions:find-version($packages as element(package)*, $minVersion as xs:string, $maxVersion as xs:string, $newest as element()?) { + if (empty($packages)) then + $newest + else + let $package := head($packages) + let $packageVersion := $package/version | $package/@version + let $newer := + if ( + ( + empty($newest) or + versions:is-newer($packageVersion, ($newest/version, $newest/@version)) + ) and + versions:is-newer($packageVersion, $minVersion) and + versions:is-older($packageVersion, $maxVersion) + ) then + $package + else + $newest + return + versions:find-version(tail($packages), $minVersion, $maxVersion, $newer) +}; + +declare + %private +function versions:is-newer($available as xs:string, $installed as xs:string) as xs:boolean { + let $verInstalled := tokenize($installed, "\.") + let $verAvailable := tokenize($available, "\.") + return + versions:compare-versions( + $verInstalled, + $verAvailable, + function($version1, $version2) { + number($version1) >= number($version2) + } + ) +}; + +declare + %private +function versions:is-older($available as xs:string, $installed as xs:string) as xs:boolean { + let $verInstalled := tokenize($installed, "\.") + let $verAvailable := tokenize($available, "\.") + return + versions:compare-versions( + $verInstalled, + $verAvailable, + function($version1, $version2) { + number($version1) <= number($version2) + } + ) +}; + +declare + %private +function versions:compare-versions($installed as xs:string*, $available as xs:string*, $compare as function(*)) as xs:boolean { + if (empty($installed)) then + exists($available) + else if (empty($available)) then + false() + else if (head($available) = head($installed)) then + if (count($available) = 1 and count($installed) = 1) then + true() + else + versions:compare-versions(tail($installed), tail($available), $compare) + else + $compare(head($available), head($installed)) +}; diff --git a/modules/view.xql b/modules/view.xq similarity index 66% rename from modules/view.xql rename to modules/view.xq index 2022d4d..59f6455 100644 --- a/modules/view.xql +++ b/modules/view.xq @@ -1,17 +1,19 @@ -xquery version "3.0"; +xquery version "3.1"; +import module namespace app="http://exist-db.org/xquery/app" at "app.xqm"; import module namespace config="http://exist-db.org/xquery/apps/config" at "config.xqm"; -import module namespace site="http://exist-db.org/apps/site-utils"; import module namespace templates="http://exist-db.org/xquery/templates"; -import module namespace app="http://exist-db.org/xquery/app" at "app.xql"; -declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization"; +declare namespace site="http://exist-db.org/apps/site-utils"; -declare option exist:timeout "300000"; +declare namespace output="http://www.w3.org/2010/xslt-xquery-serialization"; -declare option output:method "html5"; +declare option output:method "html"; +declare option output:html-version "5"; declare option output:media-type "text/html"; +declare option exist:timeout "300000"; + let $config := map { $templates:CONFIG_APP_ROOT : $config:app-root } @@ -24,4 +26,4 @@ let $lookup := function($functionName as xs:string, $arity as xs:int) { } let $content := request:get-data() return - templates:apply($content, $lookup, (), $config) \ No newline at end of file + templates:apply($content, $lookup, (), $config) diff --git a/packages.html b/packages.html index 0236145..534e02d 100644 --- a/packages.html +++ b/packages.html @@ -1,5 +1,5 @@ -
    +

    Public Application Repository

    @@ -8,9 +8,9 @@

    Public Application Repository

      -
    • +
    -
    \ No newline at end of file +
    diff --git a/post-install.xq b/post-install.xq new file mode 100644 index 0000000..0609874 --- /dev/null +++ b/post-install.xq @@ -0,0 +1,67 @@ +xquery version "3.1"; + +(:~ + : This post-install script sets permissions on the package data collection hierarchy. + : When pre-install creates the public-repo-data collection, its permissions are admin/dba. + : This ensures the collections are owned by the default user and group for the app. + : The script also builds the package metadata if it doesn't already exist. + :) + +import module namespace config="http://exist-db.org/xquery/apps/config" at "modules/config.xqm"; +import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "modules/scan.xqm"; + +declare namespace sm="http://exist-db.org/xquery/securitymanager"; +declare namespace system="http://exist-db.org/xquery/system"; +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; + +(: The following external variables are set by the repo:deploy function :) + +(: file path pointing to the exist installation directory :) +declare variable $home external; +(: path to the directory containing the unpacked .xar package :) +declare variable $dir external; +(: the target collection into which the app is deployed :) +declare variable $target external; + +(: Until https://github.com/eXist-db/exist/issues/3734 is fixed, we hard code the default group name :) +declare variable $repo-group := + (: config:repo-permissions()?group :) + "repo" +; +declare variable $repo-user := + (: config:repo-permissions()?user :) + "repo" +; + +(:~ + : Set user and group to be owner by values in repo.xml + :) +declare function local:set-data-collection-permissions($resource as xs:string) { + if (sm:get-permissions(xs:anyURI($resource))/sm:permission/@group = $repo-group) then + () + else + ( + sm:chown($resource, $repo-user), + sm:chgrp($resource, $repo-group), + sm:chmod(xs:anyURI($resource), "rwxrwxr-x") + ) +}; + +(: Set user and group ownership on the package data collection hierarchy :) + +for $col in ($config:app-data-col, xmldb:get-child-collections($config:app-data-col) ! ($config:app-data-col || "/" || .)) +return + local:set-data-collection-permissions($col), + +(: Build package metadata if missing :) + +if (doc-available($config:raw-packages-doc) and doc-available($config:package-groups-doc)) then + () +else + ( + scanrepo:rebuild-all-package-metadata(), + ($config:raw-packages-doc, $config:package-groups-doc) ! local:set-data-collection-permissions(.) + ), + +(: execute get-package.xq as repo group, so that it can write to logs :) +sm:chmod(xs:anyURI($target || "/modules/get-package.xq"), "rwsrwxr-x") \ No newline at end of file diff --git a/post-install.xql b/post-install.xql deleted file mode 100644 index a9be454..0000000 --- a/post-install.xql +++ /dev/null @@ -1,63 +0,0 @@ -xquery version "3.1"; - -import module namespace scanrepo="http://exist-db.org/xquery/admin/scanrepo" at "modules/scan.xql"; -import module namespace system="http://exist-db.org/xquery/system"; -import module namespace util="http://exist-db.org/xquery/util"; -import module namespace xmldb="http://exist-db.org/xquery/xmldb"; - -(: The following external variables are set by the repo:deploy function :) - -(: file path pointing to the exist installation directory :) -declare variable $home external; -(: path to the directory containing the unpacked .xar package :) -declare variable $dir external; -(: the target collection into which the app is deployed :) -declare variable $target external; - -(: Handle difference between 4.x.x and 5.x.x releases of eXist :) -declare variable $local:copy-collection := - let $fnNew := function-lookup(xs:QName("xmldb:copy-collection"), 2) - return - if (exists($fnNew)) then $fnNew else function-lookup(xs:QName("xmldb:copy"), 2); - -declare function local:mkcol-recursive($collection, $components) { - if (exists($components)) then - let $newColl := concat($collection, "/", $components[1]) - return ( - xmldb:create-collection($collection, $components[1]), - local:mkcol-recursive($newColl, subsequence($components, 2)) - ) - else - () -}; - -(: Helper function to recursively create a collection hierarchy. :) -declare function local:mkcol($collection, $path) { - local:mkcol-recursive($collection, tokenize($path, "/")) -}; - -declare function local:get-repo-dir() { - let $home := system:get-exist-home() - let $pathSep := util:system-property("file.separator") - return - if (doc-available(concat("file:///", $home, "/webapp/repo/packages"))) then - concat($home, "/webapp/repo/packages") - else if(ends-with($home, "WEB-INF")) then - concat(substring-before($home, "WEB-INF"), "/repo/packages") - else - concat($home, $pathSep, "/webapp/repo/packages") -}; - -declare function local:copy-previous-public-from-temp-or-create() { -if (xmldb:collection-available("/db/temp/public")) then - let $copy-dummy := $local:copy-collection("/db/temp/public", $target) - return xmldb:remove("/db/temp/public") -else - local:mkcol($target, "public") -}; - -system:as-user("repo", "repo", ( - local:copy-previous-public-from-temp-or-create(), - xmldb:store-files-from-pattern(concat($target, "/public"), local:get-repo-dir(), "*.xar"), - scanrepo:rebuild-package-meta() -)) \ No newline at end of file diff --git a/pre-install.xq b/pre-install.xq new file mode 100644 index 0000000..07a8ad3 --- /dev/null +++ b/pre-install.xq @@ -0,0 +1,80 @@ +xquery version "3.1"; + +(:~ + : This pre-install script creates the data collection hierarchy which the app + : uses to store packages and package assets. The names of these collections are + : defined in the modules/config.xqm module. Since the pre-install script + : runs before the package has been stored in the database, we read these values + : from the location on disk where the unpacked .xar resides. + :) + +declare namespace xmldb="http://exist-db.org/xquery/xmldb"; + +(: The following external variables are set by the repo:deploy function :) + +(: File path pointing to the exist installation directory :) +declare variable $home external; +(: Path to the directory containing the unpacked .xar package :) +declare variable $dir external; +(: The target collection into which the app is deployed :) +declare variable $target external; + +(: Helper function to recursively create a collection hierarchy :) +declare function local:mkcol-recursive($collection as xs:string, $components as xs:string*) { + if (exists($components)) then + let $newColl := concat($collection, "/", $components[1]) + return ( + xmldb:create-collection($collection, $components[1]), + local:mkcol-recursive($newColl, subsequence($components, 2)) + ) + else + () +}; + +(: Create a collection hierarchy :) +declare function local:mkcol($collection as xs:string, $path as xs:string) { + local:mkcol-recursive($collection, tokenize($path, "/")) +}; + +(: Configuration file for the logs collection :) +declare variable $logs-xconf := + + + + + + + ; + +(: Read the collection names from modules/config.xqm :) + +let $config-module-ns := "http://exist-db.org/xquery/apps/config" +let $config-module-variables := + fn:load-xquery-module( + $config-module-ns, + map { "location-hints": $dir || "/modules/config.xqm" } + )?variables + => map:for-each(function($name, $value) { map:entry(fn:local-name-from-QName($name), $value) }) +let $app-data-parent-col := $config-module-variables?app-data-parent-col +let $app-data-col-name := $config-module-variables?app-data-col-name +let $app-data-col := $config-module-variables?app-data-col +let $icons-col-name := $config-module-variables?icons-col-name +let $metadata-col-name := $config-module-variables?metadata-col-name +let $packages-col-name := $config-module-variables?packages-col-name +let $logs-col-name := $config-module-variables?logs-col-name +let $logs-col := $config-module-variables?logs-col +return + ( + (: Create the data collection hierarchy :) + + xmldb:create-collection($app-data-parent-col, $app-data-col-name), + for $col-name in ($icons-col-name, $metadata-col-name, $packages-col-name, $logs-col-name) + return + xmldb:create-collection($app-data-col, $col-name), + + (: Create log indexes :) + (: Store the collection configuration :) + local:mkcol("/db/system/config", $config-module-variables?logs-col), + xmldb:store("/db/system/config" || $config-module-variables?logs-col, "collection.xconf", $logs-xconf), + xmldb:reindex($config-module-variables?logs-col) + ) \ No newline at end of file diff --git a/pre-install.xql b/pre-install.xql deleted file mode 100644 index 41fa7d6..0000000 --- a/pre-install.xql +++ /dev/null @@ -1,47 +0,0 @@ -xquery version "3.1"; - -import module namespace xmldb="http://exist-db.org/xquery/xmldb"; - -(: The following external variables are set by the repo:deploy function :) - -(: file path pointing to the exist installation directory :) -declare variable $home external; -(: path to the directory containing the unpacked .xar package :) -declare variable $dir external; -(: the target collection into which the app is deployed :) -declare variable $target external; - -(: Handle difference between 4.x.x and 5.x.x releases of eXist :) -declare variable $local:copy-collection := - let $fnNew := function-lookup(xs:QName("xmldb:copy-collection"), 2) - return - if (exists($fnNew)) then $fnNew else function-lookup(xs:QName("xmldb:copy"), 2); - -declare function local:mkcol-recursive($collection, $components) { - if (exists($components)) then - let $newColl := concat($collection, "/", $components[1]) - return ( - xmldb:create-collection($collection, $components[1]), - local:mkcol-recursive($newColl, subsequence($components, 2)) - ) - else - () -}; - -(: Helper function to recursively create a collection hierarchy. :) -declare function local:mkcol($collection, $path) { - local:mkcol-recursive($collection, tokenize($path, "/")) -}; - -declare function local:copy-current-public-to-temp() { -local:mkcol("/db/", "temp"), -if (xmldb:collection-available(concat($target, "/public"))) then - $local:copy-collection(concat($target, "/public"), "/db/temp/") -else - () -}; - -(: store the collection configuration :) -local:mkcol("/db/system/config", $target), -xmldb:store-files-from-pattern(concat("/system/config", $target), $dir, "**/*.xconf", "text/xml", true()), -local:copy-current-public-to-temp() diff --git a/public/collection.xconf.old b/public/collection.xconf.old deleted file mode 100644 index be5c699..0000000 --- a/public/collection.xconf.old +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/repo.xml b/repo.xml index b879ea7..65fb420 100644 --- a/repo.xml +++ b/repo.xml @@ -8,10 +8,17 @@ 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 https://github.com/eXist-db/public-repo/blob/master/modules/upgrade-to-v2-storage.xq before installing v2!
    • +
    • Breaking: With this new version, packages are now stored outside of the public-repo app, to make upgrades of this app possible without a manual backup.
    • +
    • Note: Minimum required version of is eXist 5.0.0.
    • +
    +
    • New: Avoid costly rescanning of all packages when publishing a new package - #51
    • diff --git a/resources/images/ajax-loader.gif b/resources/images/ajax-loader.gif deleted file mode 100644 index 47adbf0..0000000 Binary files a/resources/images/ajax-loader.gif and /dev/null differ diff --git a/resources/images/body-base.gif b/resources/images/body-base.gif deleted file mode 100644 index 4f297e0..0000000 Binary files a/resources/images/body-base.gif and /dev/null differ diff --git a/resources/images/body.gif b/resources/images/body.gif deleted file mode 100644 index 91c9a3d..0000000 Binary files a/resources/images/body.gif and /dev/null differ diff --git a/resources/images/close.png b/resources/images/close.png deleted file mode 100644 index 8319264..0000000 Binary files a/resources/images/close.png and /dev/null differ diff --git a/resources/images/download.png b/resources/images/download.png deleted file mode 100644 index 4477b45..0000000 Binary files a/resources/images/download.png and /dev/null differ diff --git a/resources/images/grey-box-bot.gif b/resources/images/grey-box-bot.gif deleted file mode 100644 index 995a544..0000000 Binary files a/resources/images/grey-box-bot.gif and /dev/null differ diff --git a/resources/images/grey-box-rpt.gif b/resources/images/grey-box-rpt.gif deleted file mode 100644 index 4e17ced..0000000 Binary files a/resources/images/grey-box-rpt.gif and /dev/null differ diff --git a/resources/images/grey-box-top.gif b/resources/images/grey-box-top.gif deleted file mode 100644 index 20bc464..0000000 Binary files a/resources/images/grey-box-top.gif and /dev/null differ diff --git a/resources/images/header.gif b/resources/images/header.gif deleted file mode 100644 index 3f21eca..0000000 Binary files a/resources/images/header.gif and /dev/null differ diff --git a/resources/images/horizontal.gif b/resources/images/horizontal.gif deleted file mode 100644 index 970d0e5..0000000 Binary files a/resources/images/horizontal.gif and /dev/null differ diff --git a/resources/images/install.png b/resources/images/install.png deleted file mode 100644 index 02ef397..0000000 Binary files a/resources/images/install.png and /dev/null differ diff --git a/resources/images/library2.gif b/resources/images/library.gif similarity index 100% rename from resources/images/library2.gif rename to resources/images/library.gif diff --git a/resources/images/nav-dropdown.gif b/resources/images/nav-dropdown.gif deleted file mode 100644 index 0244f13..0000000 Binary files a/resources/images/nav-dropdown.gif and /dev/null differ diff --git a/resources/images/nav-dropdown.png b/resources/images/nav-dropdown.png deleted file mode 100644 index fd1e443..0000000 Binary files a/resources/images/nav-dropdown.png and /dev/null differ diff --git a/resources/images/nav.gif b/resources/images/nav.gif deleted file mode 100644 index 2f756cf..0000000 Binary files a/resources/images/nav.gif and /dev/null differ diff --git a/resources/images/plugin2.gif b/resources/images/plugin.gif similarity index 100% rename from resources/images/plugin2.gif rename to resources/images/plugin.gif diff --git a/retrieve.html b/retrieve.html deleted file mode 100644 index e4cd8ba..0000000 --- a/retrieve.html +++ /dev/null @@ -1,4 +0,0 @@ - -
        -
      • -
      \ No newline at end of file diff --git a/templates/page-inner.html b/templates/page-inner.html new file mode 100644 index 0000000..85c557a --- /dev/null +++ b/templates/page-inner.html @@ -0,0 +1,169 @@ + + + + eXist-db Public App Repository + + + + + +