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

Java tutorial

Introduction

Here is the source code for io.lavagna.web.api.CardController.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 static io.lavagna.service.SearchFilter.FilterType;
import static io.lavagna.service.SearchFilter.ValueType;
import static io.lavagna.service.SearchFilter.filter;
import io.lavagna.model.Board;
import io.lavagna.model.BoardColumn;
import io.lavagna.model.BoardColumn.BoardColumnLocation;
import io.lavagna.model.BoardColumnDefinition;
import io.lavagna.model.Card;
import io.lavagna.model.CardFull;
import io.lavagna.model.CardFullWithCounts;
import io.lavagna.model.CardLabel;
import io.lavagna.model.ColumnDefinition;
import io.lavagna.model.Event;
import io.lavagna.model.LabelListValue;
import io.lavagna.model.LabelListValueWithMetadata;
import io.lavagna.model.MilestoneCount;
import io.lavagna.model.Pair;
import io.lavagna.model.Permission;
import io.lavagna.model.Project;
import io.lavagna.model.ProjectAndBoard;
import io.lavagna.model.SearchResults;
import io.lavagna.model.User;
import io.lavagna.model.UserWithPermission;
import io.lavagna.service.BoardColumnRepository;
import io.lavagna.service.BoardRepository;
import io.lavagna.service.CardLabelRepository;
import io.lavagna.service.CardRepository;
import io.lavagna.service.CardService;
import io.lavagna.service.EventEmitter;
import io.lavagna.service.ProjectService;
import io.lavagna.service.SearchFilter;
import io.lavagna.service.SearchFilter.SearchFilterValue;
import io.lavagna.service.SearchService;
import io.lavagna.service.StatisticsService;
import io.lavagna.web.api.model.MilestoneDetail;
import io.lavagna.web.api.model.MilestoneInfo;
import io.lavagna.web.api.model.Milestones;
import io.lavagna.web.helper.ExpectPermission;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import lombok.Getter;
import lombok.Setter;

import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
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.RestController;

@RestController
public class CardController {

    private final CardRepository cardRepository;
    private final CardService cardService;
    private final CardLabelRepository cardLabelRepository;
    private final BoardRepository boardRepository;
    private final ProjectService projectService;
    private final BoardColumnRepository boardColumnRepository;
    private final StatisticsService statisticsService;
    private final SearchService searchService;
    private final EventEmitter eventEmitter;

