/** * This file is part of lavagna. * * lavagna is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * lavagna is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with lavagna. If not, see <http://www.gnu.org/licenses/>. */ package io.lavagna.service; import io.lavagna.common.Json; import io.lavagna.model.*; import io.lavagna.query.StatisticsQuery; import org.apache.commons.lang3.tuple.ImmutablePair; import org.springframework.stereotype.Component; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @Component class LavagnaExporter { private final ConfigurationRepository configurationRepository; private final UserRepository userRepository; private final PermissionService permissionService; private final ProjectService projectService; private final CardLabelRepository cardLabelRepository; private final BoardRepository boardRepository; private final BoardColumnRepository boardColumnRepository; private final EventRepository eventRepository; private final CardRepository cardRepository; private final CardDataRepository cardDataRepository; private final StatisticsQuery statisticsQuery; public LavagnaExporter(ConfigurationRepository configurationRepository, UserRepository userRepository, PermissionService permissionService, ProjectService projectService, CardLabelRepository cardLabelRepository, BoardRepository boardRepository, BoardColumnRepository boardColumnRepository, EventRepository eventRepository, CardRepository cardRepository, CardDataRepository cardDataRepository, StatisticsQuery statisticsQuery) { this.configurationRepository = configurationRepository; this.userRepository = userRepository; this.permissionService = permissionService; this.projectService = projectService; this.cardLabelRepository = cardLabelRepository; this.boardRepository = boardRepository; this.boardColumnRepository = boardColumnRepository; this.eventRepository = eventRepository; this.cardRepository = cardRepository; this.cardDataRepository = cardDataRepository; this.statisticsQuery = statisticsQuery; } public void exportData(OutputStream os) throws IOException { try (ZipOutputStream zf = new ZipOutputStream(os); OutputStreamWriter osw = new OutputStreamWriter(zf, StandardCharsets.UTF_8)) { writeEntry("config.json", configurationRepository.findAll(), zf, osw); writeEntry("users.json", userRepository.findAll(), zf, osw); writeEntry("permissions.json", permissionService.findAllRolesAndRelatedPermissionWithUsers(), zf, osw); writeEntry("users-password-hash.json", userRepository.findUsersWithPasswords(), zf, osw); exportFiles(zf, osw); for (Project p : projectService.findAll()) { exportProject(zf, osw, p); } // final int amountPerPage = 100; int pages = (eventRepository.count() + amountPerPage - 1) / amountPerPage; writeEntry("events-page-count.json", pages, zf, osw); for (int i = 0; i < pages; i++) { writeEntry("events-" + i + ".json", toEventFull(eventRepository.find(i * 100, 100)), zf, osw); } // writeEntry("card-data-types-order.json", cardDataRepository.findAllByTypes(EnumSet.of(CardType.ACTION_LIST, CardType.ACTION_CHECKED, CardType.ACTION_UNCHECKED)), zf, osw); } } private void exportFiles(ZipOutputStream zf, OutputStreamWriter osw) throws IOException { osw.flush(); for (CardDataUploadContentInfo fileData : cardDataRepository.findAllDataUploadContentInfo()) { zf.putNextEntry(new ZipEntry("files/" + fileData.getDigest())); cardDataRepository.outputFileContent(fileData.getDigest(), zf); writeEntry("files/" + fileData.getDigest() + ".json", fileData, zf, osw); } } private List<EventFull> toEventFull(List<Event> events) { List<EventFull> res = new ArrayList<>(events.size()); for (Event e : events) { // TODO not optimal User u = userRepository.findById(e.getUserId()); ImmutablePair<Board, Card> bc = findByCardId(e.getCardId()); // String content = handleContent(e); User labelUser = e.getValueUser() != null ? userRepository.findById(e.getValueUser()) : null; ImmutablePair<Board, Card> labelCard = e.getValueCard() != null ? findByCardId(e.getValueCard()) : null; // res.add(new EventFull(e, u, bc, content, labelCard, labelUser)); } return res; } private ImmutablePair<Board, Card> findByCardId(int id) { Card c = cardRepository.findBy(id); Board b = boardRepository.findBoardById(boardColumnRepository.findById(c.getColumnId()).getBoardId()); return ImmutablePair.of(b, c); } // TODO CLEANUP private String handleContent(Event e) { if (e.getDataId() == null) { return null; } switch (e.getEvent()) { case COMMENT_CREATE: return extractFirstContent(e, CardType.COMMENT_HISTORY); case DESCRIPTION_CREATE: return extractFirstContent(e, CardType.DESCRIPTION_HISTORY); case DESCRIPTION_UPDATE: case COMMENT_UPDATE: List<Event> nextEvent = eventRepository.findNextEventFor(e); return cardDataRepository.getDataLightById( nextEvent.isEmpty() ? e.getDataId() : nextEvent.get(0).getPreviousDataId()).getContent(); case ACTION_ITEM_CREATE: case ACTION_LIST_CREATE: return cardDataRepository.getDataLightById(e.getDataId()).getContent(); case FILE_UPLOAD: case FILE_DELETE: return cardDataRepository.getDataLightById(e.getDataId()).getContent(); default: return null; } } private String extractFirstContent(Event e, CardType type) { CardData cd = cardDataRepository.getDataLightById(e.getDataId()); List<CardData> history = cardDataRepository.findAllDataLightByReferenceIdAndType(cd.getId(), type); return (history.isEmpty() ? cd : history.get(0)).getContent(); } private static void writeEntry(String entryName, Object toSerialize, ZipOutputStream zf, OutputStreamWriter osw) { try { zf.putNextEntry(new ZipEntry(entryName)); Json.GSON.toJson(toSerialize, osw); osw.flush(); zf.flush(); zf.closeEntry(); } catch (IOException ioe) { throw new IllegalStateException("error while serializing entry " + entryName, ioe); } } private void exportProject(ZipOutputStream zf, OutputStreamWriter osw, Project p) { String projectNameDir = "projects/" + p.getShortName(); writeEntry(projectNameDir + ".json", p, zf, osw); writeEntry(projectNameDir + "/permissions.json", permissionService.findAllRolesAndRelatedPermissionWithUsersInProjectId(p.getId()), zf, osw); List<Pair<CardLabel, List<LabelListValueWithMetadata>>> labels = new ArrayList<>(); for (CardLabel cl : cardLabelRepository.findLabelsByProject(p.getId())) { Pair<CardLabel, List<LabelListValueWithMetadata>> toAdd = Pair.Companion.of(cl, cardLabelRepository.findListValuesByLabelId(cl.getId())); labels.add(toAdd); } writeEntry(projectNameDir + "/labels.json", labels, zf, osw); writeEntry(projectNameDir + "/column-definitions.json", projectService.findMappedColumnDefinitionsByProjectId(p.getId()), zf, osw); for (BoardInfo boardInfo : boardRepository.findBoardInfo(p.getId())) { exportBoard(boardInfo, p, zf, osw); } } private void exportBoard(BoardInfo boardInfo, Project p, ZipOutputStream zf, OutputStreamWriter osw) { String boardNameDir = "boards/" + boardInfo.getShortName(); writeEntry(boardNameDir + ".json", Pair.Companion.of(p.getShortName(), boardInfo), zf, osw); int boardId = boardRepository.findBoardIdByShortName(boardInfo.getShortName()); writeEntry(boardNameDir + "/columns.json", boardColumnRepository.findAllColumnsFor(boardId), zf, osw); writeEntry(boardNameDir + "/cards.json", cardRepository.findAllByBoardShortName(boardInfo.getShortName()), zf, osw); writeEntry(boardNameDir + "/statistics.json", statisticsQuery.findForBoard(boardId), zf, osw); } }