Skip to content

Commit 32a100b

Browse files
authored
#256 Add sandbox functionality (#257)
1 parent 228d7a9 commit 32a100b

File tree

12 files changed

+974
-74
lines changed

12 files changed

+974
-74
lines changed

build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ apply plugin: "org.grails.grails-gsp"
4040
apply plugin: "com.bertramlabs.asset-pipeline"
4141

4242

43-
def alaSecurityLibsVersion='6.2.0'
43+
def alaSecurityLibsVersion='6.3.0'
4444
def geotoolsVersion='27.2'
4545

4646
war {
@@ -212,7 +212,7 @@ dependencies {
212212

213213
// developmentOnly 'io.methvin:directory-watcher:0.15.0'
214214

215-
215+
implementation 'org.gbif:dwc-api:1.47'
216216
}
217217

218218
configurations {

grails-app/conf/application.yml

+9-1
Original file line numberDiff line numberDiff line change
@@ -468,4 +468,12 @@ openapi:
468468
cachetimeoutms: 4000
469469

470470
# Allow setting a fixed locale to prevent number formatting issues: https://github.com/AtlasOfLivingAustralia/spatial-service/issues/247
471-
#useFixedLocale: en
471+
#useFixedLocale: en
472+
473+
# Internal sandbox service for processing uploaded CSV files
474+
sandboxEnabled: true
475+
sandboxSolrUrl: http://localhost:8983/solr
476+
sandboxSolrCollection: sandbox
477+
sandboxThreadCount: 2
478+
pipelinesCmd: "java -Dspark.local.dir=/data/spatial-data/sandbox/tmp -Djava.io.tmpdir=/data/spatial-data/sandbox/tmp -Dlog4j.configuration=file:/data/spatial-data/modelling/la-pipelines/log4j.properties -cp /data/spatial-data/modelling/la-pipelines/pipelines-2.19.0-SNAPSHOT-shaded.jar"
479+
pipelinesConfig: "--config=/data/spatial-data/modelling/la-pipelines/la-pipelines.yaml"

grails-app/controllers/au/org/ala/spatial/LayerController.groovy

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class LayerController {
9696
def img(String id) {
9797
if (layerService.getLayerByName(id)) {
9898
File f = new File(spatialConfig.data.dir + '/public/thumbnail/' + id + '.jpg')
99-
render(file: f, fileName: "${id}.jpg")
99+
render(file: f, fileName: "${id}.jpg", contentType: "image/jpg")
100100
} else {
101101
response.sendError(404, "$id not found")
102102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
/*
2+
* Copyright (C) 2016 Atlas of Living Australia
3+
* All Rights Reserved.
4+
*
5+
* The contents of this file are subject to the Mozilla Public
6+
* License Version 1.1 (the "License"); you may not use this file
7+
* except in compliance with the License. You may obtain a copy of
8+
* the License at http://www.mozilla.org/MPL/
9+
*
10+
* Software distributed under the License is distributed on an "AS
11+
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
12+
* implied. See the License for the specific language governing
13+
* rights and limitations under the License.
14+
*/
15+
16+
package au.org.ala.spatial
17+
18+
import au.ala.org.ws.security.RequireApiKey
19+
import au.org.ala.plugins.openapi.Path
20+
import au.org.ala.spatial.dto.SandboxIngress
21+
import au.org.ala.web.AuthService
22+
import grails.converters.JSON
23+
import io.swagger.v3.oas.annotations.Operation
24+
import io.swagger.v3.oas.annotations.Parameter
25+
import io.swagger.v3.oas.annotations.media.Content
26+
import io.swagger.v3.oas.annotations.media.Schema
27+
import io.swagger.v3.oas.annotations.parameters.RequestBody
28+
import io.swagger.v3.oas.annotations.responses.ApiResponse
29+
import io.swagger.v3.oas.annotations.security.SecurityRequirement
30+
import org.springframework.web.multipart.MultipartFile
31+
32+
import javax.ws.rs.Produces
33+
34+
import static io.swagger.v3.oas.annotations.enums.ParameterIn.QUERY
35+
36+
class SandboxController {
37+
38+
SpatialObjectsService spatialObjectsService
39+
40+
SpatialConfig spatialConfig
41+
def dataSource
42+
def sandboxService
43+
AuthService authService
44+
45+
@Operation(
46+
method = "POST",
47+
tags = "uploads",
48+
operationId = "uploadCSV",
49+
summary = "Upload a CSV or zipped CSV file containing occurrence points",
50+
parameters = [
51+
@Parameter(
52+
name = "name",
53+
in = QUERY,
54+
description = "datasetName",
55+
schema = @Schema(implementation = String),
56+
required = true
57+
)
58+
],
59+
requestBody = @RequestBody(
60+
description = "Uploaded CSV or zipped CSV file",
61+
content = @Content(
62+
mediaType = 'application/zip',
63+
schema = @Schema(
64+
type = "string",
65+
format = "binary"
66+
)
67+
)
68+
),
69+
responses = [
70+
@ApiResponse(
71+
description = "Recognised header and a dataResourceUid",
72+
responseCode = "200",
73+
content = [
74+
@Content(
75+
mediaType = "application/json"
76+
)
77+
]
78+
)
79+
],
80+
security = [@SecurityRequirement(name = 'openIdConnect')]
81+
)
82+
@Path("/sandbox/upload")
83+
@Produces("application/json")
84+
@RequireApiKey
85+
def upload() {
86+
if (!spatialConfig.sandboxEnabled) {
87+
return [error: "Sandbox is disabled"] as JSON
88+
}
89+
90+
// Use linked hash map to maintain key ordering
91+
Map<Object, Object> retMap = new LinkedHashMap<Object, Object>()
92+
93+
// Parse the request
94+
Map<String, MultipartFile> items = request.getFileMap()
95+
96+
if (items.size() == 1) {
97+
MultipartFile fileItem = items.values()[0]
98+
99+
SandboxIngress info = sandboxService.upload(fileItem, (String) params.name, authService.getUserId())
100+
if (info) {
101+
render info as JSON
102+
return
103+
} else {
104+
retMap.put("error", "Failed to upload file")
105+
}
106+
} else {
107+
retMap.put("error", items.size() + " files sent in request. A single zipped CSV file should be supplied.")
108+
}
109+
110+
render retMap as JSON
111+
}
112+
113+
// delete service
114+
@Operation(
115+
method = "DELETE",
116+
tags = "uploads",
117+
operationId = "deleteCSV",
118+
summary = "Delete a CSV or zipped CSV file containing occurrence points",
119+
parameters = [
120+
@Parameter(
121+
name = "id",
122+
in = QUERY,
123+
description = "datasetId",
124+
schema = @Schema(implementation = String),
125+
required = true
126+
)
127+
],
128+
responses = [
129+
@ApiResponse(
130+
description = "Delete the file",
131+
responseCode = "200",
132+
content = [
133+
@Content(
134+
mediaType = "application/json"
135+
)
136+
]
137+
)
138+
],
139+
security = [@SecurityRequirement(name = 'openIdConnect')]
140+
)
141+
@Path("/sandbox/delete")
142+
@Produces("application/json")
143+
@RequireApiKey
144+
def delete() {
145+
if (!spatialConfig.sandboxEnabled) {
146+
return [error: "Sandbox is disabled"] as JSON
147+
}
148+
149+
// Use linked hash map to maintain key ordering
150+
Map<Object, Object> retMap = new LinkedHashMap<Object, Object>()
151+
152+
// Parse the request
153+
String id = params.id
154+
155+
if (id) {
156+
boolean successful = sandboxService.delete(id, authService.getUserId(), authService.userInRole("ROLE_ADMIN"))
157+
if (successful) {
158+
retMap.put("message", "File deleted")
159+
render retMap as JSON
160+
return
161+
}
162+
} else {
163+
retMap.put("error", "No file id supplied")
164+
}
165+
166+
render retMap as JSON, status: 500
167+
}
168+
169+
// status service
170+
@Operation(
171+
method = "GET",
172+
tags = "uploads",
173+
operationId = "statusCSV",
174+
summary = "Get the status of a CSV or zipped CSV file containing occurrence points",
175+
parameters = [
176+
@Parameter(
177+
name = "id",
178+
in = QUERY,
179+
description = "datasetId",
180+
schema = @Schema(implementation = String),
181+
required = true
182+
)
183+
],
184+
responses = [
185+
@ApiResponse(
186+
description = "Status of the file",
187+
responseCode = "200",
188+
content = [
189+
@Content(
190+
mediaType = "application/json"
191+
)
192+
]
193+
)
194+
],
195+
security = [@SecurityRequirement(name = 'openIdConnect')]
196+
)
197+
@Path("/sandbox/status")
198+
@Produces("application/json")
199+
def status() {
200+
if (!spatialConfig.sandboxEnabled) {
201+
return [error: "Sandbox is disabled"] as JSON
202+
}
203+
204+
// Use linked hash map to maintain key ordering
205+
Map<Object, Object> retMap = new LinkedHashMap<Object, Object>()
206+
207+
// Parse the request
208+
String id = params.id
209+
210+
if (id) {
211+
SandboxIngress info = sandboxService.getStatus(id)
212+
if (info) {
213+
render info as JSON
214+
return
215+
} else {
216+
retMap.put("error", "Failed to get status")
217+
}
218+
} else {
219+
retMap.put("error", "No file id supplied")
220+
}
221+
222+
render retMap as JSON, status: 500
223+
}
224+
}
225+

0 commit comments

Comments
 (0)