1 package de.dlr.shepard.data.timeseries.endpoints;
2
3 import de.dlr.shepard.auth.permission.io.PermissionsIO;
4 import de.dlr.shepard.auth.permission.model.Roles;
5 import de.dlr.shepard.auth.permission.services.PermissionsService;
6 import de.dlr.shepard.common.exceptions.InvalidAuthException;
7 import de.dlr.shepard.common.filters.Subscribable;
8 import de.dlr.shepard.common.util.Constants;
9 import de.dlr.shepard.common.util.QueryParamHelper;
10 import de.dlr.shepard.data.ContainerAttributes;
11 import de.dlr.shepard.data.timeseries.io.TimeseriesContainerIO;
12 import de.dlr.shepard.data.timeseries.io.TimeseriesContainerIOMapper;
13 import de.dlr.shepard.data.timeseries.io.TimeseriesIO;
14 import de.dlr.shepard.data.timeseries.io.TimeseriesWithDataPoints;
15 import de.dlr.shepard.data.timeseries.model.Timeseries;
16 import de.dlr.shepard.data.timeseries.model.TimeseriesDataPointsQueryParams;
17 import de.dlr.shepard.data.timeseries.model.TimeseriesTuple;
18 import de.dlr.shepard.data.timeseries.model.enums.AggregateFunction;
19 import de.dlr.shepard.data.timeseries.model.enums.CsvFormat;
20 import de.dlr.shepard.data.timeseries.model.enums.FillOption;
21 import de.dlr.shepard.data.timeseries.services.TimeseriesContainerService;
22 import de.dlr.shepard.data.timeseries.services.TimeseriesCsvService;
23 import de.dlr.shepard.data.timeseries.services.TimeseriesService;
24 import jakarta.enterprise.context.RequestScoped;
25 import jakarta.inject.Inject;
26 import jakarta.transaction.Transactional;
27 import jakarta.validation.Valid;
28 import jakarta.validation.constraints.NotBlank;
29 import jakarta.validation.constraints.NotNull;
30 import jakarta.validation.constraints.PositiveOrZero;
31 import jakarta.ws.rs.Consumes;
32 import jakarta.ws.rs.DELETE;
33 import jakarta.ws.rs.DefaultValue;
34 import jakarta.ws.rs.GET;
35 import jakarta.ws.rs.NotFoundException;
36 import jakarta.ws.rs.POST;
37 import jakarta.ws.rs.PUT;
38 import jakarta.ws.rs.Path;
39 import jakarta.ws.rs.PathParam;
40 import jakarta.ws.rs.Produces;
41 import jakarta.ws.rs.QueryParam;
42 import jakarta.ws.rs.WebApplicationException;
43 import jakarta.ws.rs.core.Context;
44 import jakarta.ws.rs.core.MediaType;
45 import jakarta.ws.rs.core.Response;
46 import jakarta.ws.rs.core.Response.Status;
47 import jakarta.ws.rs.core.SecurityContext;
48 import java.io.IOException;
49 import java.nio.file.InvalidPathException;
50 import java.util.Collections;
51 import java.util.NoSuchElementException;
52 import org.eclipse.microprofile.openapi.annotations.Operation;
53 import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
54 import org.eclipse.microprofile.openapi.annotations.media.Content;
55 import org.eclipse.microprofile.openapi.annotations.media.Schema;
56 import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
57 import org.eclipse.microprofile.openapi.annotations.parameters.RequestBody;
58 import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
59 import org.eclipse.microprofile.openapi.annotations.tags.Tag;
60 import org.jboss.resteasy.reactive.RestForm;
61 import org.jboss.resteasy.reactive.multipart.FileUpload;
62
63 @Consumes(MediaType.APPLICATION_JSON)
64 @Produces(MediaType.APPLICATION_JSON)
65 @Path(Constants.TIMESERIES_CONTAINERS)
66 @RequestScoped
67 public class TimeseriesRest {
68
69 @Inject
70 TimeseriesService timeseriesService;
71
72 @Inject
73 TimeseriesCsvService timeseriesCsvService;
74
75 @Inject
76 TimeseriesContainerService timeseriesContainerService;
77
78 @Inject
79 PermissionsService permissionsService;
80
81 @Context
82 private SecurityContext securityContext;
83
84 @GET
85 @Tag(name = Constants.TIMESERIES_CONTAINER)
86 @Operation(description = "Get all timeseries containers")
87 @APIResponse(
88 description = "ok",
89 responseCode = "200",
90 content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = TimeseriesContainerIO.class))
91 )
92 @APIResponse(responseCode = "400", description = "bad request")
93 @APIResponse(responseCode = "401", description = "not authorized")
94 @APIResponse(responseCode = "403", description = "forbidden")
95 @APIResponse(responseCode = "404", description = "not found")
96 @Parameter(name = Constants.QP_NAME)
97 @Parameter(name = Constants.QP_PAGE)
98 @Parameter(name = Constants.QP_SIZE)
99 @Parameter(name = Constants.QP_ORDER_BY_ATTRIBUTE)
100 @Parameter(name = Constants.QP_ORDER_DESC)
101 public Response getAllTimeseriesContainers(
102 @QueryParam(Constants.QP_NAME) String name,
103 @QueryParam(Constants.QP_PAGE) @PositiveOrZero Integer page,
104 @QueryParam(Constants.QP_SIZE) @PositiveOrZero Integer size,
105 @QueryParam(Constants.QP_ORDER_BY_ATTRIBUTE) ContainerAttributes orderBy,
106 @QueryParam(Constants.QP_ORDER_DESC) Boolean orderDesc
107 ) {
108 var params = new QueryParamHelper();
109 if (name != null) params = params.withName(name);
110 if (page != null && size != null) params = params.withPageAndSize(page, size);
111 if (orderBy != null) params = params.withOrderByAttribute(orderBy, orderDesc);
112 var containers = timeseriesContainerService.getAllContainers(params);
113 var result = TimeseriesContainerIOMapper.map(containers);
114
115 return Response.ok(result).build();
116 }
117
118 @GET
119 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}")
120 @Tag(name = Constants.TIMESERIES_CONTAINER)
121 @Operation(description = "Get timeseries container")
122 @APIResponse(
123 description = "ok",
124 responseCode = "200",
125 content = @Content(schema = @Schema(implementation = TimeseriesContainerIO.class))
126 )
127 @APIResponse(responseCode = "400", description = "bad request")
128 @APIResponse(responseCode = "401", description = "not authorized")
129 @APIResponse(responseCode = "403", description = "forbidden")
130 @APIResponse(responseCode = "404", description = "not found")
131 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
132 public Response getTimeseriesContainer(
133 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId
134 ) {
135 var container = timeseriesContainerService.getContainer(timeseriesContainerId);
136 return Response.ok(TimeseriesContainerIOMapper.map(container)).build();
137 }
138
139 @POST
140 @Tag(name = Constants.TIMESERIES_CONTAINER)
141 @Operation(description = "Create a new timeseries container")
142 @APIResponse(
143 description = "created",
144 responseCode = "201",
145 content = @Content(schema = @Schema(implementation = TimeseriesContainerIO.class))
146 )
147 @APIResponse(responseCode = "400", description = "bad request")
148 @APIResponse(responseCode = "401", description = "not authorized")
149 @APIResponse(responseCode = "403", description = "forbidden")
150 @APIResponse(responseCode = "404", description = "not found")
151 @Transactional
152 public Response createTimeseriesContainer(
153 @RequestBody(
154 content = @Content(schema = @Schema(implementation = TimeseriesContainerIO.class))
155 ) @Valid TimeseriesContainerIO timeseriesContainer
156 ) {
157 var container = timeseriesContainerService.createContainer(timeseriesContainer);
158 return Response.ok(TimeseriesContainerIOMapper.map(container)).status(Status.CREATED).build();
159 }
160
161 @DELETE
162 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}")
163 @Subscribable
164 @Tag(name = Constants.TIMESERIES_CONTAINER)
165 @Operation(description = "Delete timeseries container")
166 @APIResponse(description = "deleted", responseCode = "204")
167 @APIResponse(responseCode = "400", description = "bad request")
168 @APIResponse(responseCode = "401", description = "not authorized")
169 @APIResponse(responseCode = "403", description = "forbidden")
170 @APIResponse(responseCode = "404", description = "not found")
171 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
172 public Response deleteTimeseriesContainer(
173 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId
174 ) {
175 timeseriesContainerService.deleteContainer(timeseriesContainerId);
176 return Response.status(Status.NO_CONTENT).build();
177 }
178
179 @POST
180 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.PAYLOAD)
181 @Subscribable
182 @Tag(name = Constants.TIMESERIES_CONTAINER)
183 @Operation(description = "Upload timeseries to container")
184 @APIResponse(
185 description = "created",
186 responseCode = "201",
187 content = @Content(schema = @Schema(implementation = TimeseriesTuple.class))
188 )
189 @APIResponse(responseCode = "400", description = "bad request")
190 @APIResponse(responseCode = "401", description = "not authorized")
191 @APIResponse(responseCode = "403", description = "forbidden")
192 @APIResponse(responseCode = "404", description = "not found")
193 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
194 public Response createTimeseries(
195 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long containerId,
196 @RequestBody(
197 content = @Content(schema = @Schema(implementation = TimeseriesWithDataPoints.class))
198 ) @Valid TimeseriesWithDataPoints payload
199 ) {
200 Timeseries timeseries = timeseriesService.saveDataPoints(containerId, payload.getTimeseries(), payload.getPoints());
201
202 return Response.ok(timeseries.getTimeseriesTuple()).status(Status.CREATED).build();
203 }
204
205 @Deprecated(forRemoval = true)
206 @GET
207 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.AVAILABLE)
208 @Tag(name = Constants.TIMESERIES_CONTAINER)
209 @Operation(
210 description = "Get timeseries available. Deprecated, use /timeseriesContainers/{containerId}/timeseries instead."
211 )
212 @APIResponse(
213 description = "ok",
214 responseCode = "200",
215 content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = TimeseriesTuple.class))
216 )
217 @APIResponse(responseCode = "400", description = "bad request")
218 @APIResponse(responseCode = "401", description = "not authorized")
219 @APIResponse(responseCode = "403", description = "forbidden")
220 @APIResponse(responseCode = "404", description = "not found")
221 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
222 public Response getTimeseriesAvailable(
223 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId
224 ) {
225 try {
226 var timeseriesListWithoutId = timeseriesService
227 .getTimeseriesAvailable(timeseriesContainerId)
228 .map(Timeseries::getTimeseriesTuple)
229 .toList();
230 return Response.ok(timeseriesListWithoutId).build();
231 } catch (InvalidPathException | InvalidAuthException e) {
232 return Response.ok(Collections.emptyList()).build();
233 }
234 }
235
236 @GET
237 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.TIMESERIES)
238 @Tag(name = Constants.TIMESERIES_CONTAINER)
239 @Operation(description = "Get all available timeseries for that container.")
240 @APIResponse(
241 description = "ok",
242 responseCode = "200",
243 content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = TimeseriesIO.class))
244 )
245 @APIResponse(responseCode = "400", description = "bad request")
246 @APIResponse(responseCode = "401", description = "not authorized")
247 @APIResponse(responseCode = "403", description = "forbidden")
248 @APIResponse(responseCode = "404", description = "not found")
249 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
250 @Parameter(name = Constants.MEASUREMENT)
251 @Parameter(name = Constants.DEVICE)
252 @Parameter(name = Constants.LOCATION)
253 @Parameter(name = Constants.SYMBOLICNAME)
254 @Parameter(name = Constants.FIELD)
255 public Response getTimeseriesOfContainer(
256 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
257 @QueryParam(Constants.MEASUREMENT) String measurement,
258 @QueryParam(Constants.DEVICE) String device,
259 @QueryParam(Constants.LOCATION) String location,
260 @QueryParam(Constants.SYMBOLICNAME) String symbolicName,
261 @QueryParam(Constants.FIELD) String field
262 ) {
263 var timeseriesList = timeseriesService
264 .getTimeseriesAvailable(timeseriesContainerId)
265 .map(TimeseriesIO::new)
266 .filter(
267 entity ->
268 (measurement == null || measurement.isEmpty() || entity.getMeasurement().equals(measurement)) &&
269 (device == null || device.isEmpty() || entity.getDevice().equals(device)) &&
270 (location == null || location.isEmpty() || entity.getLocation().equals(location)) &&
271 (symbolicName == null || symbolicName.isEmpty() || entity.getSymbolicName().equals(symbolicName)) &&
272 (field == null || field.isEmpty() || entity.getField().equals(field))
273 )
274 .toList();
275 return Response.ok(timeseriesList).build();
276 }
277
278 @GET
279 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.TIMESERIES + "/{" + Constants.TIMESERIES_ID + "}")
280 @Tag(name = Constants.TIMESERIES_CONTAINER)
281 @Operation(description = "Get the 5-tuple describing a timeseries by its id.")
282 @APIResponse(
283 description = "ok",
284 responseCode = "200",
285 content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = TimeseriesIO.class))
286 )
287 @APIResponse(responseCode = "400", description = "bad request")
288 @APIResponse(responseCode = "401", description = "not authorized")
289 @APIResponse(responseCode = "403", description = "forbidden")
290 @APIResponse(responseCode = "404", description = "not found")
291 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
292 public Response getTimeseriesById(
293 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long containerId,
294 @PathParam(Constants.TIMESERIES_ID) @NotNull @PositiveOrZero Long timeseriesId
295 ) {
296 var timeseries = timeseriesService.getTimeseriesById(timeseriesId);
297 return Response.ok(new TimeseriesIO(timeseries)).build();
298 }
299
300 @GET
301 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.PAYLOAD)
302 @Tag(name = Constants.TIMESERIES_CONTAINER)
303 @Operation(description = "Get timeseries payload")
304 @APIResponse(
305 description = "ok",
306 responseCode = "200",
307 content = @Content(schema = @Schema(implementation = TimeseriesWithDataPoints.class))
308 )
309 @APIResponse(responseCode = "400", description = "bad request")
310 @APIResponse(responseCode = "401", description = "not authorized")
311 @APIResponse(responseCode = "403", description = "forbidden")
312 @APIResponse(responseCode = "404", description = "not found")
313 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
314 @Parameter(name = Constants.MEASUREMENT, required = true)
315 @Parameter(name = Constants.LOCATION, required = true)
316 @Parameter(name = Constants.DEVICE, required = true)
317 @Parameter(name = Constants.SYMBOLICNAME, required = true)
318 @Parameter(name = Constants.FIELD, required = true)
319 @Parameter(name = Constants.START, required = true)
320 @Parameter(name = Constants.END, required = true)
321 @Parameter(name = Constants.FUNCTION)
322 @Parameter(name = Constants.GROUP_BY)
323 @Parameter(name = Constants.FILLOPTION)
324 public Response getTimeseries(
325 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
326 @QueryParam(Constants.MEASUREMENT) @NotBlank String measurement,
327 @QueryParam(Constants.LOCATION) @NotBlank String location,
328 @QueryParam(Constants.DEVICE) @NotBlank String device,
329 @QueryParam(Constants.SYMBOLICNAME) @NotBlank String symbolicName,
330 @QueryParam(Constants.FIELD) @NotBlank String field,
331 @QueryParam(Constants.START) @NotNull @PositiveOrZero Long start,
332 @QueryParam(Constants.END) @NotNull @PositiveOrZero Long end,
333 @QueryParam(Constants.FUNCTION) AggregateFunction function,
334 @QueryParam(Constants.GROUP_BY) Long groupBy,
335 @QueryParam(Constants.FILLOPTION) FillOption fillOption
336 ) throws Exception {
337 var timeseries = new TimeseriesTuple(measurement, device, location, symbolicName, field);
338 TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
339 start,
340 end,
341 groupBy,
342 fillOption,
343 function
344 );
345 var timeseriesData = timeseriesService.getDataPointsByTimeseries(timeseriesContainerId, timeseries, queryParams);
346 TimeseriesWithDataPoints timeseriesWithData = new TimeseriesWithDataPoints(timeseries, timeseriesData);
347 return Response.ok(timeseriesWithData).build();
348 }
349
350 @GET
351 @Produces({ MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON })
352 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.EXPORT)
353 @Tag(name = Constants.TIMESERIES_CONTAINER)
354 @Operation(description = "Export timeseries payload")
355 @APIResponse(
356 description = "ok",
357 responseCode = "200",
358 content = @Content(
359 mediaType = MediaType.APPLICATION_OCTET_STREAM,
360 schema = @Schema(type = SchemaType.STRING, format = "binary")
361 )
362 )
363 @APIResponse(responseCode = "400", description = "bad request")
364 @APIResponse(responseCode = "401", description = "not authorized")
365 @APIResponse(responseCode = "403", description = "forbidden")
366 @APIResponse(responseCode = "404", description = "not found")
367 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
368 @Parameter(name = Constants.MEASUREMENT, required = true)
369 @Parameter(name = Constants.LOCATION, required = true)
370 @Parameter(name = Constants.DEVICE, required = true)
371 @Parameter(name = Constants.SYMBOLICNAME, required = true)
372 @Parameter(name = Constants.FIELD, required = true)
373 @Parameter(name = Constants.START, required = true)
374 @Parameter(name = Constants.END, required = true)
375 @Parameter(name = Constants.FUNCTION)
376 @Parameter(name = Constants.GROUP_BY)
377 @Parameter(name = Constants.FILLOPTION)
378 @Parameter(name = Constants.CSVFORMAT)
379 public Response exportTimeseries(
380 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
381 @QueryParam(Constants.MEASUREMENT) @NotBlank String measurement,
382 @QueryParam(Constants.LOCATION) @NotBlank String location,
383 @QueryParam(Constants.DEVICE) @NotBlank String device,
384 @QueryParam(Constants.SYMBOLICNAME) @NotBlank String symbolicName,
385 @QueryParam(Constants.FIELD) @NotBlank String field,
386 @QueryParam(Constants.START) @NotNull @PositiveOrZero Long start,
387 @QueryParam(Constants.END) @NotNull @PositiveOrZero Long end,
388 @QueryParam(Constants.FUNCTION) AggregateFunction function,
389 @QueryParam(Constants.GROUP_BY) Long groupBy,
390 @QueryParam(Constants.FILLOPTION) FillOption fillOption,
391 @QueryParam(Constants.CSVFORMAT) @DefaultValue(value = "ROW") CsvFormat csvFormat
392 ) throws IOException {
393 var timeseries = new TimeseriesTuple(measurement, device, location, symbolicName, field);
394 TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
395 start,
396 end,
397 groupBy,
398 fillOption,
399 function
400 );
401 var inputStream = timeseriesCsvService.exportTimeseriesDataToCsv(
402 timeseriesContainerId,
403 timeseries,
404 queryParams,
405 csvFormat
406 );
407
408 return Response.ok(inputStream, MediaType.APPLICATION_OCTET_STREAM)
409 .header("Content-Disposition", "attachment; filename=\"timeseries-export.csv\"")
410 .build();
411 }
412
413 @POST
414 @Consumes(MediaType.MULTIPART_FORM_DATA)
415 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.IMPORT)
416 @Tag(name = Constants.TIMESERIES_CONTAINER)
417 @Operation(description = "Import timeseries payload")
418 @APIResponse(description = "ok", responseCode = "200")
419 @APIResponse(responseCode = "400", description = "bad request")
420 @APIResponse(responseCode = "401", description = "not authorized")
421 @APIResponse(responseCode = "403", description = "forbidden")
422 @APIResponse(responseCode = "404", description = "not found")
423 @Subscribable
424 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
425 public Response importTimeseries(
426 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
427 MultipartBodyFileUpload body
428 ) throws IOException {
429 String filePath = body.fileUpload != null ? body.fileUpload.uploadedFile().toString() : null;
430
431 if (filePath == null) {
432 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
433 }
434
435 timeseriesCsvService.importTimeseriesFromCsv(timeseriesContainerId, filePath);
436 return Response.ok().build();
437 }
438
439 @GET
440 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.PERMISSIONS)
441 @Tag(name = Constants.TIMESERIES_CONTAINER)
442 @Operation(description = "Get permissions")
443 @APIResponse(
444 description = "ok",
445 responseCode = "200",
446 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
447 )
448 @APIResponse(responseCode = "400", description = "bad request")
449 @APIResponse(responseCode = "401", description = "not authorized")
450 @APIResponse(responseCode = "403", description = "forbidden")
451 @APIResponse(responseCode = "404", description = "not found")
452 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
453 public PermissionsIO getTimeseriesPermissions(
454 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId
455 ) {
456 var permissions = permissionsService.getPermissionsOfEntity(timeseriesContainerId);
457 return new PermissionsIO(permissions);
458 }
459
460 @PUT
461 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.PERMISSIONS)
462 @Tag(name = Constants.TIMESERIES_CONTAINER)
463 @Operation(description = "Edit permissions")
464 @APIResponse(
465 description = "ok",
466 responseCode = "200",
467 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
468 )
469 @APIResponse(responseCode = "400", description = "bad request")
470 @APIResponse(responseCode = "401", description = "not authorized")
471 @APIResponse(responseCode = "403", description = "forbidden")
472 @APIResponse(responseCode = "404", description = "not found")
473 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
474 public PermissionsIO editTimeseriesPermissions(
475 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
476 @RequestBody(
477 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
478 ) @Valid PermissionsIO permissions
479 ) {
480 var updatedPermissions = permissionsService.updatePermissionsByNeo4jId(permissions, timeseriesContainerId);
481 if (updatedPermissions == null) throw new NotFoundException();
482 return new PermissionsIO(updatedPermissions);
483 }
484
485 @GET
486 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.ROLES)
487 @Tag(name = Constants.TIMESERIES_CONTAINER)
488 @Operation(description = "Get roles")
489 @APIResponse(
490 description = "ok",
491 responseCode = "200",
492 content = @Content(schema = @Schema(implementation = Roles.class))
493 )
494 @APIResponse(responseCode = "400", description = "bad request")
495 @APIResponse(responseCode = "401", description = "not authorized")
496 @APIResponse(responseCode = "403", description = "forbidden")
497 @APIResponse(responseCode = "404", description = "not found")
498 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
499 public Roles getTimeseriesRoles(
500 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId
501 ) {
502 var roles = permissionsService.getUserRolesOnEntity(
503 timeseriesContainerId,
504 securityContext.getUserPrincipal().getName()
505 );
506 if (roles == null) throw new NotFoundException();
507 return roles;
508 }
509
510 @Schema(type = SchemaType.STRING, format = "binary", description = "Timeseries as CSV")
511 public interface UploadItemSchema {}
512
513 public static class UploadFormSchema {
514
515 @Schema(required = true)
516 public UploadItemSchema file;
517 }
518
519 @Schema(implementation = UploadFormSchema.class)
520 public static class MultipartBodyFileUpload {
521
522 @RestForm(Constants.FILE)
523 public FileUpload fileUpload;
524 }
525 }