    public CardController(CardRepository cardRepository, CardService cardService,
            CardLabelRepository cardLabelRepository, BoardRepository boardRepository, ProjectService projectService,
            BoardColumnRepository boardColumnRepository, StatisticsService statisticsService,
            SearchService searchService, EventEmitter eventEmitter) {
        this.cardRepository = cardRepository;
        this.cardService = cardService;
        this.cardLabelRepository = cardLabelRepository;
        this.boardRepository = boardRepository;
        this.projectService = projectService;
        this.boardColumnRepository = boardColumnRepository;
        this.statisticsService = statisticsService;
        this.searchService = searchService;
        this.eventEmitter = eventEmitter;
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/column/{columnId}/card", method = RequestMethod.GET)
    public List<CardFullWithCounts> fetchAllInColumn(@PathVariable("columnId") int columnId) {
        return cardService.fetchAllInColumn(columnId);
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/project/{projectShortName}/cards-by-milestone", method = RequestMethod.GET)
    public Milestones findCardsByMilestone(@PathVariable("projectShortName") String projectShortName) {
        Project project = projectService.findByShortName(projectShortName);
        Map<Integer, Integer> milestoneToIndex = new HashMap<>();
        List<MilestoneInfo> milestones = new ArrayList<>();
        getMilestones(project.getId(), milestoneToIndex, milestones);

        for (MilestoneCount count : statisticsService.findCardsCountByMilestone(project.getId())) {
            MilestoneInfo md = milestones.get(milestoneToIndex.get(count.getMilestoneId()));
            md.getCardsCountByStatus().put(count.getColumnDefinition(), count.getCount());
        }

        Map<ColumnDefinition, Integer> statusColors = new EnumMap<>(ColumnDefinition.class);
        for (BoardColumnDefinition cd : projectService.findColumnDefinitionsByProjectId(project.getId())) {
            statusColors.put(cd.getValue(), cd.getColor());
        }

        return new Milestones(milestones, statusColors);
    }

    private void getMilestones(int projectId, Map<Integer, Integer> milestoneToIndex,
            List<MilestoneInfo> milestones) {
        CardLabel label = cardLabelRepository.findLabelByName(projectId, "MILESTONE", CardLabel.LabelDomain.SYSTEM);
        List<LabelListValueWithMetadata> listValues = cardLabelRepository.findListValuesByLabelId(label.getId());
        int foundUnassignedIndex = -1;
        int mIndex = 0;
        for (LabelListValue milestone : listValues) {
            milestones
                    .add(new MilestoneInfo(milestone, new EnumMap<ColumnDefinition, Long>(ColumnDefinition.class)));
            milestoneToIndex.put(milestone.getId(), mIndex);
            if ("Unassigned".equals(milestone.getValue())) {
                foundUnassignedIndex = mIndex;
            }
            mIndex++;
        }
        if (foundUnassignedIndex < 0) {
            LabelListValue unassigned = new LabelListValue(-1, 0, Integer.MAX_VALUE, "Unassigned");
            milestones.add(
                    new MilestoneInfo(unassigned, new EnumMap<ColumnDefinition, Long>(ColumnDefinition.class)));
            milestoneToIndex.put(null, milestoneToIndex.size());
        } else {
            milestoneToIndex.put(null, foundUnassignedIndex);
        }
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/project/{projectShortName}/cards-by-milestone-detail/{milestone}", method = RequestMethod.GET)
    public MilestoneDetail findCardsByMilestoneDetail(@PathVariable("projectShortName") String projectShortName,
            @PathVariable("milestone") String milestone, UserWithPermission user) {

        int projectId = projectService.findByShortName(projectShortName).getId();
        CardLabel label = cardLabelRepository.findLabelByName(projectId, "MILESTONE", CardLabel.LabelDomain.SYSTEM);
        List<LabelListValueWithMetadata> listValues = cardLabelRepository
                .findListValuesByLabelIdAndValue(label.getId(), milestone);

        SearchFilter filter;
        Map<Long, Pair<Long, Long>> assignedAndClosedCards;

        if (listValues.size() > 0) {
            filter = filter(FilterType.MILESTONE, ValueType.STRING, milestone);
            assignedAndClosedCards = statisticsService.getAssignedAndClosedCardsByMilestone(listValues.get(0),
                    DateUtils.addWeeks(DateUtils.truncate(new Date(), Calendar.DATE), -2));
        } else {
            filter = filter(FilterType.MILESTONE, ValueType.UNASSIGNED, null);
            assignedAndClosedCards = null;
        }

        SearchFilter notTrashFilter = filter(SearchFilter.FilterType.NOTLOCATION, SearchFilter.ValueType.STRING,
                BoardColumnLocation.TRASH.toString());

        SearchResults cards = searchService.find(Arrays.asList(filter, notTrashFilter), projectId, null, user);
        return new MilestoneDetail(cards, assignedAndClosedCards);
    }

    /**
     * Return the latest 11 card in a given location, ordered by mutation time. (11 so the user can paginate 10 at the
     * time and know if there are more).
     *
     * @param shortName
     * @param location
     * @param page
     * @return
     */
    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/board/{shortName}/cards-in/{location}/{page}", method = RequestMethod.GET)
    public List<CardFullWithCounts> fetchPaginatedIn(@PathVariable("shortName") String shortName,
            @PathVariable("location") BoardColumnLocation location, @PathVariable("page") int page) {
        int boardId = boardRepository.findBoardIdByShortName(shortName);
        return cardService.fetchPaginatedByBoardIdAndLocation(boardId, location, page);
    }

    private void emitCreateCard(int columnId, Card createdCard) {
        ProjectAndBoard projectAndBoard = boardRepository.findProjectAndBoardByColumnId(columnId);
        eventEmitter.emitCreateCard(projectAndBoard.getProject().getShortName(),
                projectAndBoard.getBoard().getShortName(), columnId, createdCard.getId());
    }

    // TODO: check that columnId is effectively inside the board named shortName
    @ExpectPermission(Permission.CREATE_CARD)
    @RequestMapping(value = "/api/column/{columnId}/card", method = RequestMethod.POST)
    public void create(@PathVariable("columnId") int columnId, @RequestBody CardData card, User user) {
        Card createdCard = cardService.createCard(card.name, columnId, new Date(), user);
        emitCreateCard(columnId, createdCard);
    }

    @ExpectPermission(Permission.CREATE_CARD)
    @RequestMapping(value = "/api/card/{cardId}/clone/{columnId}", method = RequestMethod.POST)
    public void clone(@PathVariable("cardId") int cardId, @PathVariable("columnId") int columnId, User user) {
        Card clonedCard = cardService.cloneCard(cardId, columnId, user);
        emitCreateCard(columnId, clonedCard);
    }

    @ExpectPermission(Permission.CREATE_CARD)
    @RequestMapping(value = "/api/column/{columnId}/card-top", method = RequestMethod.POST)
    public void createCardFromTop(@PathVariable("columnId") int columnId, @RequestBody CardData card, User user) {
        Card createdCard = cardService.createCardFromTop(card.name, columnId, new Date(), user);
        emitCreateCard(columnId, createdCard);
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card/{cardId}", method = RequestMethod.GET)
    public CardFull findCardById(@PathVariable("cardId") int id) {
        return cardRepository.findFullBy(id);
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card-by-seq/{boardShortName:[A-Z0-9_]+}-{seqNr:[0-9]+}", method = RequestMethod.GET)
    public CardFull findCardIdByBoardNameAndSeq(@PathVariable("boardShortName") String boardShortName,
            @PathVariable("seqNr") int seqNr) {
        return cardRepository.findFullBy(boardShortName, seqNr);
    }

    @ExpectPermission(Permission.READ)
    @RequestMapping(value = "/api/card/{cardId}/activity", method = RequestMethod.GET)
    public List<Event> getCardActivity(@PathVariable("cardId") int id) {
        return cardRepository.fetchAllActivityByCardId(id);
    }

    @ExpectPermission(Permission.UPDATE_CARD)
    @RequestMapping(value = "/api/card/{cardId}", method = RequestMethod.POST)
    public void updateCard(@PathVariable("cardId") int id, @RequestBody CardData updateCard, User user) {
        cardService.updateCard(id, updateCard.name, user, new Date());

        Card c = cardRepository.findBy(id);
        ProjectAndBoard projectAndBoard = boardRepository.findProjectAndBoardByColumnId(c.getColumnId());
        eventEmitter.emitUpdateCard(projectAndBoard.getProject().getShortName(),
                projectAndBoard.getBoard().getShortName(), c.getColumnId(), id);
    }

    @ExpectPermission(Permission.MOVE_CARD)
    @RequestMapping(value = "/api/card/{cardId}/from-column/{previousColumnId}/to-column/{newColumnId}", method = RequestMethod.POST)
    public Event moveCardToColumn(@PathVariable("cardId") int id,
            @PathVariable("previousColumnId") int previousColumnId, @PathVariable("newColumnId") int newColumnId,
            @RequestBody ColumnOrders columnOrders, User user) {

        //
        BoardColumn prevCol = boardColumnRepository.findById(previousColumnId);
        BoardColumn newCol = boardColumnRepository.findById(newColumnId);
        Card c = cardRepository.findBy(id);
        Validate.isTrue(c.getColumnId() == prevCol.getId(), "card must be inside previous column");
        Validate.isTrue(prevCol.getBoardId() == newCol.getBoardId(), "can only move inside the same board");
        //

        Event event = cardService.moveCardToColumnAndReorder(id, //
                previousColumnId, newColumnId, columnOrders.newContainer, user);

        //
        eventEmitter.emitUpdateCardPosition(previousColumnId);
        eventEmitter.emitUpdateCardPosition(newColumnId);
        //

        Board board = boardRepository.findBoardById(prevCol.getBoardId());

        if (prevCol.getLocation() != BoardColumnLocation.BOARD) {
            eventEmitter.emitMoveCardFromOutsideOfBoard(board.getShortName(), prevCol.getLocation());
        }
        eventEmitter.emitCardHasMoved(
                projectService.findRelatedProjectShortNameByBoardShortname(board.getShortName()),
                board.getShortName(), Collections.singletonList(id));

        return event;
    }

    @ExpectPermission(Permission.MOVE_CARD)
    @RequestMapping(value = "/api/card/{cardId}/from-column/{previousColumnId}/to-column-end/{newColumnId}", method = RequestMethod.POST)
    public Event moveCardToColumn(@PathVariable("cardId") int id,
            @PathVariable("previousColumnId") int previousColumnId, @PathVariable("newColumnId") int newColumnId,
            User user) {
        List<CardFullWithCounts> cards = cardService.fetchAllInColumn(newColumnId);
        List<Integer> order = new ArrayList<>();
        for (CardFullWithCounts card : cards) {
            order.add(card.getId());
        }
        order.add(id);
        ColumnOrders orders = new ColumnOrders();
        orders.setNewContainer(order);

        return moveCardToColumn(id, previousColumnId, newColumnId, orders, user);
    }

    @ExpectPermission(Permission.MOVE_CARD)
    @RequestMapping(value = "/api/column/{columnId}/order", method = RequestMethod.POST)
    public boolean updateCardOrder(@PathVariable("columnId") int columnId, @RequestBody List<Number> cardIds) {
        cardRepository.updateCardOrder(Utils.from(cardIds), columnId);
        eventEmitter.emitUpdateCardPosition(columnId);
        return true;
    }

    @ExpectPermission(Permission.MOVE_CARD)
    @RequestMapping(value = "/api/card/from-column/{previousColumnId}/to-location/{location}", method = RequestMethod.POST)
    public void moveCardsToLocation(@PathVariable("previousColumnId") int previousColumnId,
            @PathVariable("location") BoardColumnLocation location, @RequestBody CardIds cardIds, User user) {
        Validate.isTrue(location != BoardColumnLocation.BOARD);
        Validate.isTrue(!cardIds.cardIds.isEmpty());
        BoardColumn col = boardColumnRepository
                .findById(cardRepository.findBy(cardIds.cardIds.get(0)).getColumnId());

        // Validate.isTrue(col.getLocation() == BoardColumnLocation.BOARD);

        BoardColumn destination = boardColumnRepository.findDefaultColumnFor(col.getBoardId(), location);

        Validate.isTrue(col.getLocation() != destination.getLocation());

        cardService.moveCardsToColumn(cardIds.cardIds, previousColumnId, destination.getId(), user.getId(),
                BoardColumnLocation.MAPPING.get(location), new Date());

        eventEmitter.emitUpdateCardPosition(previousColumnId);

        String boardShortName = boardRepository.findBoardById(destination.getBoardId()).getShortName();

        if (col.getLocation() == BoardColumnLocation.BOARD) {
            eventEmitter.emitMoveCardOutsideOfBoard(boardShortName, location);
        } else {
            eventEmitter.emitMoveCardFromOutsideOfBoard(boardShortName, col.getLocation());
            eventEmitter.emitMoveCardFromOutsideOfBoard(boardShortName, location);
        }

        eventEmitter.emitCardHasMoved(projectService.findRelatedProjectShortNameByBoardShortname(boardShortName),
                boardShortName, cardIds.cardIds);
    }

    private static final List<SearchFilter> TO_ME_STATUS_OPEN = Arrays.asList(
            new SearchFilter(FilterType.ASSIGNED, "to", new SearchFilterValue(ValueType.CURRENT_USER, "me")),
            SearchFilter.filter(FilterType.STATUS, ValueType.STRING, "OPEN"));

    @ExpectPermission(Permission.SEARCH)
    @RequestMapping(value = "/api/self/cards/{page}", method = RequestMethod.GET)
    public SearchResults getOpenCards(@PathVariable(value = "page") int page, UserWithPermission user) {
        return searchService.find(TO_ME_STATUS_OPEN, null, null, user, page);
    }

    @ExpectPermission(Permission.SEARCH)
    @RequestMapping(value = "/api/self/project/{projectShortName}/cards/{page}", method = RequestMethod.GET)
    public SearchResults getOpenCardsByProjectShortName(@PathVariable(value = "projectShortName") String shortName,
            @PathVariable(value = "page") int page, UserWithPermission user) {
        return searchService.find(TO_ME_STATUS_OPEN, projectService.findByShortName(shortName).getId(), null, user,
                page);
    }

    @Getter
    @Setter
    public static class CardData {
        private String name;
    }

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

    @Getter
    @Setter
    public static class CardIds {
        private List<Integer> cardIds;
    }
}