Skip to content

Commit 1ef9b89

Browse files
committed
Add a facility for modifying the properties of Documents and Collections
Closes #10
1 parent 61147e7 commit 1ef9b89

File tree

6 files changed

+542
-3
lines changed

6 files changed

+542
-3
lines changed

src/main/xar-resources/api.xqm

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import module namespace doc = "http://fusiondb.com/ns/studio/api/document" at "m
3333
import module namespace exp = "http://fusiondb.com/ns/studio/api/explorer" at "modules/explorer.xqm";
3434
import module namespace hsc = "https://tools.ietf.org/html/rfc2616#section-10" at "modules/http-status-codes.xqm";
3535
import module namespace idx = "http://fusiondb.com/ns/studio/api/index" at "modules/index.xqm";
36-
import module namespace jx = "http://joewiz.org/ns/xquery/json-xml" at "modules/json-xml.xqm";
3736
import module namespace mul = "http://fusiondb.com/ns/studio/api/multipart" at "modules/multipart.xqm";
3837
import module namespace perr = "http://fusiondb.com/ns/studio/api/error" at "modules/error.xqm";
3938
import module namespace prxq = "http://fusiondb.com/ns/studio/api/restxq" at "modules/restxq.xqm";
@@ -232,6 +231,52 @@ function api:put-document($uri, $copy-source, $move-source, $media-type, $body)
232231
})
233232
};
234233

234+
declare
235+
%rest:POST("{$body}")
236+
%rest:path("/fusiondb/document")
237+
%rest:query-param("uri", "{$uri}")
238+
%rest:consumes("application/json")
239+
%rest:produces("application/json")
240+
%output:method("json")
241+
function api:update-document-properties($uri, $body) {
242+
api:with-valid-uri-ex($uri, function($uri) {
243+
if (empty($body))
244+
then
245+
[
246+
map {
247+
"code": $hsc:bad-request,
248+
"reason": "Missing request body"
249+
},
250+
()
251+
]
252+
else
253+
(: TODO explicit xs:string cast below is required due to
254+
a Type error in eXist-db that needs investigating...
255+
UntypedValueCheck.java for $body has atomize=false,
256+
but should be atomize=true.
257+
:)
258+
let $json-txt := util:base64-decode($body cast as xs:string)
259+
let $document-properties := fn:parse-json($json-txt)
260+
return
261+
try {
262+
[
263+
map {
264+
"code": if (doc:update-properties($uri, $document-properties)) then $hsc:no-content else $hsc:not-found
265+
},
266+
()
267+
]
268+
} catch perr:PD001 {
269+
[
270+
map {
271+
"code": $hsc:forbidden,
272+
"reason": $err:description
273+
},
274+
()
275+
]
276+
}
277+
})
278+
};
279+
235280
declare
236281
%rest:DELETE
237282
%rest:path("/fusiondb/document")
@@ -322,6 +367,52 @@ function api:put-collection($uri, $copy-source, $move-source) {
322367
})
323368
};
324369

370+
declare
371+
%rest:POST("{$body}")
372+
%rest:path("/fusiondb/collection")
373+
%rest:query-param("uri", "{$uri}")
374+
%rest:consumes("application/json")
375+
%rest:produces("application/json")
376+
%output:method("json")
377+
function api:update-collection-properties($uri, $body) {
378+
api:with-valid-uri-ex($uri, function($uri) {
379+
if (empty($body))
380+
then
381+
[
382+
map {
383+
"code": $hsc:bad-request,
384+
"reason": "Missing request body"
385+
},
386+
()
387+
]
388+
else
389+
(: TODO explicit xs:string cast below is required due to
390+
a Type error in eXist-db that needs investigating...
391+
UntypedValueCheck.java for $body has atomize=false,
392+
but should be atomize=true.
393+
:)
394+
let $json-txt := util:base64-decode($body cast as xs:string)
395+
let $collection-properties := fn:parse-json($json-txt)
396+
return
397+
try {
398+
[
399+
map {
400+
"code": if (col:update-properties($uri, $collection-properties)) then $hsc:no-content else $hsc:not-found
401+
},
402+
()
403+
]
404+
} catch perr:PD001 {
405+
[
406+
map {
407+
"code": $hsc:forbidden,
408+
"reason": $err:description
409+
},
410+
()
411+
]
412+
}
413+
})
414+
};
415+
325416
declare
326417
%rest:DELETE
327418
%rest:path("/fusiondb/collection")

src/main/xar-resources/modules/collection.xqm

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,55 @@ declare function col:put($uri as xs:string) as xs:string {
3737
perr:error($perr:PD001, $uri)
3838
};
3939

