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.List;
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 List<TimeseriesIO> timeseriesList = timeseriesService
264 .findByQuintupleInContainer(timeseriesContainerId, measurement, device, location, symbolicName, field)
265 .map(TimeseriesIO::new)
266 .toList();
267 return Response.ok(timeseriesList).build();
268 }
269
270 @GET
271 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.TIMESERIES + "/{" + Constants.TIMESERIES_ID + "}")
272 @Tag(name = Constants.TIMESERIES_CONTAINER)
273 @Operation(description = "Get the 5-tuple describing a timeseries by its id.")
274 @APIResponse(
275 description = "ok",
276 responseCode = "200",
277 content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = TimeseriesIO.class))
278 )
279 @APIResponse(responseCode = "400", description = "bad request")
280 @APIResponse(responseCode = "401", description = "not authorized")
281 @APIResponse(responseCode = "403", description = "forbidden")
282 @APIResponse(responseCode = "404", description = "not found")
283 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
284 public Response getTimeseriesById(
285 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long containerId,
286 @PathParam(Constants.TIMESERIES_ID) @NotNull @PositiveOrZero Long timeseriesId
287 ) {
288 var timeseries = timeseriesService.getTimeseriesById(timeseriesId);
289 return Response.ok(new TimeseriesIO(timeseries)).build();
290 }
291
292 @GET
293 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.PAYLOAD)
294 @Tag(name = Constants.TIMESERIES_CONTAINER)
295 @Operation(description = "Get timeseries payload")
296 @APIResponse(
297 description = "ok",
298 responseCode = "200",
299 content = @Content(schema = @Schema(implementation = TimeseriesWithDataPoints.class))
300 )
301 @APIResponse(responseCode = "400", description = "bad request")
302 @APIResponse(responseCode = "401", description = "not authorized")
303 @APIResponse(responseCode = "403", description = "forbidden")
304 @APIResponse(responseCode = "404", description = "not found")
305 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
306 @Parameter(name = Constants.MEASUREMENT, required = true)
307 @Parameter(name = Constants.LOCATION, required = true)
308 @Parameter(name = Constants.DEVICE, required = true)
309 @Parameter(name = Constants.SYMBOLICNAME, required = true)
310 @Parameter(name = Constants.FIELD, required = true)
311 @Parameter(name = Constants.START, required = true)
312 @Parameter(name = Constants.END, required = true)
313 @Parameter(name = Constants.FUNCTION)
314 @Parameter(name = Constants.GROUP_BY)
315 @Parameter(name = Constants.FILLOPTION)
316 public Response getTimeseries(
317 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
318 @QueryParam(Constants.MEASUREMENT) @NotBlank String measurement,
319 @QueryParam(Constants.LOCATION) @NotBlank String location,
320 @QueryParam(Constants.DEVICE) @NotBlank String device,
321 @QueryParam(Constants.SYMBOLICNAME) @NotBlank String symbolicName,
322 @QueryParam(Constants.FIELD) @NotBlank String field,
323 @QueryParam(Constants.START) @NotNull @PositiveOrZero Long start,
324 @QueryParam(Constants.END) @NotNull @PositiveOrZero Long end,
325 @QueryParam(Constants.FUNCTION) AggregateFunction function,
326 @QueryParam(Constants.GROUP_BY) Long groupBy,
327 @QueryParam(Constants.FILLOPTION) FillOption fillOption
328 ) throws Exception {
329 var timeseries = new TimeseriesTuple(measurement, device, location, symbolicName, field);
330 TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
331 start,
332 end,
333 groupBy,
334 fillOption,
335 function
336 );
337 var timeseriesData = timeseriesService.getDataPointsByTimeseries(timeseriesContainerId, timeseries, queryParams);
338 TimeseriesWithDataPoints timeseriesWithData = new TimeseriesWithDataPoints(timeseries, timeseriesData);
339 return Response.ok(timeseriesWithData).build();
340 }
341
342 @GET
343 @Produces({ MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_JSON })
344 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.EXPORT)
345 @Tag(name = Constants.TIMESERIES_CONTAINER)
346 @Operation(description = "Export timeseries payload")
347 @APIResponse(
348 description = "ok",
349 responseCode = "200",
350 content = @Content(
351 mediaType = MediaType.APPLICATION_OCTET_STREAM,
352 schema = @Schema(type = SchemaType.STRING, format = "binary")
353 )
354 )
355 @APIResponse(responseCode = "400", description = "bad request")
356 @APIResponse(responseCode = "401", description = "not authorized")
357 @APIResponse(responseCode = "403", description = "forbidden")
358 @APIResponse(responseCode = "404", description = "not found")
359 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
360 @Parameter(name = Constants.MEASUREMENT, required = true)
361 @Parameter(name = Constants.LOCATION, required = true)
362 @Parameter(name = Constants.DEVICE, required = true)
363 @Parameter(name = Constants.SYMBOLICNAME, required = true)
364 @Parameter(name = Constants.FIELD, required = true)
365 @Parameter(name = Constants.START, required = true)
366 @Parameter(name = Constants.END, required = true)
367 @Parameter(name = Constants.FUNCTION)
368 @Parameter(name = Constants.GROUP_BY)
369 @Parameter(name = Constants.FILLOPTION)
370 @Parameter(name = Constants.CSVFORMAT)
371 public Response exportTimeseries(
372 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
373 @QueryParam(Constants.MEASUREMENT) @NotBlank String measurement,
374 @QueryParam(Constants.LOCATION) @NotBlank String location,
375 @QueryParam(Constants.DEVICE) @NotBlank String device,
376 @QueryParam(Constants.SYMBOLICNAME) @NotBlank String symbolicName,
377 @QueryParam(Constants.FIELD) @NotBlank String field,
378 @QueryParam(Constants.START) @NotNull @PositiveOrZero Long start,
379 @QueryParam(Constants.END) @NotNull @PositiveOrZero Long end,
380 @QueryParam(Constants.FUNCTION) AggregateFunction function,
381 @QueryParam(Constants.GROUP_BY) Long groupBy,
382 @QueryParam(Constants.FILLOPTION) FillOption fillOption,
383 @QueryParam(Constants.CSVFORMAT) @DefaultValue(value = "ROW") CsvFormat csvFormat
384 ) throws IOException {
385 var timeseries = new TimeseriesTuple(measurement, device, location, symbolicName, field);
386 TimeseriesDataPointsQueryParams queryParams = new TimeseriesDataPointsQueryParams(
387 start,
388 end,
389 groupBy,
390 fillOption,
391 function
392 );
393 var inputStream = timeseriesCsvService.exportTimeseriesDataToCsv(
394 timeseriesContainerId,
395 timeseries,
396 queryParams,
397 csvFormat
398 );
399
400 return Response.ok(inputStream, MediaType.APPLICATION_OCTET_STREAM)
401 .header("Content-Disposition", "attachment; filename=\"timeseries-export.csv\"")
402 .build();
403 }
404
405 @POST
406 @Consumes(MediaType.MULTIPART_FORM_DATA)
407 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.IMPORT)
408 @Tag(name = Constants.TIMESERIES_CONTAINER)
409 @Operation(description = "Import timeseries payload")
410 @APIResponse(description = "ok", responseCode = "200")
411 @APIResponse(responseCode = "400", description = "bad request")
412 @APIResponse(responseCode = "401", description = "not authorized")
413 @APIResponse(responseCode = "403", description = "forbidden")
414 @APIResponse(responseCode = "404", description = "not found")
415 @Subscribable
416 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
417 public Response importTimeseries(
418 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
419 MultipartBodyFileUpload body
420 ) throws IOException {
421 String filePath = body.fileUpload != null ? body.fileUpload.uploadedFile().toString() : null;
422
423 if (filePath == null) {
424 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
425 }
426
427 timeseriesCsvService.importTimeseriesFromCsv(timeseriesContainerId, filePath);
428 return Response.ok().build();
429 }
430
431 @GET
432 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.PERMISSIONS)
433 @Tag(name = Constants.TIMESERIES_CONTAINER)
434 @Operation(description = "Get permissions")
435 @APIResponse(
436 description = "ok",
437 responseCode = "200",
438 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
439 )
440 @APIResponse(responseCode = "400", description = "bad request")
441 @APIResponse(responseCode = "401", description = "not authorized")
442 @APIResponse(responseCode = "403", description = "forbidden")
443 @APIResponse(responseCode = "404", description = "not found")
444 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
445 public PermissionsIO getTimeseriesPermissions(
446 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId
447 ) {
448 var permissions = permissionsService.getPermissionsOfEntity(timeseriesContainerId);
449 return new PermissionsIO(permissions);
450 }
451
452 @PUT
453 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.PERMISSIONS)
454 @Tag(name = Constants.TIMESERIES_CONTAINER)
455 @Operation(description = "Edit permissions")
456 @APIResponse(
457 description = "ok",
458 responseCode = "200",
459 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
460 )
461 @APIResponse(responseCode = "400", description = "bad request")
462 @APIResponse(responseCode = "401", description = "not authorized")
463 @APIResponse(responseCode = "403", description = "forbidden")
464 @APIResponse(responseCode = "404", description = "not found")
465 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
466 public PermissionsIO editTimeseriesPermissions(
467 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId,
468 @RequestBody(
469 content = @Content(schema = @Schema(implementation = PermissionsIO.class))
470 ) @Valid PermissionsIO permissions
471 ) {
472 var updatedPermissions = permissionsService.updatePermissionsByNeo4jId(permissions, timeseriesContainerId);
473 if (updatedPermissions == null) throw new NotFoundException();
474 return new PermissionsIO(updatedPermissions);
475 }
476
477 @GET
478 @Path("/{" + Constants.TIMESERIES_CONTAINER_ID + "}/" + Constants.ROLES)
479 @Tag(name = Constants.TIMESERIES_CONTAINER)
480 @Operation(description = "Get roles")
481 @APIResponse(
482 description = "ok",
483 responseCode = "200",
484 content = @Content(schema = @Schema(implementation = Roles.class))
485 )
486 @APIResponse(responseCode = "400", description = "bad request")
487 @APIResponse(responseCode = "401", description = "not authorized")
488 @APIResponse(responseCode = "403", description = "forbidden")
489 @APIResponse(responseCode = "404", description = "not found")
490 @Parameter(name = Constants.TIMESERIES_CONTAINER_ID)
491 public Roles getTimeseriesRoles(
492 @PathParam(Constants.TIMESERIES_CONTAINER_ID) @NotNull @PositiveOrZero Long timeseriesContainerId
493 ) {
494 var roles = permissionsService.getUserRolesOnEntity(
495 timeseriesContainerId,
496 securityContext.getUserPrincipal().getName()
497 );
498 if (roles == null) throw new NotFoundException();
499 return roles;
500 }
501
502 @Schema(type = SchemaType.STRING, format = "binary", description = "Timeseries as CSV")
503 public interface UploadItemSchema {}
504
505 public static class UploadFormSchema {
506
507 @Schema(required = true)
508 public UploadItemSchema file;
509 }
510
511 @Schema(implementation = UploadFormSchema.class)
512 public static class MultipartBodyFileUpload {
513
514 @RestForm(Constants.FILE)
515 public FileUpload fileUpload;
516 }
517 }