/**
* 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.model.*;
import io.lavagna.model.CardLabel.LabelType;
import io.lavagna.model.util.CalendarTokenNotFoundException;
import io.lavagna.service.calendarutils.CalendarEventHandler;
import io.lavagna.service.calendarutils.CalendarEvents;
import io.lavagna.service.calendarutils.CalendarVEventHandler;
import io.lavagna.service.calendarutils.StandardCalendarEventHandler;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Method;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.Version;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.URISyntaxException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.*;
import static io.lavagna.common.Constants.DATE_FORMAT;
import static io.lavagna.common.Constants.SYSTEM_LABEL_MILESTONE;
import static io.lavagna.service.SearchFilter.filter;
@Service
@Transactional(readOnly = true)
public class CalendarService {
private final ConfigurationRepository configurationRepository;
private final SearchService searchService;
private final UserRepository userRepository;
private final UserService userService;
private final CardDataService cardDataService;
private final ProjectService projectService;
private final CardLabelRepository cardLabelRepository;
public CalendarService(ConfigurationRepository configurationRepository, SearchService searchService,
UserService userService, UserRepository userRepository, CardDataService cardDataService,
ProjectService projectService, CardLabelRepository cardLabelRepository) {
this.configurationRepository = configurationRepository;
this.searchService = searchService;
this.userRepository = userRepository;
this.userService = userService;
this.cardDataService = cardDataService;
this.projectService = projectService;
this.cardLabelRepository = cardLabelRepository;
}
@Transactional(readOnly = false)
public void setCalendarFeedDisabled(User user, boolean isDisabled) {
userRepository.setCalendarFeedDisabled(user, isDisabled);
}
@Transactional(readOnly = false)
public CalendarInfo findCalendarInfoFromUser(User user) {
try {
return userRepository.findCalendarInfoFromUserId(user);
} catch (CalendarTokenNotFoundException ex) {
String token = UUID.randomUUID().toString();// <- this use secure random
String hashedToken = DigestUtils.sha256Hex(token);
userRepository.registerCalendarToken(user, hashedToken);
return findCalendarInfoFromUser(user);
}
}
private UserWithPermission findUserFromCalendarToken(String token) {
int userId = userRepository.findUserIdFromCalendarToken(token);
return userService.findUserWithPermission(userId);
}
private void getMilestoneEventsFromProject(CalendarEventHandler handler, UserWithPermission user, Project project)
throws ParseException, URISyntaxException {
CardLabel milestoneLabel = cardLabelRepository.findLabelByName(project.getId(), SYSTEM_LABEL_MILESTONE,
CardLabel.LabelDomain.SYSTEM);
for (LabelListValueWithMetadata m : cardLabelRepository.findListValuesByLabelId(milestoneLabel.getId())) {
if (m.getMetadata().containsKey("releaseDate")) {
Date date = DateUtils.parseDate(m.getMetadata().get("releaseDate"),
DATE_FORMAT, "dd.MM.yyyy");
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.setTime(date);
cal.set(java.util.Calendar.HOUR, 12);
cal.set(java.util.Calendar.MINUTE, 0);
SearchFilter filter = filter(SearchFilter.FilterType.MILESTONE, SearchFilter.ValueType.STRING,
m.getValue());
SearchFilter notTrashFilter = filter(SearchFilter.FilterType.NOTLOCATION,
SearchFilter.ValueType.STRING, BoardColumn.BoardColumnLocation.TRASH.toString());
SearchResults cards = searchService.find(Arrays.asList(filter, notTrashFilter), project.getId(),
null, user);
handler.addMilestoneEvent(project.getShortName(), new Timestamp(cal.getTimeInMillis()), m, cards);
}
}
}
private void addMilestoneEvents(CalendarEventHandler handler, UserWithPermission user)
throws URISyntaxException, ParseException {
List<Project> projects = projectService.findAllProjects(user);
for (Project project : projects) {
getMilestoneEventsFromProject(handler, user, project);
}
}
private void addCardEvents(CalendarEventHandler handler, UserWithPermission user)
throws URISyntaxException, ParseException {
Map<Integer, CardFullWithCounts> map = new LinkedHashMap<>();
SearchFilter locationFilter = filter(SearchFilter.FilterType.LOCATION, SearchFilter.ValueType.STRING,
BoardColumn.BoardColumnLocation.BOARD.toString());
SearchFilter aFilter = filter(SearchFilter.FilterType.ASSIGNED, SearchFilter.ValueType.CURRENT_USER, "me");
for (CardFullWithCounts card : searchService.find(Arrays.asList(locationFilter, aFilter), null, null, user)
.getFound()) {
map.put(card.getId(), card);
}
SearchFilter wFilter = filter(SearchFilter.FilterType.WATCHED_BY, SearchFilter.ValueType.CURRENT_USER, "me");
for (CardFullWithCounts card : searchService.find(Arrays.asList(locationFilter, wFilter), null, null, user)
.getFound()) {
map.put(card.getId(), card);
}
for (CardFullWithCounts card : map.values()) {
for (LabelAndValue lav : card.getLabelsWithType(LabelType.TIMESTAMP)) {
handler.addCardEvent(card, lav);
}
}
}
public CalendarEvents getProjectCalendar(String projectShortName, UserWithPermission user)
throws URISyntaxException, ParseException {
final CalendarEvents events = new CalendarEvents(new HashMap<Date, CalendarEvents.MilestoneDayEvents>());
final CalendarEventHandler handler = new StandardCalendarEventHandler(events);
Project project = projectService.findByShortName(projectShortName);
// Milestones
getMilestoneEventsFromProject(handler, user, project);
// Cards
SearchFilter locationFilter = filter(SearchFilter.FilterType.LOCATION, SearchFilter.ValueType.STRING,
BoardColumn.BoardColumnLocation.BOARD.toString());
for (CardFullWithCounts card : searchService.find(Arrays.asList(locationFilter), project.getId(), null, user)
.getFound()) {
for (LabelAndValue lav : card.getLabelsWithType(LabelType.TIMESTAMP)) {
handler.addCardEvent(card, lav);
}
}
return events;
}
public CalendarEvents getUserCalendar(UserWithPermission user) throws URISyntaxException, ParseException {
final CalendarEvents events = new CalendarEvents(new HashMap<Date, CalendarEvents.MilestoneDayEvents>());
final CalendarEventHandler handler = new StandardCalendarEventHandler(events);
// Milestones
addMilestoneEvents(handler, user);
// Cards
addCardEvents(handler, user);
return events;
}
public Calendar getCalDavCalendar(String userToken) throws URISyntaxException, ParseException {
UserWithPermission user;
try {
user = findUserFromCalendarToken(userToken);
} catch (EmptyResultDataAccessException ex) {
throw new SecurityException("Invalid token");
}
if (userRepository.isCalendarFeedDisabled(user)) {
throw new SecurityException("Calendar feed disabled");
}
final Calendar calendar = new Calendar();
calendar.getProperties().add(new ProdId("-//Lavagna//iCal4j 1.0//EN"));
calendar.getProperties().add(Version.VERSION_2_0);
calendar.getProperties().add(CalScale.GREGORIAN);
calendar.getProperties().add(Method.PUBLISH);
final List<VEvent> events = new ArrayList<>();
final String applicationUrl = StringUtils
.appendIfMissing(configurationRepository.getValue(Key.BASE_APPLICATION_URL), "/");
final CalendarEventHandler handler = new CalendarVEventHandler(applicationUrl, cardDataService, userRepository,
events);
// Milestones
addMilestoneEvents(handler, user);
// Cards
addCardEvents(handler, user);
calendar.getComponents().addAll(events);
return calendar;
}
}