Skip to content

Commit

Permalink
fix(backlog#10): implement notification to call projects for news
Browse files Browse the repository at this point in the history
  • Loading branch information
Titohma authored and Thomah committed Mar 12, 2023
1 parent 7b8a638 commit 434054e
Show file tree
Hide file tree
Showing 19 changed files with 157 additions and 38 deletions.
2 changes: 1 addition & 1 deletion pom-cluecumber.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<groupId>fr.les-projets-cagnottes</groupId>
<artifactId>core</artifactId>
<version>0.15.2</version>
<version>0.16.0</version>
<name>Les Projets Cagnottes - Core</name>
<description>Les Projets Cagnottes - Main API Component</description>

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</parent>
<groupId>fr.les-projets-cagnottes</groupId>
<artifactId>core</artifactId>
<version>0.15.2</version>
<version>0.16.0</version>
<name>Les Projets Cagnottes - Core</name>
<description>Les Projets Cagnottes - Main API Component</description>
<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import fr.lesprojetscagnottes.core.donation.task.DonationProcessingTask;
import fr.lesprojetscagnottes.core.notification.model.NotificationName;
import fr.lesprojetscagnottes.core.notification.service.NotificationService;
import fr.lesprojetscagnottes.core.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -30,8 +29,6 @@ public class CampaignScheduler {
@Value("${fr.lesprojetscagnottes.web.url}")
private String webUrl;

private final UserService userService;

private final DonationProcessingTask donationProcessingTask;

private final CampaignRepository campaignRepository;
Expand All @@ -41,12 +38,10 @@ public class CampaignScheduler {
private final NotificationService notificationService;

@Autowired
public CampaignScheduler(UserService userService,
DonationProcessingTask donationProcessingTask,
public CampaignScheduler(DonationProcessingTask donationProcessingTask,
CampaignRepository campaignRepository,
DonationRepository donationRepository,
NotificationService notificationService) {
this.userService = userService;
this.donationProcessingTask = donationProcessingTask;
this.campaignRepository = campaignRepository;
this.donationRepository = donationRepository;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package fr.lesprojetscagnottes.core.news.controller;

import com.google.gson.Gson;
import fr.lesprojetscagnottes.core.common.exception.BadRequestException;
import fr.lesprojetscagnottes.core.common.exception.ForbiddenException;
import fr.lesprojetscagnottes.core.common.exception.NotFoundException;
import fr.lesprojetscagnottes.core.news.entity.NewsEntity;
import fr.lesprojetscagnottes.core.news.model.NewsModel;
import fr.lesprojetscagnottes.core.news.model.NewsType;
import fr.lesprojetscagnottes.core.news.repository.NewsRepository;
import fr.lesprojetscagnottes.core.organization.entity.OrganizationEntity;
import fr.lesprojetscagnottes.core.organization.repository.OrganizationRepository;
Expand All @@ -29,6 +29,7 @@
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;

Expand All @@ -38,9 +39,6 @@
@Tag(name = "News", description = "The News API")
public class NewsController {

@Autowired
private Gson gson;

@Autowired
private OrganizationRepository organizationRepository;

Expand Down Expand Up @@ -158,6 +156,12 @@ public NewsModel create(Principal principal, @RequestBody NewsModel news) {
throw new ForbiddenException();
}

// Update project if necessary
if(project != null && news.getType().equals(NewsType.ARTICLE)) {
project.setLastStatusUpdate(new Date());
project = projectRepository.save(project);
}

// Save news
NewsEntity newsToSave = new NewsEntity();
newsToSave.setType(news.getType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,14 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface NewsRepository extends JpaRepository<NewsEntity, Long> {
import java.util.Date;

@Query(nativeQuery = true,
value = "select c.* from projects c " +
"inner join projects_organizations on c.id = projects_organizations.project_id " +
"inner join organizations o on projects_organizations.organization_id = o.id " +
"inner join organizations_users on organizations_users.organization_id = o.id " +
"inner join users u on u.id = organizations_users.user_id " +
"where u.id = ?1 and c.status IN (?2) --#pageable\n",
countQuery = "select count(*) from projects c " +
"inner join projects_organizations on c.id = projects_organizations.project_id " +
"inner join organizations o on projects_organizations.organization_id = o.id " +
"inner join organizations_users on organizations_users.organization_id = o.id " +
"inner join users u on u.id = organizations_users.user_id " +
"where u.id = ?1 c.status IN (?2)")
Page<NewsEntity> findAllByUser(Long userId, Pageable pageable);
public interface NewsRepository extends JpaRepository<NewsEntity, Long> {

Page<NewsEntity> findAllByOrganizationIdOrOrganizationIdIsNull(Long organizationId, Pageable pageable);

Page<NewsEntity> findAllByProjectId(Long id, Pageable pageable);

NewsEntity findFirstByProjectIdAndCreatedAtGreaterThanOrderByCreatedAtDesc(Long id, Date createdAt);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.springframework.stereotype.Service;

import java.security.Principal;
import java.util.Date;

@Slf4j
@Service
Expand Down Expand Up @@ -61,4 +62,8 @@ public DataPage<NewsModel> listByProjects_Id(Principal principal, Long id, int o
return models;
}

public NewsEntity findFirstByProjectIdAndCreatedAtGreaterThanOrderByCreatedAtDesc(Long id, Date createdAt) {
return newsRepository.findFirstByProjectIdAndCreatedAtGreaterThanOrderByCreatedAtDesc(id, createdAt);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package fr.lesprojetscagnottes.core.notification.model;

public enum NotificationName {
CAMPAIGN_STARTED, CAMPAIGN_REMINDER,PROJECT_PUBLISHED
CAMPAIGN_STARTED, CAMPAIGN_REMINDER,PROJECT_PUBLISHED,PROJECT_CALL_FOR_NEWS
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package fr.lesprojetscagnottes.core.project.model;

import fr.lesprojetscagnottes.core.common.GenericModel;
import fr.lesprojetscagnottes.core.common.audit.AuditEntity;
import fr.lesprojetscagnottes.core.common.strings.StringsCommon;
import fr.lesprojetscagnottes.core.common.GenericModel;
import fr.lesprojetscagnottes.core.project.entity.ProjectEntity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

import jakarta.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;

Expand All @@ -27,6 +28,9 @@ public class ProjectModel extends AuditEntity<String> {
@Enumerated(EnumType.STRING)
protected ProjectStatus status;

@Column(name = "last_status_update")
protected Date lastStatusUpdate;

@Column(name = "short_description")
protected String shortDescription;

Expand Down Expand Up @@ -63,6 +67,7 @@ public static ProjectModel fromEntity(ProjectEntity entity) {
model.setId(entity.getId());
model.setTitle(entity.getTitle());
model.setStatus(entity.getStatus());
model.setLastStatusUpdate(entity.getLastStatusUpdate());
model.setShortDescription(entity.getShortDescription());
model.setLongDescription(entity.getLongDescription());
model.setPeopleRequired(entity.getPeopleRequired());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.Date;
import java.util.Set;

public interface ProjectRepository extends JpaRepository<ProjectEntity, Long> {
Expand All @@ -18,6 +19,8 @@ public interface ProjectRepository extends JpaRepository<ProjectEntity, Long> {

Set<ProjectEntity> findAllByPeopleGivingTime_Id(Long memberId);

Set<ProjectEntity> findAllByStatusInAndLastStatusUpdateLessThan(Set<ProjectStatus> status, Date lastStatusUpdate);

Page<ProjectEntity> findAllByStatusIn(Set<ProjectStatus> status, Pageable pageable);

Page<ProjectEntity> findAllByOrganizationIdAndStatusIn(Long id, Set<ProjectStatus> status, Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package fr.lesprojetscagnottes.core.project.scheduler;

import fr.lesprojetscagnottes.core.common.date.DateUtils;
import fr.lesprojetscagnottes.core.news.entity.NewsEntity;
import fr.lesprojetscagnottes.core.news.service.NewsService;
import fr.lesprojetscagnottes.core.notification.model.NotificationName;
import fr.lesprojetscagnottes.core.notification.service.NotificationService;
import fr.lesprojetscagnottes.core.project.entity.ProjectEntity;
import fr.lesprojetscagnottes.core.project.model.ProjectStatus;
import fr.lesprojetscagnottes.core.project.repository.ProjectRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;

@Component
@Slf4j
public class ProjectScheduler {

@Value("${fr.lesprojetscagnottes.web.url}")
private String webUrl;

private final NewsService newsService;

private final NotificationService notificationService;

private final ProjectRepository projectRepository;

@Autowired
public ProjectScheduler(NewsService newsService,
NotificationService notificationService,
ProjectRepository projectRepository) {
this.newsService = newsService;
this.notificationService = notificationService;
this.projectRepository = projectRepository;
}

@Scheduled(cron = "${fr.lesprojetscagnottes.core.schedule.newsproject}")
public void notifyCallForNews() {
log.info("Calling for news has started");
Set<ProjectStatus> status = new LinkedHashSet<>();
status.add(ProjectStatus.IN_PROGRESS);
LocalDateTime now = LocalDateTime.now();
LocalDateTime nowMinus2Months = now.minusMonths(2);
Date nowMinus2MonthsDate = DateUtils.asDate(nowMinus2Months);
Set<ProjectEntity> projects = projectRepository.findAllByStatusInAndLastStatusUpdateLessThan(status, nowMinus2MonthsDate);
projects.forEach(project -> {
NewsEntity news = newsService.findFirstByProjectIdAndCreatedAtGreaterThanOrderByCreatedAtDesc(project.getId(), nowMinus2MonthsDate);
if(news == null) {
Map<String, Object> model = new HashMap<>();
model.put("_user_email_", project.getLeader().getEmail());
model.put("_organization_id_", project.getOrganization().getId());
model.put("user_fullname", project.getLeader().getFullname());
model.put("project_title", project.getTitle());
model.put("project_url", webUrl + "/projects/" + project.getId());
notificationService.create(NotificationName.PROJECT_CALL_FOR_NEWS, model, project.getOrganization().getId());
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@
import org.springframework.stereotype.Service;

import java.security.Principal;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;

@Slf4j
@Service
Expand Down Expand Up @@ -288,13 +285,13 @@ public void updateStatus(Principal principal, Long id, ProjectStatus status) {

// Update status
project.setStatus(status);
project.setLastStatusUpdate(new Date());
projectRepository.save(project);

// Prepare & send notifications
if(previousStatus.equals(ProjectStatus.DRAFT)
&& status.equals(ProjectStatus.IN_PROGRESS)) {
Map<String, Object> model = new HashMap<>();
model.put("user_username", userLoggedIn.getUsername());
model.put("user_fullname", userLoggedIn.getFullname());
model.put("project_title", project.getTitle());
model.put("project_url", webUrl + "/projects/" + project.getId());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package fr.lesprojetscagnottes.core.providers.slack;

import com.google.gson.Gson;
import fr.lesprojetscagnottes.core.notification.entity.NotificationEntity;
import fr.lesprojetscagnottes.core.notification.model.NotificationVariables;
import fr.lesprojetscagnottes.core.notification.service.NotificationService;
import fr.lesprojetscagnottes.core.providers.slack.entity.SlackNotificationEntity;
import fr.lesprojetscagnottes.core.providers.slack.entity.SlackTeamEntity;
import fr.lesprojetscagnottes.core.providers.slack.entity.SlackUserEntity;
import fr.lesprojetscagnottes.core.providers.slack.service.SlackClientService;
import fr.lesprojetscagnottes.core.providers.slack.service.SlackNotificationService;
import fr.lesprojetscagnottes.core.providers.slack.service.SlackTeamService;
import fr.lesprojetscagnottes.core.providers.slack.service.SlackUserService;
import fr.lesprojetscagnottes.core.user.entity.UserEntity;
import fr.lesprojetscagnottes.core.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -31,6 +38,15 @@ public class SlackNotificationScheduler {
@Autowired
private SlackTeamService slackTeamService;

@Autowired
private SlackUserService slackUserService;

@Autowired
private UserService userService;

@Autowired
private Gson gson;

@Autowired
private NotificationService notificationService;

Expand All @@ -50,8 +66,31 @@ public void processNotifications() {
slackNotification.setTeam(slackTeamService.findByOrganizationId(notification.getOrganization().getId()));
slackNotification = slackNotificationService.save(slackNotification);
}
if(!slackNotification.getSent()) {
if(slackNotification.getTeam() != null && !slackNotification.getSent()) {
log.debug("Sending Slack notification {}", slackNotification.getId());

NotificationVariables notificationVariables = gson.fromJson(notification.getVariables(), NotificationVariables.class);
if(notificationVariables.get("_user_email_") != null && notificationVariables.get("_organization_id_") != null) {
UserEntity user = userService.findByEmail(notificationVariables.get("_user_email_").toString());
log.debug("User : {}", user);
if(user != null) {
Long organizationId = Double.valueOf(notificationVariables.get("_organization_id_").toString()).longValue();
SlackTeamEntity slackTeam = slackTeamService.findByOrganizationId(organizationId);
log.debug("SlackTeam matching organization {} : {}", organizationId, slackTeam);
if(slackTeam != null) {
SlackUserEntity slackUser = slackUserService.findByUserIdAndSlackTeamId(user.getId(), slackTeam.getId());
log.debug("SlackUser matching user {} & slackteam {} : {}", user.getId(), slackTeam.getId(), slackUser);
if(slackUser != null) {
notificationVariables.put("user_tagname", "<@" + slackUser.getSlackId() + ">");
}
}
}
}
if(notificationVariables.get("user_tagname") == null) {
notificationVariables.put("user_tagname", "*" + notificationVariables.get("user_fullname") + "*");
}
notification.setVariables(gson.toJson(notificationVariables));

slackClientService.sendNotification(notification, slackNotification);
slackNotification.setSent(true);
slackNotificationService.save(slackNotification);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ public interface SlackUserRepository extends JpaRepository<SlackUserEntity, Long
@Transactional
void deleteAllBySlackTeamId(Long slackTeamId);

SlackUserEntity findByUserIdAndSlackTeamId(Long userId, Long slackTeamId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public SlackUserEntity findBySlackId(String slackId) {
return slackUserRepository.findBySlackId(slackId);
}

public SlackUserEntity findByUserIdAndSlackTeamId(Long userId, Long slackTeamId) {
return slackUserRepository.findByUserIdAndSlackTeamId(userId, slackTeamId);
}

public SlackUserEntity save(SlackUserEntity slackUser) {
return slackUserRepository.save(slackUser);
}
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ fr.lesprojetscagnottes.core.storage.root=files
fr.lesprojetscagnottes.core.storage.data=data
fr.lesprojetscagnottes.core.schedule.campaignfunding=0 0 * * * *
fr.lesprojetscagnottes.core.schedule.campaignalmostfinished=0 0 10 * * *
fr.lesprojetscagnottes.core.schedule.newsproject=0 0 10 * * MON

# Web Component
fr.lesprojetscagnottes.web.url=http://localhost:4200
Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/db/changelog/db.changelog-0.16.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
--liquibase formatted sql

--changeset lesprojetscagnottes:add-project-last_status_update
ALTER TABLE projects
ADD last_status_update timestamp without time zone DEFAULT now();
UPDATE projects SET last_status_update = updated_at;
--rollback alter table projects drop column last_status_update;
Loading

0 comments on commit 434054e

Please sign in to comment.