ImportExportController.java
package com.seebie.server.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.seebie.server.dto.RecordCount;
import com.seebie.server.dto.UserData;
import com.seebie.server.mapper.dtotoentity.CsvToSleepData;
import com.seebie.server.mapper.entitytodto.SleepDetailsToCsv;
import com.seebie.server.service.ImportExportService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.UUID;
import static java.nio.charset.StandardCharsets.UTF_8;
// if we use server.servlet.context-path=/api, static content and API all come from the same base
// so we can use that for api-only requests only if the UI is served separately
@RestController
@RequestMapping(path="/api", produces = MediaType.APPLICATION_JSON_VALUE)
public class ImportExportController {
private static Logger LOG = LoggerFactory.getLogger(ImportExportController.class);
private final ImportExportService importExportService;
private ObjectMapper jsonMapper;
private CsvToSleepData fromCsv;
private SleepDetailsToCsv toCsv;
// if there's only one constructor, can omit Autowired and Inject
public ImportExportController(MappingJackson2HttpMessageConverter converter, ImportExportService importExportService, CsvToSleepData fromCsv, SleepDetailsToCsv toCsv) {
this.jsonMapper = converter.getObjectMapper();
this.importExportService = importExportService;
this.fromCsv = fromCsv;
this.toCsv = toCsv;
}
@PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
@GetMapping(value = "/user/{publicId}/export/json", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<byte[]> exportUserData(@PathVariable UUID publicId) {
String filename = "seebie-data-" + publicId + ".json";
String headerValue = "attachment; filename=" + filename;
String json = toJson(importExportService.retrieveUserData(publicId));
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, headerValue)
.body(json.getBytes(UTF_8));
}
@PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
@PostMapping("/user/{publicId}/import/json")
public RecordCount importUserData(@PathVariable UUID publicId, @RequestParam("file") MultipartFile file) throws IOException {
LOG.info("Import started for user " + publicId);
String rawJson = new String(file.getBytes(), UTF_8);
var userData = parseJson(rawJson);
long numImported = importExportService.saveUserData(publicId, userData);
LOG.info("Imported " + numImported + " records for " + publicId);
return new RecordCount(numImported);
}
@PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
@GetMapping("/user/{publicId}/export/csv")
public ResponseEntity<byte[]> downloadSleepData(@PathVariable UUID publicId) {
String filename = "seebie-data-" + publicId + ".csv";
String headerValue = "attachment; filename=" + filename;
String csv = toCsv.apply(importExportService.retrieveSleepDetails(publicId));
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, headerValue)
.body(csv.getBytes(UTF_8));
}
@PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
@PostMapping("/user/{publicId}/import/csv")
public RecordCount uploadSleepData(@PathVariable UUID publicId, @RequestParam("file") MultipartFile file) throws IOException {
LOG.info("Upload started...");
String rawCsv = new String(file.getBytes(), UTF_8);
var parsedData = fromCsv.apply(rawCsv);
long numImported = importExportService.saveSleepData(publicId, parsedData);
LOG.info("Imported " + numImported + " records for " + publicId);
return new RecordCount(numImported);
}
public String toJson(UserData userData) {
try {
return jsonMapper.writeValueAsString(userData);
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
public UserData parseJson(String userDataJson) {
try {
return jsonMapper.readValue(userDataJson, UserData.class);
}
catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
}