Skip to content

Commit

Permalink
Notification back end (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
haiphucnguyen authored Nov 22, 2024
1 parent 2cb06cf commit 5143a9e
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 9 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ parssonVersion="1.1.7"
springbootVersion = "3.3.5"
springDependencyManagementVersion="1.1.6"
jhisterVersion = "8.7.1"
j2HtmlVersion = "1.6.0"

[libraries]
jhipster-framework = { module = "tech.jhipster:jhipster-framework", version.ref = "jhisterVersion" }
Expand All @@ -29,6 +30,7 @@ mapstruct = { module = "org.mapstruct:mapstruct", version.ref = "mapstructVersio
mapstruct-processor = { module = "org.mapstruct:mapstruct-processor", version.ref = "mapstructVersion" }
jclouds = { module = "org.apache.jclouds:jclouds-all", version.ref = "jcloudsVersion" }
dot-env = { module = "io.github.cdimascio:dotenv-java", version.ref = "dotEnvVersion" }
j2html = {module = "com.j2html:j2html", version.ref="j2HtmlVersion"}
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logbackVersion" }
junit-bom = { module = "org.junit:junit-bom", version.ref = "junitVersion" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api" }
Expand Down
1 change: 1 addition & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ dependencies {
implementation("com.github.ben-manes.caffeine:caffeine")
implementation("com.zaxxer:HikariCP")
implementation(libs.dot.env)
implementation(libs.j2html)
implementation(libs.bundles.json)

implementation("jakarta.annotation:jakarta.annotation-api")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.flexwork.modules.collab.domain;

import io.flexwork.modules.usermanagement.domain.User;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "fw_notification")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Notification {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String content;

@Column(nullable = false, updatable = false, columnDefinition = "TIMESTAMPTZ")
private LocalDateTime createdAt;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

@Column(nullable = false)
private boolean isRead;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.flexwork.modules.collab.repository;

import io.flexwork.modules.collab.domain.Notification;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface NotificationRepository extends JpaRepository<Notification, Long> {

List<Notification> findByUserIdAndIsReadFalse(Long userId);

@Modifying
@Query("UPDATE Notification n SET n.isRead = true WHERE n.id IN :ids")
void markAsRead(@Param("ids") List<Long> ids);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.flexwork.modules.collab.service;

import io.flexwork.modules.collab.repository.NotificationRepository;
import io.flexwork.modules.collab.service.dto.NotificationDTO;
import io.flexwork.modules.collab.service.mapper.NotificationMapper;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Service
public class NotificationService {
private final NotificationRepository notificationRepository;

private final NotificationMapper notificationMapper;

public NotificationService(
NotificationRepository notificationRepository, NotificationMapper notificationMapper) {
this.notificationRepository = notificationRepository;
this.notificationMapper = notificationMapper;
}

@Transactional(readOnly = true)
public List<NotificationDTO> getUnreadNotificationsForUser(Long userId) {
return notificationRepository.findByUserIdAndIsReadFalse(userId).stream()
.map(notificationMapper::toDTO)
.toList();
}

@Transactional
public void markNotificationsAsRead(List<Long> notificationIds) {
notificationRepository.markAsRead(notificationIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.flexwork.modules.collab.service.dto;

import java.time.LocalDateTime;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class NotificationDTO {
private Long id;
private String content;
private LocalDateTime createdAt;
private Long userId;
private boolean isRead;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
@Data
@Builder
public class OrganizationDTO {
private Long id; // Organization ID
private String name; // Organization name
private String logoUrl; // Logo URL
private String slogan; // Organization slogan
private String description; // Description of the organization
private Set<TeamDTO> teams; // Set of team IDs
private Long id;
private String name;
private String logoUrl;
private String slogan;
private String description;
private Set<TeamDTO> teams;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.flexwork.modules.collab.service.event;

import io.flexwork.modules.teams.service.dto.TeamRequestDTO;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;

@Getter
public class NewTeamRequestCreatedEvent extends ApplicationEvent {
private TeamRequestDTO teamRequest;

public NewTeamRequestCreatedEvent(Object source, TeamRequestDTO teamRequest) {
super(source);
this.teamRequest = teamRequest;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.flexwork.modules.collab.service.listener;

import static j2html.TagCreator.*;

import io.flexwork.modules.collab.domain.Notification;
import io.flexwork.modules.collab.repository.NotificationRepository;
import io.flexwork.modules.collab.repository.TeamRepository;
import io.flexwork.modules.collab.service.event.NewTeamRequestCreatedEvent;
import io.flexwork.modules.teams.service.dto.TeamRequestDTO;
import io.flexwork.modules.usermanagement.domain.User;
import io.flexwork.modules.usermanagement.service.dto.UserWithTeamRoleDTO;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class TeamRequestEventListener {
private final NotificationRepository notificationRepository;
private final TeamRepository teamRepository;

public TeamRequestEventListener(
NotificationRepository notificationRepository, TeamRepository teamRepository) {
this.notificationRepository = notificationRepository;
this.teamRepository = teamRepository;
}

@EventListener
public void onNewTeamRequestCreated(NewTeamRequestCreatedEvent event) {
TeamRequestDTO teamRequestDTO = event.getTeamRequest();
String html =
p(
text("A new "),
a("ticket request").withHref("#"),
text(" has been just created by "),
a("user").withHref("#"))
.render();

List<UserWithTeamRoleDTO> usersInTeam =
teamRepository.findUsersByTeamId(teamRequestDTO.getTeamId());
List<Notification> notifications =
usersInTeam.stream()
.map(
user ->
Notification.builder()
.content(html)
.user(User.builder().id(user.getId()).build())
.isRead(false)
.createdAt(LocalDateTime.now())
.build())
.toList();
notificationRepository.saveAll(notifications);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.flexwork.modules.collab.service.mapper;

import io.flexwork.modules.collab.domain.Notification;
import io.flexwork.modules.collab.service.dto.NotificationDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface NotificationMapper {

@Mapping(source = "user.id", target = "userId")
NotificationDTO toDTO(Notification notification);

@Mapping(source = "userId", target = "user.id")
Notification toEntity(NotificationDTO notificationDTO);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.flexwork.modules.collab.web.rest;

import io.flexwork.modules.collab.service.NotificationService;
import io.flexwork.modules.collab.service.dto.NotificationDTO;
import java.util.List;
import lombok.Data;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/notifications")
public class NotificationController {
private final NotificationService notificationService;

public NotificationController(NotificationService notificationService) {
this.notificationService = notificationService;
}

@PostMapping("/mark-read")
public ResponseEntity<Void> markNotificationsAsRead(@RequestBody MarkReadRequest request) {
if (request.getNotificationIds() == null || request.getNotificationIds().isEmpty()) {
return ResponseEntity.badRequest().build();
}

notificationService.markNotificationsAsRead(request.getNotificationIds());
return ResponseEntity.noContent().build();
}

@GetMapping("/unread")
public ResponseEntity<List<NotificationDTO>> getUnreadNotifications(
@RequestParam("userId") Long userId) {
List<NotificationDTO> notifications =
notificationService.getUnreadNotificationsForUser(userId);
return ResponseEntity.ok(notifications);
}

@Data
public static class MarkReadRequest {
private List<Long> notificationIds;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static io.flexwork.query.QueryUtils.createSpecification;

import io.flexwork.modules.collab.service.event.NewTeamRequestCreatedEvent;
import io.flexwork.modules.teams.domain.TeamRequest;
import io.flexwork.modules.teams.repository.TeamRequestRepository;
import io.flexwork.modules.teams.service.dto.TeamRequestDTO;
Expand All @@ -11,6 +12,7 @@
import java.util.Optional;
import org.jclouds.rest.ResourceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
Expand All @@ -23,12 +25,16 @@ public class TeamRequestService {

private final TeamRequestRepository teamRequestRepository;
private final TeamRequestMapper teamRequestMapper;
private final ApplicationEventPublisher eventPublisher;

@Autowired
public TeamRequestService(
TeamRequestRepository teamRequestRepository, TeamRequestMapper teamRequestMapper) {
TeamRequestRepository teamRequestRepository,
TeamRequestMapper teamRequestMapper,
ApplicationEventPublisher eventPublisher) {
this.teamRequestRepository = teamRequestRepository;
this.teamRequestMapper = teamRequestMapper;
this.eventPublisher = eventPublisher;
}

@Transactional(readOnly = true)
Expand Down Expand Up @@ -60,7 +66,9 @@ public TeamRequestDTO createTeamRequest(TeamRequestDTO teamRequestDTO) {
TeamRequest teamRequest = teamRequestMapper.toEntity(teamRequestDTO);
teamRequest.setCreatedDate(LocalDateTime.now());
teamRequest = teamRequestRepository.save(teamRequest);
return teamRequestMapper.toDto(teamRequest);
TeamRequestDTO savedTeamRequestDTO = teamRequestMapper.toDto(teamRequest);
eventPublisher.publishEvent(new NewTeamRequestCreatedEvent(this, savedTeamRequestDTO));
return savedTeamRequestDTO;
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@
referencedTableName="fw_organization" referencedColumnNames="id"
onDelete="CASCADE" constraintName="fk_teams_organization" />


<createTable tableName="fw_user_team">
<column name="user_id" type="BIGINT">
<constraints nullable="false" />
Expand Down Expand Up @@ -229,6 +228,28 @@
baseTableName="fw_comment" baseColumnNames="created_by"
constraintName="fk_comment_user" referencedTableName="fw_user"
referencedColumnNames="id" />

<createTable tableName="fw_notification">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false" />
</column>
<column name="content" type="TEXT">
<constraints nullable="false" />
</column>
<column name="created_at" type="timestamptz"
defaultValueComputed="CURRENT_TIMESTAMP" />
<column name="user_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="is_read" type="BOOLEAN" defaultValue="false">
<constraints nullable="false" />
</column>
</createTable>

<addForeignKeyConstraint
baseTableName="fw_notification" baseColumnNames="user_id"
constraintName="fk_notification_user" referencedTableName="fw_user"
referencedColumnNames="id" />
</changeSet>

<changeSet author="flexapp"
Expand Down

0 comments on commit 5143a9e

Please sign in to comment.