io.lavagna.web.api.CardDataController.java Source code

Java tutorial

Introduction

Here is the source code for io.lavagna.web.api.CardDataController.java

Source

/**
 * 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.web.api;

import io.lavagna.model.CardData;
import io.lavagna.model.CardDataFull;
import io.lavagna.model.CardDataHistory;
import io.lavagna.model.CardType;
import io.lavagna.model.Event;
import io.lavagna.model.Event.EventType;
import io.lavagna.model.FileDataLight;
import io.lavagna.model.Key;
import io.lavagna.model.Permission;
import io.lavagna.model.User;
import io.lavagna.service.CardDataRepository;
import io.lavagna.service.CardDataService;
import io.lavagna.service.CardRepository;
import io.lavagna.service.ConfigurationRepository;
import io.lavagna.service.EventEmitter;
import io.lavagna.service.EventRepository;
import io.lavagna.web.helper.CardCommentOwnershipChecker;
import io.lavagna.web.helper.ExpectPermission;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import lombok.Getter;
import lombok.Setter;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class CardDataController {

    private static final Logger LOG = LogManager.getLogger();

    private final CardDataService cardDataService;
    private final CardDataRepository cardDataRepository;
    private final CardRepository cardRepository;
    private final EventRepository eventRepository;
    private final EventEmitter eventEmitter;
    private final ConfigurationRepository configurationRepository;

    public CardDataController(CardDataService cardDataService, CardDataRepository cardDataRepository,
            CardRepository cardRepository, ConfigurationRepository configurationRepository,
            EventRepository eventRepository, EventEmitter eventEmitter) {
        this.cardDataService = cardDataService;
        this.cardDataRepository = cardDataRepository;
        this.cardRepository = cardRepository;
        this.eventRepository = eventRepository;
        this.eventEmitter = eventEmitter;
        this.configurationRepository = configurationRepository;
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card/{cardId}/data", method = RequestMethod.GET)
    @ResponseBody
    public List<CardData> findAllLightByCardId(@PathVariable("cardId") int cardId) {
        return cardDataRepository.findAllDataLightByCardId(cardId);
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card/{cardId}/description", method = RequestMethod.GET)
    @ResponseBody
    public CardDataHistory description(@PathVariable("cardId") int cardId) {
        return cardDataService.findLatestDescriptionByCardId(cardId);
    }

    @ExpectPermission(Permission.UPDATE_CARD)
    @RequestMapping(value = "/api/card/{cardId}/description", method = RequestMethod.POST)
    @ResponseBody
    public int updateDescription(@PathVariable("cardId") int cardId, @RequestBody Content content, User user) {
        int result = cardDataService.updateDescription(cardId, content.content, new Date(), user.getId());
        eventEmitter.emitUpdateDescription(cardRepository.findBy(cardId).getColumnId(), cardId);
        return result;
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card/{cardId}/comments", method = RequestMethod.GET)
    @ResponseBody
    public List<CardDataHistory> findAllComments(@PathVariable("cardId") int cardId) {
        List<CardDataFull> comments = cardDataService.findAllCommentsByCardId(cardId);
        // look for duplicates, the event model will keep the entire history of
        // the comment
        Map<Integer, CardDataHistory> duplicates = new HashMap<>();
        for (CardDataFull comment : comments) {
            if (!duplicates.containsKey(comment.getId())) {
                CardDataHistory newComment = new CardDataHistory(comment.getId(), comment.getContent(),
                        comment.getOrder(), comment.getUserId(), comment.getTime(), comment.getUserId(),
                        comment.getTime());
                duplicates.put(comment.getId(), newComment);
            }
            CardDataHistory instance = duplicates.get(comment.getId());

            if (comment.getEventType() == EventType.COMMENT_CREATE) {
                instance.setUserId(comment.getUserId());
                instance.setTime(comment.getTime());
            }
            if (comment.getEventType() == EventType.COMMENT_UPDATE) {
                instance.setUpdatedCount(instance.getUpdatedCount() + 1);
                // never null because the object's fields are always initialized
                if (comment.getTime().getTime() > instance.getUpdateDate().getTime()) {
                    instance.setUpdateUser(comment.getUserId());
                    instance.setUpdateDate(comment.getTime());
                }
            }
            duplicates.put(comment.getId(), instance);
        }
        return new ArrayList<>(duplicates.values());
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card/{cardId}/actionlists", method = RequestMethod.GET)
    @ResponseBody
    public List<CardData> findAllActionLists(@PathVariable("cardId") int cardId) {
        return cardDataService.findAllActionListsAndItemsByCardId(cardId);
    }

    @ExpectPermission(Permission.CREATE_CARD_COMMENT)
    @RequestMapping(value = "/api/card/{cardId}/comment", method = RequestMethod.POST)
    @ResponseBody
    public CardData createComment(@PathVariable("cardId") int cardId, @RequestBody Content commentData, User user) {
        CardData comment = cardDataService.createComment(cardId, commentData.content, new Date(), user.getId());
        eventEmitter.emitCreateComment(cardRepository.findBy(cardId).getColumnId(), cardId);
        return comment;
    }

    @ExpectPermission(value = Permission.UPDATE_CARD_COMMENT, ownershipChecker = CardCommentOwnershipChecker.class)
    @RequestMapping(value = "/api/card-data/comment/{commentId}", method = RequestMethod.POST)
    @ResponseBody
    public void updateComment(@PathVariable("commentId") int commentId, @RequestBody Content content, User user) {
        cardDataService.updateComment(commentId, content.content, new Date(), user);
        eventEmitter.emitUpdateComment(cardDataRepository.getUndeletedDataLightById(commentId).getCardId());
    }

    @ExpectPermission(value = Permission.DELETE_CARD_COMMENT, ownershipChecker = CardCommentOwnershipChecker.class)
    @RequestMapping(value = "/api/card-data/comment/{commentId}", method = RequestMethod.DELETE)
    @ResponseBody
    public Event deleteComment(@PathVariable("commentId") int commentId, User user) {
        Event res = cardDataService.deleteComment(commentId, user, new Date());
        eventEmitter.emitDeleteComment(cardRepository.findBy(res.getCardId()).getColumnId(), res.getCardId());
        return res;
    }

    @ExpectPermission(value = Permission.DELETE_CARD_COMMENT, ownershipChecker = CardCommentOwnershipChecker.class)
    @RequestMapping(value = "/api/card-data/undo/{eventId}/comment", method = RequestMethod.POST)
    @ResponseBody
    public int undoDeleteComment(@PathVariable("eventId") int eventId, User user) {
        Event event = eventRepository.getEventById(eventId);
        Validate.isTrue(event.getEvent() == EventType.COMMENT_DELETE);

        cardDataService.undoDeleteComment(event);
        eventEmitter.emitUndoDeleteComment(cardRepository.findBy(event.getCardId()).getColumnId(),
                event.getCardId());

        return event.getDataId();
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card/{cardId}/actionlist", method = RequestMethod.POST)
    @ResponseBody
    public CardData createActionList(@PathVariable("cardId") int cardId, @RequestBody Content actionListData,
            User user) {
        CardData actionList = cardDataService.createActionList(cardId, actionListData.content, user.getId(),
                new Date());
        eventEmitter.emitCreateActionList(cardId);
        return actionList;
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/actionlist/{actionListId}", method = RequestMethod.DELETE)
    @ResponseBody
    public Event deleteActionList(@PathVariable("actionListId") int actionListId, User user) {
        Event res = cardDataService.deleteActionList(actionListId, user, new Date());
        eventEmitter.emitDeleteActionList(cardRepository.findBy(res.getCardId()).getColumnId(), res.getCardId());
        return res;
    }

    @ExpectPermission(value = Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/undo/{eventId}/actionlist", method = RequestMethod.POST)
    @ResponseBody
    public int undoDeleteActionList(@PathVariable("eventId") int eventId, User user) {
        Event event = eventRepository.getEventById(eventId);
        Validate.isTrue(event.getEvent() == EventType.ACTION_LIST_DELETE);

        cardDataService.undoDeleteActionList(event);
        eventEmitter.emitUndoDeleteActionList(cardRepository.findBy(event.getCardId()).getColumnId(),
                event.getCardId());

        return event.getDataId();
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/actionlist/{actionListId}/update", method = RequestMethod.POST)
    @ResponseBody
    public int updateActionList(@PathVariable("actionListId") int actionListId, @RequestBody Content data,
            User user) {
        int res = cardDataService.updateActionList(actionListId, data.content);
        eventEmitter.emitUpdateActionList(cardDataRepository.getUndeletedDataLightById(actionListId).getCardId());
        return res;
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/actionlist/{actionListId}/item", method = RequestMethod.POST)
    @ResponseBody
    public CardData createActionItem(@PathVariable("actionListId") int actionListId,
            @RequestBody Content actionItemData, User user) {
        int cardId = cardDataRepository.getUndeletedDataLightById(actionListId).getCardId();
        CardData actionItem = cardDataService.createActionItem(cardId, actionListId, actionItemData.content,
                user.getId(), new Date());
        eventEmitter.emitCreateActionItem(cardRepository.findBy(cardId).getColumnId(), cardId);
        return actionItem;
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/actionitem/{actionItemId}", method = RequestMethod.DELETE)
    @ResponseBody
    public Event deleteActionItem(@PathVariable("actionItemId") int actionItemId, User user) {
        int cardId = cardDataRepository.getUndeletedDataLightById(actionItemId).getCardId();
        Event res = cardDataService.deleteActionItem(actionItemId, user, new Date());
        eventEmitter.emitDeleteActionItem(cardRepository.findBy(cardId).getColumnId(), cardId);
        return res;
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/undo/{eventId}/actionitem", method = RequestMethod.POST)
    @ResponseBody
    public int undoDeleteActionItem(@PathVariable("eventId") int eventId, User user) {
        Event event = eventRepository.getEventById(eventId);
        Validate.isTrue(event.getEvent() == EventType.ACTION_ITEM_DELETE);

        cardDataService.undoDeleteActionItem(event);
        eventEmitter.emiteUndoDeleteActionItem(cardRepository.findBy(event.getCardId()).getColumnId(),
                event.getCardId());

        return event.getDataId();
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/actionitem/{actionItemId}/toggle/{status}", method = RequestMethod.POST)
    @ResponseBody
    public int toggleActionItem(@PathVariable("actionItemId") int actionItemId,
            @PathVariable("status") Boolean status, User user) {
        int cardId = cardDataRepository.getUndeletedDataLightById(actionItemId).getCardId();
        int res = cardDataService.toggleActionItem(actionItemId, status, user.getId(), new Date());
        eventEmitter.emitToggleActionItem(cardRepository.findBy(cardId).getColumnId(), cardId);
        return res;
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/actionitem/{actionItemId}/update", method = RequestMethod.POST)
    @ResponseBody
    public int updateActionItem(@PathVariable("actionItemId") int actionItemId, @RequestBody Content data,
            User user) {
        int cardId = cardDataRepository.getUndeletedDataLightById(actionItemId).getCardId();
        int res = cardDataService.updateActionItem(actionItemId, data.content);
        eventEmitter.emitUpdateUpdateActionItem(cardId);
        return res;
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/actionitem/{actionItemId}/move-to-actionlist/{to}", method = RequestMethod.POST)
    @ResponseBody
    public boolean moveActionItem(@PathVariable("actionItemId") int actionItemId,
            @PathVariable("to") Integer newReferenceId, @RequestBody OrderData dataOrder, User user) {
        int cardId = cardDataRepository.getUndeletedDataLightById(actionItemId).getCardId();

        cardDataService.moveActionItem(cardId, actionItemId, newReferenceId, dataOrder.newContainer, user,
                new Date());
        eventEmitter.emitMoveActionItem(cardId);
        return true;
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card/{cardId}/order/actionlist", method = RequestMethod.POST)
    @ResponseBody
    public boolean reorderActionLists(@PathVariable("cardId") int cardId, @RequestBody List<Number> order) {
        cardDataRepository.updateActionListOrder(cardId, Utils.from(order));
        eventEmitter.emitReorderActionLists(cardId);
        return true;
    }

    @ExpectPermission(Permission.MANAGE_ACTION_LIST)
    @RequestMapping(value = "/api/card-data/actionlist/{actionListId}/order", method = RequestMethod.POST)
    @ResponseBody
    public boolean reorderActionItems(@PathVariable("actionListId") int actionListId,
            @RequestBody List<Number> order) {
        CardData cd = cardDataRepository.getUndeletedDataLightById(actionListId);
        Validate.isTrue(cd.getType() == CardType.ACTION_LIST);
        int cardId = cd.getCardId();
        cardDataRepository.updateOrderByCardAndReferenceId(cardId, actionListId, Utils.from(order));
        eventEmitter.emitReorderActionItems(cardId);
        return true;
    }

    @ExpectPermission(Permission.CREATE_FILE)
    @RequestMapping(value = "/api/card/{cardId}/file", method = RequestMethod.POST)
    @ResponseBody
    public List<String> uploadFiles(@PathVariable("cardId") int cardId,
            @RequestParam("files") List<MultipartFile> files, User user, HttpServletResponse resp)
            throws IOException {

        LOG.debug("Files uploaded: {}", files.size());

        if (!ensureFileSize(files)) {
            resp.setStatus(422);
            return Collections.emptyList();
        }

        List<String> digests = new ArrayList<>();
        for (MultipartFile file : files) {
            Path p = Files.createTempFile("lavagna", "upload");
            try (InputStream fileIs = file.getInputStream()) {
                Files.copy(fileIs, p, StandardCopyOption.REPLACE_EXISTING);
                String digest = DigestUtils.sha256Hex(Files.newInputStream(p));
                String contentType = file.getContentType() != null ? file.getContentType()
                        : "application/octet-stream";
                boolean result = cardDataService.createFile(file.getOriginalFilename(), digest, file.getSize(),
                        cardId, Files.newInputStream(p), contentType, user, new Date()).getLeft();
                if (result) {
                    LOG.debug("file uploaded! size: {}, original name: {}, content-type: {}", file.getSize(),
                            file.getOriginalFilename(), file.getContentType());
                    digests.add(digest);
                }
            } finally {
                Files.delete(p);
                LOG.debug("deleted temp file {}", p);
            }
        }
        eventEmitter.emitUploadFile(cardRepository.findBy(cardId).getColumnId(), cardId);
        return digests;
    }

    private boolean ensureFileSize(List<MultipartFile> files) {
        Integer maxSizeInByte = NumberUtils
                .createInteger(configurationRepository.getValueOrNull(Key.MAX_UPLOAD_FILE_SIZE));
        if (maxSizeInByte == null) {
            return true;
        }
        for (MultipartFile file : files) {
            if (file.getSize() > maxSizeInByte) {
                return false;
            }
        }
        return true;
    }

    private static Set<String> WHITE_LIST_MIME_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(//
            "image/gif", "image/jpeg", "image/png", "image/webp", "image/bmp", // images
            "video/webm", "video/ogg", "video/mp4", //
            "text/plain"//
    )));

    // TODO: fix exception handling
    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card-data/file/{fileId}/{ignore:.+}", method = RequestMethod.GET)
    public void getFile(@PathVariable("fileId") int fileId, HttpServletResponse response) {
        FileDataLight fileData = cardDataRepository.getUndeletedFileByCardDataId(fileId);
        try (OutputStream out = response.getOutputStream()) {
            if (WHITE_LIST_MIME_TYPES.contains(fileData.getContentType())) {
                response.setContentType(fileData.getContentType());
            } else {
                response.setHeader("Content-Disposition", "attachment;filename=\"" + fileData.getName() + "\"");
                response.setContentType("application/octet-stream");
            }
            cardDataRepository.outputFileContent(fileData.getDigest(), out);
        } catch (IOException e) {
            LOG.error("error getting file", e);
            response.setStatus(500);
        }
    }

    @ExpectPermission(Permission.DELETE_FILE)
    @RequestMapping(value = "/api/card-data/file/{fileId}", method = RequestMethod.DELETE)
    @ResponseBody
    public Event deleteFile(@PathVariable("fileId") int fileId, User user) {
        Event result = cardDataService.deleteFile(fileId, user, new Date());
        eventEmitter.emitDeleteFile(cardRepository.findBy(result.getCardId()).getColumnId(), result.getCardId());
        return result;
    }

    @ExpectPermission(Permission.DELETE_FILE)
    @RequestMapping(value = "/api/card-data/undo/{eventId}/file", method = RequestMethod.POST)
    @ResponseBody
    public int undoDeleteFile(@PathVariable("eventId") int eventId, User user) {
        Event event = eventRepository.getEventById(eventId);
        Validate.isTrue(event.getEvent() == EventType.FILE_DELETE);

        cardDataService.undoDeleteFile(event);
        eventEmitter.emiteUndoDeleteFile(cardRepository.findBy(event.getCardId()).getColumnId(), event.getCardId());

        return event.getDataId();
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card/{cardId}/files", method = RequestMethod.GET)
    @ResponseBody
    public List<FileDataLight> findAllFiles(@PathVariable("cardId") int cardId) {
        return cardDataRepository.findAllFilesByCardId(cardId);
    }

    // -- activity extension
    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card-data/activity/{id}", method = RequestMethod.GET)
    @ResponseBody
    public CardData getCardDataById(@PathVariable("id") int id) {
        return cardDataRepository.getDataLightById(id);
    }

    @Getter
    @Setter
    public static class Content {
        private String content;
    }

    @Getter
    @Setter
    public static class OrderData {
        private List<Integer> newContainer;
    }

}