teacup/products.xqm

360 lines
15 KiB
Plaintext

(:~
: Taxonomies functions.
:
: @author Rave Technologies, https://www.rave-tech.com/, 2017
:)
module namespace blproduct = 'http://www.rave-tech.com/bloomsbury/product';
import module namespace config = 'http://www.rave-tech.com/bloomsbury/config' at './module/config.xqm';
import module namespace audit = 'http://www.rave-tech.com/bloomsbury/audit' at './module/manage-audit.xqm';
import module namespace schematron = "http://github.com/Schematron/schematron-basex";
declare namespace product = 'http://cms.bloomsbury.com/product-manifest';
(:~
: Add Product into the system.
: @param $body Product XML as document node <product>....</<product>
: @header $authorization Authorization key
: @return element(result)
:)
declare
%updating
%rest:path("/products")
%rest:POST("{$body}")
%rest:header-param("Authorization", "{$authorization}", "none")
%rest:consumes("application/xml", "text/xml")
function blproduct:add(
$body as document-node(),
$authorization as xs:string
)
{
config:session-check($authorization),
try
{
let $productID := if($body/product:product/@id/string()!='')
then $body/product:product/@id/string()
else fn:concat($config:ProductIDPrefix,fn:string(convert:dateTime-to-integer(fn:adjust-dateTime-to-timezone(convert:integer-to-dateTime(prof:current-ms())))))
let $body := if($body/product:product/@id/string()!='')
then $body
else
copy $c := $body
modify replace node $c/product:product/@id with (attribute{'id'}{$productID})
return $c
let $name := $body/product:product/@name/string()
let $targetUri := fn:concat($config:ProductDir,$productID,$config:LatestDir,$productID,'.xml')
return
(:Step 1: Check if Product is already exist :)
if(fn:doc-available(fn:concat($config:CoreDatabase,$targetUri)))
then update:output(<result><status>Failure</status><message>Product is already available</message></result>)
else blproduct:validate-and-add-product($body,$productID,$name,$targetUri,'Product Add',config:session-value($authorization))
}
catch *
{
admin:write-log("[Product Add][Error: " || $err:description || "]"),
admin:write-log("[Product Add][Error: " || $err:additional || "]"),
update:output(<result><status>Error</status><message>Error generated. Please check system log</message></result>)
}
};
(:~
: Update Product.
: @param $body Product XML as document node <product>....</product>
: @header $authorization Authorization key
: @return element(result)
:)
declare
%updating
%rest:path("/products/{$productID=.+}")
%rest:PUT("{$body}")
%rest:header-param("Authorization", "{$authorization}", "none")
%rest:consumes("application/xml", "text/xml")
function blproduct:update(
$body as document-node(),
$productID as xs:string,
$authorization as xs:string
)
{
config:session-check($authorization),
try
{
let $productID := $body/product:product/@id/string()
let $name := $body/product:product/@name/string()
let $targetUri := fn:concat($config:ProductDir,$productID,$config:LatestDir,$productID,'.xml')
return
(:Step 1: Check if Product is already exist :)
if(fn:doc-available(fn:concat($config:CoreDatabase ,$targetUri)))
then blproduct:validate-and-add-product($body,$productID,$name,$targetUri,'Product Update',config:session-value($authorization))
else update:output(<result><status>Failure</status><message>Product is not available to update</message></result>)
}
catch *
{
admin:write-log("[Product Update][Error: " || $err:description || "]"),
admin:write-log("[Product Update][Error: " || $err:additional || "]"),
update:output(<result><status>Error</status><message>Error generated. Please check system log</message></result>)
}
};
(:~
: Display Product specific detail.
: @param $productID ID of the product
: @header $authorization Authorization key
: @return element(result)
:)
declare
%rest:path("/products/{$productID=.+}")
%rest:GET
%rest:header-param("Authorization", "{$authorization}", "none")
function blproduct:info(
$productID as xs:string?,
$authorization as xs:string
)
{
config:session-check($authorization),
try
{
let $targetUri := fn:concat($config:ProductDir,$productID,$config:LatestDir,$productID,'.xml')
let $productXml := db:open($config:CoreDatabase,$targetUri)
return
if($productXml)
then <result><status>Success</status>{$productXml}</result>
else <result><status>Failure</status><message>Product is unavailable</message></result>
}
catch *
{
admin:write-log("[Product Info][Error: " || $err:description || "]"),
admin:write-log("[Product Info][Error: " || $err:additional || "]"),
<result><status>Error</status><message>Error generated. Please check system log</message></result>
}
};
(:~
: To get specific or list of available product.
: @param $query Name of the product to filter
: @param $page Page number of the list optional
: @param $size Total number of records to display in a page optional
: @header $authorization Authorization key
: @return element(result)
:)
declare
%rest:path("/products")
%rest:GET
%rest:query-param("q", "{$query}")
%rest:query-param("user", "{$user}")
%rest:query-param("page", "{$page}")
%rest:query-param("size", "{$size}")
%rest:header-param("Authorization", "{$authorization}", "none")
function blproduct:list(
$query as xs:string?,
$user as xs:string?,
$page as xs:integer?,
$size as xs:integer?,
$authorization as xs:string
)
{
config:session-check($authorization),
try
{
let $page := if($page) then $page else $config:page
let $size := if($size) then $size else $config:size
let $productRecords := <products>{
for $eachProduct in db:open($config:CoreDatabase ,$config:ProductDir)/product:product[if($user) then ((*:managers/*:user/@id/string()=$user) or (db:open($config:CoreDatabase,$config:UserDir)/user[role='admin'][id=$user]) or (@createdBy/string()=$user)) else .]
[fn:contains(fn:base-uri(.),$config:LatestDir)]
[fn:not(fn:matches(fn:base-uri(.),$config:VersionDir))]
[fn:not(@delete)]
[if($query) then (matches(@name,$query,'i')) else .]
let $id := $eachProduct/@id/string()
let $name := $eachProduct/@name/string()
let $taxonomyRef := $eachProduct/@taxonomyRef/string()
let $pipelineRef := $eachProduct/@pipelineRef/string()
let $description := $eachProduct/@pipelineRef/string()
let $delete := $eachProduct/@delete/string()
order by $name
return <product xmlns="http://cms.bloomsbury.com/product-manifest" id="{$id}" name="{$name}" taxonomyRef="{$taxonomyRef}" pipelineRef="{$pipelineRef}" description="{$description}" delete="{$delete}"></product>
}</products>
let $countRecord := count($productRecords/*:product)
let $start := if($page eq 1)
then $page
else if($page gt 1)
then fn:sum(($page * $size)+1) - $size
else fn:sum(($page * $size)) - $size
let $end := if($page eq 1)
then $size
else if($page gt 1)
then fn:sum(($start + $size)-1)
else fn:sum($start + $size)
return
if($productRecords/*:product)
then <result><status>Success</status><total>{$countRecord}</total><entities>{$productRecords/*:product[position() = $start to $end]}</entities></result>
else <result><status>Success</status><message>Product is unavailable</message></result>
}
catch *
{
admin:write-log("[Product List][Error: " || $err:description || "]"),
admin:write-log("[Product List][Error: " || $err:additional || "]"),
<result><status>Error</status><message>Error generated. Please check system log</message></result>
}
};
(:~
: Soft Delete product.
: @param $productID ID of the product to delete
: @header $authorization Authorization key
: @return string message
:)
declare
%updating
%rest:path("/products/{$productID=.+}")
%rest:DELETE
%rest:header-param("Authorization", "{$authorization}", "none")
function blproduct:delete(
$productID as xs:string,
$authorization as xs:string
)
{
(: TODO: we added @delete into root element however it is not valid as per RELAX NG :)
config:session-check($authorization),
let $productXml := db:open($config:CoreDatabase,$config:ProductDir)/product:product[@id=$productID]
[fn:contains(base-uri(.),$config:LatestDir)]
return
if($productXml)
then
let $config:ProductUri := fn:concat($config:ProductDir,$productID,$config:LatestDir,$productID,'.xml')
return
(
insert node (attribute{'delete'}{'Yes'}) into db:open($config:CoreDatabase,$config:ProductUri)/product:product,
audit:product($productID,'Product Delete',config:session-value($authorization)),
update:output(<result><status>Success</status><message>Product Deleted</message></result>)
)
else update:output(<result><status>Failure</status><message>Product is unavailable</message></result>)
};
(:~
: Add or update products.
: @param $productXml Product XML content
: @param $productID ID of the product
: @param $name Name of the product
: @param $targetUri The saving path of the product
: @param $messageFlag The action of product (add or update)
: @param $sessionValue Session Value
: @return element(result)
:)
declare %private %updating function blproduct:validate-and-add-product(
$productXml as document-node(),
$productID as xs:string,
$name as xs:string,
$targetUri as xs:string,
$messageFlag as xs:string,
$sessionValue as xs:string
)
{
(:Validate as per Relax NG :)
let $reportChunk := <report>
<info>
<id>{$productID}</id>
<name>{$name}</name>
<submitted-on>{fn:adjust-dateTime-to-timezone(convert:integer-to-dateTime(prof:current-ms()))}</submitted-on>
<requester-id>{fn:substring-before($sessionValue,'$$$$')}</requester-id>
<requester-email>{fn:substring-after($sessionValue,'$$$$')}</requester-email>
</info>
<ingestion-report>
<steps/>
</ingestion-report>
</report>
let $startTime := fn:adjust-dateTime-to-timezone(convert:integer-to-dateTime(prof:current-ms()))
let $validate := <validate><status>valid</status></validate>
(: TODO: skipped it to ingest invalid file :)
(:validate:rng-report($productXml, bin:decode-string(db:retrieve($config:Database, fn:concat($config:ValidationDir,$config:ProductRelaxNG)),'UTF-8'), fn:true()):)
return
if($validate/status='valid')
then
(
config:update-message("[" || $messageFlag || "][Product is valid as per Relax NG : " || $productID || "]")
,
let $reportUri := fn:concat($config:ProductDir,$productID,'/','report.xml')
let $appendReport := let $updateReport := <step>
<type>RELAX NG Validation</type>
<started-on>{$startTime}</started-on>
<completed-on>{fn:adjust-dateTime-to-timezone(convert:integer-to-dateTime(prof:current-ms()))}</completed-on>
<status>Pass</status>
<error></error>
</step>
return
copy $target := $reportChunk
modify insert node $updateReport as last into $target/ingestion-report/steps
return $target
return
(
if($messageFlag='Product Update') then blproduct:create-version($productID) else(),
db:replace($config:CoreDatabase,$targetUri,$productXml),
db:replace($config:CoreDatabase,$reportUri,$appendReport),
audit:product($productID,$messageFlag,$sessionValue),
update:output(<result><status>Success</status><message>{$messageFlag}</message></result>)
)
)
else
(
let $appendReport := let $updateReport := <step>
<type>RELAX NG Validation</type>
<started-on>{$startTime}</started-on>
<completed-on>{fn:adjust-dateTime-to-timezone(convert:integer-to-dateTime(prof:current-ms()))}</completed-on>
<status>Fail</status>
<error>{$validate}</error>
</step>
return
copy $target := $reportChunk
modify
(
insert node $updateReport as last into $target/ingestion-report/steps,
insert node <finished-on>{fn:adjust-dateTime-to-timezone(convert:integer-to-dateTime(prof:current-ms()))}</finished-on> as last into $target/ingestion-report
)
return $target
let $reportUri := fn:concat($config:ProductDir,$productID,'/','report.xml')
return db:replace($config:CoreDatabase,$reportUri,$appendReport)
,
update:output(<result><status>Failure</status><message>Product is invalid as per Relax NG. Error report generated</message></result>)
)
};
(:~
: Maintain history of the product.
: @param $productID ID of the product
: @return empty sequence
:)
declare %private %updating function blproduct:create-version(
$productID as xs:string
)
{
let $versionFileName := let $tstamp := fn:current-dateTime()
let $suffix := fn:format-dateTime($tstamp, "[Y1,4][M01][D01][H01][m01][s01][f01]")
return fn:concat($productID,'_version_',$suffix,'.xml')
let $targetUri := fn:concat($config:ProductDir,$productID,$config:LatestDir,$productID,'.xml')
let $versionUri := fn:replace(fn:substring-before($targetUri,fn:tokenize($targetUri,'/')[last()]),$config:LatestDir,$config:VersionDir)
return
(
db:replace($config:CoreDatabase, fn:concat($versionUri,$versionFileName), db:open($config:CoreDatabase,$targetUri))
,
config:update-message("[Product Update][Product version is created : " || $productID || "]")
,
let $versionControlUri := fn:concat($config:ProductDir,$productID,'/',$config:VersionControlFileName)
return
if(fn:doc-available(fn:concat('/',$config:CoreDatabase ,$versionControlUri)))
then
let $versionDoc := db:open($config:CoreDatabase,$versionControlUri)
let $nextVersion := fn:sum(max($versionDoc/versions/version/@number) + 1)
return
(
insert node <version number="{$nextVersion}" uri="{fn:concat($versionUri,$versionFileName)}" on="{fn:adjust-dateTime-to-timezone(fn:current-dateTime())}"/> into db:open($config:CoreDatabase,$versionControlUri)/versions,
config:update-message("[Product Control][Product Control updated : " || $productID || "]")
)
else
db:replace($config:CoreDatabase,$versionControlUri,<versions><version number="1" uri="{fn:concat($versionUri,$versionFileName)}" on="{fn:adjust-dateTime-to-timezone(fn:current-dateTime())}"/></versions>)
)
};