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