40+
41+
declare function col:update-properties($uri as xs:string, $collection-properties as map(xs:string, item())) as xs:boolean {
42+
if (xmldb:collection-available($uri)) then
43+
if (sm:has-access(xs:anyURI($uri), "r--")
44+
and ut:is-current-user(sm:get-permissions(xs:anyURI($uri))/sm:permission/@owner))
45+
then
46+
(: update collection properties :)
47+
(
48+
let $_ :=
49+
if ($collection-properties?mode)
50+
then
51+
sm:chmod(xs:anyURI($uri), $collection-properties?mode)
52+
else()
53+
let $_ :=
54+
if ($collection-properties?acl)
55+
then
56+
let $_ := sm:clear-acl(xs:anyURI($uri))
57+
let $_ := array:for-each($collection-properties?acl, function($ace) {
58+
let $f := if ($ace?target eq "USER")
59+
then
60+
sm:add-user-ace#4
61+
else
62+
sm:add-group-ace#4
63+
return
64+
$f(xs:anyURI($uri), $ace?who, $ace?accessType eq "ALLOWED", $ace?mode)
65+
66+
})
67+
return
68+
()
69+
else()
70+
let $_ :=
71+
if ($collection-properties?group)
72+
then
73+
sm:chgrp(xs:anyURI($uri), $collection-properties?group)
74+
else()
75+
let $_ :=
76+
if ($collection-properties?owner)
77+
then
78+
sm:chown(xs:anyURI($uri), $collection-properties?owner)
79+
else()
80+
return
81+
true()
82+
)
83+
else
84+
perr:error($perr:PD001, $uri)
85+
else
86+
false()
87+
};
88+
4089
declare function col:delete($uri as xs:string) as xs:boolean {
4190
if (xmldb:collection-available($uri))
4291
then

src/main/xar-resources/modules/document.xqm

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,7 @@ declare
7777
%private
7878
function doc:store($collection-uri as xs:string, $doc-name as xs:string, $content) {
7979
if (sm:has-access(xs:anyURI($collection-uri), "w")) then
80-
let $_ := util:log("INFO", ("ABOUT TO STORE=" || $collection-uri || "/" || $doc-name)) return
81-
80+
8281
(: NOTE we don't explicitly specify the media-type from the
8382
request here, instead we let eXist-db figure it out :)
8483

@@ -104,6 +103,63 @@ function doc:store($collection-uri as xs:string, $doc-name as xs:string, $conten
104103
perr:error($perr:PD001, $collection-uri || "/" || $doc-name)
105104
};
106105

106+
declare function doc:update-properties($uri as xs:string, $document-properties as map(xs:string, item())) as xs:boolean {
107+
if (ut:doc-available($uri)) then
108+
let $collection-uri := ut:parent-path($uri)
109+
let $doc-name := ut:last-path-component($uri)
110+
return
111+
if (sm:has-access(xs:anyURI($uri), "r--")
112+
and sm:has-access(xs:anyURI($collection-uri), "r-x")
113+
and ut:is-current-user(sm:get-permissions(xs:anyURI($uri))/sm:permission/@owner))
114+
then
115+
(: update document properties :)
116+
(
117+
let $_ :=
118+
if ($document-properties?mediaType)
119+
then
120+
xmldb:set-mime-type(xs:anyURI($uri), $document-properties?mediaType)
121+
else()
122+
let $_ :=
123+
if ($document-properties?mode)
124+
then
125+
sm:chmod(xs:anyURI($uri), $document-properties?mode)
126+
else()
127+
let $_ :=
128+
if ($document-properties?acl)
129+
then
130+
let $_ := sm:clear-acl(xs:anyURI($uri))
131+
let $_ := array:for-each($document-properties?acl, function($ace) {
132+
let $f := if ($ace?target eq "USER")
133+
then
134+
sm:add-user-ace#4
135+
else
136+
sm:add-group-ace#4
137+
return
138+
$f(xs:anyURI($uri), $ace?who, $ace?accessType eq "ALLOWED", $ace?mode)
139+
140+
})
141+
return
142+
()
143+
else()
144+
let $_ :=
145+
if ($document-properties?group)
146+
then
147+
sm:chgrp(xs:anyURI($uri), $document-properties?group)
148+
else()
149+
let $_ :=
150+
if ($document-properties?owner)
151+
then
152+
sm:chown(xs:anyURI($uri), $document-properties?owner)
153+
else()
154+
return
155+
true()
156+
)
157+
else
158+
perr:error($perr:PD001, $uri)
159+
else
160+
false()
161+
};
162+
107163
declare function doc:delete($uri as xs:string) as xs:boolean {
108164
if (ut:doc-available($uri)) then
109165
let $collection-uri := ut:parent-path($uri)

src/test/java/com/fusiondb/studio/api/API.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,13 @@ static boolean testServerHasBadJsonSerialization() {
166166
|| dockerTestImage.endsWith("existdb:5.0.0")
167167
|| dockerTestImage.endsWith("existdb:5.2.0");
168168
}
169+
170+
static boolean testServerHasBadXmldbSetMimeType() {
171+
final String dockerTestImage = envVarOrDefault(ENV_VAR_DOCKER_TEST_IMAGE, null, envVarValue -> envVarValue);
172+
if (dockerTestImage == null || dockerTestImage.isEmpty()) {
173+
return false;
174+
}
175+
176+
return dockerTestImage.endsWith("fusiondb-server:1.0.0-ALPHA2");
177+
}
169178
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Fusion Studio API - API for Fusion Studio
3+
* Copyright © 2017 Evolved Binary ([email protected])
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package com.fusiondb.studio.api;
19+
20+
import io.restassured.response.ExtractableResponse;
21+
import io.restassured.response.Response;
22+
import org.junit.jupiter.api.Test;
23+
24+
import java.util.Map;
25+
26+
import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
27+
import static com.fusiondb.studio.api.API.*;
28+
import static io.restassured.RestAssured.given;
29+
import static io.restassured.http.ContentType.JSON;
30+
import static io.restassured.http.ContentType.XML;
31+
import static io.restassured.internal.RestAssuredResponseOptionsGroovyImpl.BINARY;
32+
import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath;
33+
import static java.nio.charset.StandardCharsets.UTF_8;
34+
import static org.apache.http.HttpStatus.*;
35+
import static org.junit.jupiter.api.Assertions.*;
36+
37+
public class CollectionIT {
38+
39+
@Test
40+
public void createCollection() {
41+
final String colPath = "/db/fusion-studio-api-test-document-it-col-1";
42+
final ExtractableResponse<Response> collectionResponse = createCollection(colPath);
43+
assertEquals(colPath, collectionResponse.jsonPath().getString("uri"));
44+
}
45+
46+
@Test
47+
public void updateOwnerAndGroup() {
48+
final String collectionPath = "/db/fusion-studio-api-test-document-it-col-2";
49+
50+
// 1. create the collection
51+
ExtractableResponse<Response> collectionResponse = createCollection(collectionPath);
52+
assertEquals(collectionPath, collectionResponse.jsonPath().getString("uri"));
53+
assertEquals(DEFAULT_ADMIN_USERNAME, collectionResponse.jsonPath().getString("owner"));
54+
assertEquals("dba", collectionResponse.jsonPath().getString("group"));
55+
56+
// 2. update the collection owner and group
57+
final Map<String, Object> requestBody = mapOf(
58+
Tuple("owner", "guest"),
59+
Tuple("group", "guest")
60+
);
61+
given().
62+
auth().preemptive().basic(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD).
63+
contentType(JSON).
64+
body(requestBody).
65+
when().
66+
post(getApiBaseUri() + "/collection?uri=" + collectionPath).
67+
then().
68+
statusCode(SC_NO_CONTENT);
69+
70+
// 3. get the collection properties
71+
collectionResponse = given().
72+
auth().preemptive().basic(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD).
73+
when().
74+
get(getApiBaseUri() + "/explorer?uri=" + collectionPath).
75+
then().
76+
statusCode(SC_OK).
77+
assertThat().
78+
body(matchesJsonSchemaInClasspath("collection-schema.json")).
79+
extract();
80+
assertEquals("guest", collectionResponse.jsonPath().getString("owner"));
81+
assertEquals("guest", collectionResponse.jsonPath().getString("group"));
82+
}
83+
84+
@Test
85+
public void updateMode() {
86+
final String collectionPath = "/db/fusion-studio-api-test-document-it-col-3";
87+
88+
// 1. create the collection
89+
ExtractableResponse<Response> collectionResponse = createCollection(collectionPath);
90+
assertEquals(collectionPath, collectionResponse.jsonPath().getString("uri"));
91+
assertEquals("rwxr-xr-x", collectionResponse.jsonPath().getString("mode"));
92+
93+
// 2. update the collection mode
94+
final Map<String, Object> requestBody = mapOf(
95+
Tuple("mode", "rwxr-----")
96+
);
97+
given().
98+
auth().preemptive().basic(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD).
99+
contentType(JSON).
100+
body(requestBody).
101+
when().
102+
post(getApiBaseUri() + "/collection?uri=" + collectionPath).
103+
then().
104+
statusCode(SC_NO_CONTENT);
105+
106+
// 3. get the collection properties
107+
collectionResponse = given().
108+
auth().preemptive().basic(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD).
109+
when().
110+
get(getApiBaseUri() + "/explorer?uri=" + collectionPath).
111+
then().
112+
statusCode(SC_OK).
113+
assertThat().
114+
body(matchesJsonSchemaInClasspath("collection-schema.json")).
115+
extract();
116+
assertEquals("rwxr-----", collectionResponse.jsonPath().getString("mode"));
117+
}
118+
119+
private ExtractableResponse<Response> createCollection(final String path) {
120+
return
121+
given().
122+
auth().preemptive().basic(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_PASSWORD).
123+
when().
124+
put(getApiBaseUri() + "/collection?uri=" + path).
125+
then().
126+
statusCode(SC_CREATED).
127+
assertThat().
128+
body(matchesJsonSchemaInClasspath("collection-schema.json")).
129+
extract();
130+
}
131+
}

0 commit comments

Comments
 (0)