diff --git a/docker-compose.yml b/docker-compose.yml
index 7a56b2a..f9b75e8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,6 +8,22 @@ services:
- MYSQL_PASSWORD=${DB_PASSWORD}
ports:
- "3302:3306"
+
+ kafka:
+ image: wurstmeister/kafka
+ ports:
+ - "9092:9092"
+ environment:
+ - KAFKA_ADVERTISED_HOST_NAME=kafka
+ - KAFKA_ADVERTISED_PORT=9092
+ - KAFKA_CREATE_TOPICS=topic1:1:1
+ - KAFKA_ZOOKEEPER_CONNECT=${KAFKA_ZOOKEEPER_CONNECT}
+
+ zookeeper:
+ image: wurstmeister/zookeeper
+ ports:
+ - "2181:2181"
+
app:
build:
context: .
@@ -30,4 +46,5 @@ services:
ports:
- "8080:8080"
depends_on:
+ - kafka
- mysql
diff --git a/pom.xml b/pom.xml
index ee6261f..1cc2f4e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,6 +1,6 @@
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
org.springframework.boot
@@ -74,6 +74,15 @@
firebase-admin
9.1.1
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
+ org.springframework.kafka
+ spring-kafka
+
@@ -113,4 +122,4 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/com/rm/mynotes/model/Note.java b/src/main/java/com/rm/mynotes/model/Note.java
index 259b2a9..463f74f 100644
--- a/src/main/java/com/rm/mynotes/model/Note.java
+++ b/src/main/java/com/rm/mynotes/model/Note.java
@@ -12,6 +12,8 @@
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.List;
@Data
@Builder
@@ -40,6 +42,8 @@ public class Note {
@NotNull
private OffsetDateTime lastUpdate;
+ private List reminders = new ArrayList<>();
+
@NotNull
private OffsetDateTime createdAt;
@@ -53,5 +57,6 @@ public Note(NoteDTO noteDTO) {
this.description = noteDTO.getDescription();
this.createdAt = CommonFunctions.getCurrentDatetime();
this.lastUpdate = CommonFunctions.getCurrentDatetime();
+ this.reminders = new ArrayList<>();
}
}
diff --git a/src/main/java/com/rm/mynotes/model/Notification.java b/src/main/java/com/rm/mynotes/model/Notification.java
new file mode 100644
index 0000000..e3e9610
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/model/Notification.java
@@ -0,0 +1,34 @@
+package com.rm.mynotes.model;
+
+import com.rm.mynotes.utils.constants.StatusTypes;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+import java.time.OffsetDateTime;
+import java.util.Set;
+
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+public class Notification {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull
+ private OffsetDateTime createdAt;
+
+ private Boolean wasRead = false;
+
+ private Set content;
+
+ @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
+ @JoinTable(name = "notification_reminder", joinColumns = @JoinColumn(name = "notification_id", referencedColumnName = "id"),
+ inverseJoinColumns = @JoinColumn(name = "reminder_id", referencedColumnName = "id")
+ )
+ private Reminder reminder;
+}
diff --git a/src/main/java/com/rm/mynotes/model/Reminder.java b/src/main/java/com/rm/mynotes/model/Reminder.java
new file mode 100644
index 0000000..5337600
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/model/Reminder.java
@@ -0,0 +1,35 @@
+package com.rm.mynotes.model;
+
+import com.rm.mynotes.utils.constants.StatusTypes;
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotNull;
+import java.time.OffsetDateTime;
+
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+public class Reminder {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @NotNull
+ private String title;
+
+ @NotNull
+ private OffsetDateTime createdAt;
+
+ @NotNull
+ private OffsetDateTime lastUpdate;
+
+ @NotNull
+ private OffsetDateTime reminderDate;
+
+ @Enumerated(EnumType.STRING)
+ private StatusTypes status;
+}
diff --git a/src/main/java/com/rm/mynotes/repository/NotificationRepository.java b/src/main/java/com/rm/mynotes/repository/NotificationRepository.java
new file mode 100644
index 0000000..5b666ae
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/repository/NotificationRepository.java
@@ -0,0 +1,9 @@
+package com.rm.mynotes.repository;
+
+import com.rm.mynotes.model.Notification;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface NotificationRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/rm/mynotes/repository/ReminderRepository.java b/src/main/java/com/rm/mynotes/repository/ReminderRepository.java
new file mode 100644
index 0000000..29e537f
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/repository/ReminderRepository.java
@@ -0,0 +1,9 @@
+package com.rm.mynotes.repository;
+
+import com.rm.mynotes.model.Reminder;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface ReminderRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/rm/mynotes/resource/ReminderResource.java b/src/main/java/com/rm/mynotes/resource/ReminderResource.java
new file mode 100644
index 0000000..1e72c7d
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/resource/ReminderResource.java
@@ -0,0 +1,28 @@
+package com.rm.mynotes.resource;
+
+import com.rm.mynotes.model.Reminder;
+import com.rm.mynotes.service.mold.ReminderService;
+import com.rm.mynotes.utils.dto.requests.ReminderDTO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping
+public class ReminderResource {
+ @Autowired
+ private ReminderService reminderService;
+
+ @GetMapping("/app/reminder/1")
+ public String test() {
+ return "você fez uma requisição!";
+ }
+
+ @PostMapping("/app/reminder/{noteId}")
+ public ResponseEntity createReminder(Authentication authentication, @RequestBody ReminderDTO reminderDTO, @RequestParam(required = true, name = "noteId") Long noteId) {
+ return reminderService.createReminder(authentication, reminderDTO, noteId);
+ }
+}
diff --git a/src/main/java/com/rm/mynotes/resource/WebSocketResource.java b/src/main/java/com/rm/mynotes/resource/WebSocketResource.java
new file mode 100644
index 0000000..50866bb
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/resource/WebSocketResource.java
@@ -0,0 +1,21 @@
+package com.rm.mynotes.resource;
+
+import com.rm.mynotes.model.Reminder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.messaging.handler.annotation.MessageMapping;
+import org.springframework.messaging.handler.annotation.SendTo;
+import org.springframework.stereotype.Controller;
+
+@Controller
+public class WebSocketResource {
+ @Autowired
+ private KafkaTemplate kafkaTemplate;
+
+ @MessageMapping("/createReminder")
+ @SendTo("/topic/reminders")
+ public Reminder createEvent(Reminder reminder) {
+ kafkaTemplate.send("remindersTopic", reminder.toString());
+ return reminder;
+ }
+}
diff --git a/src/main/java/com/rm/mynotes/service/impl/ReminderServiceImplementation.java b/src/main/java/com/rm/mynotes/service/impl/ReminderServiceImplementation.java
new file mode 100644
index 0000000..50e3397
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/service/impl/ReminderServiceImplementation.java
@@ -0,0 +1,71 @@
+package com.rm.mynotes.service.impl;
+
+import com.rm.mynotes.model.Note;
+import com.rm.mynotes.model.Reminder;
+import com.rm.mynotes.model.UserEntity;
+import com.rm.mynotes.repository.NoteRepository;
+import com.rm.mynotes.repository.ReminderRepository;
+import com.rm.mynotes.repository.UserRepository;
+import com.rm.mynotes.service.mold.ReminderService;
+import com.rm.mynotes.utils.constants.RoutePaths;
+import com.rm.mynotes.utils.constants.StatusTypes;
+import com.rm.mynotes.utils.dto.payloads.ResponseDTO;
+import com.rm.mynotes.utils.dto.requests.ReminderDTO;
+import com.rm.mynotes.utils.functions.CommonFunctions;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.stereotype.Service;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Objects;
+
+@Service
+@RequiredArgsConstructor
+public class ReminderServiceImplementation implements ReminderService {
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private NoteRepository noteRepository;
+
+ @Autowired
+ private ReminderRepository reminderRepository;
+
+ @Autowired
+ private CommonFunctions commonFunctions;
+
+ @Override
+ public ResponseEntity createReminder(Authentication authentication, ReminderDTO reqReminder, Long noteId) {
+ UserEntity user = commonFunctions.getCurrentUser(authentication);
+ Note note = user.getNotes().stream().filter(userNote -> Objects.equals(userNote.getId(), noteId))
+ .findFirst().orElseThrow(() -> new BadCredentialsException("A anotação informada não existe."));
+
+ Reminder reminder = Reminder.builder()
+ .reminderDate(reqReminder.getReminderDate())
+ .createdAt(CommonFunctions.getCurrentDatetime())
+ .lastUpdate(CommonFunctions.getCurrentDatetime())
+ .status(StatusTypes.PENDING)
+ .build();
+
+ List reminders = note.getReminders();
+ reminders.add(reminderRepository.save(reminder));
+
+ note.setReminders(reminders);
+ noteRepository.save(note);
+
+ URI uri = URI.create(ServletUriComponentsBuilder.fromCurrentContextPath().path(RoutePaths.REMINDER).toUriString());
+
+ return ResponseEntity.created(uri).body(reminder);
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity handleException(Exception exception) {
+ return CommonFunctions.errorHandling(exception);
+ }
+}
diff --git a/src/main/java/com/rm/mynotes/service/mold/ReminderService.java b/src/main/java/com/rm/mynotes/service/mold/ReminderService.java
new file mode 100644
index 0000000..ca14e27
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/service/mold/ReminderService.java
@@ -0,0 +1,10 @@
+package com.rm.mynotes.service.mold;
+
+import com.rm.mynotes.model.Reminder;
+import com.rm.mynotes.utils.dto.requests.ReminderDTO;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+
+public interface ReminderService {
+ ResponseEntity createReminder(Authentication authentication, ReminderDTO reqReminder, Long noteId);
+}
diff --git a/src/main/java/com/rm/mynotes/utils/config/KafkaConsumerConfig.java b/src/main/java/com/rm/mynotes/utils/config/KafkaConsumerConfig.java
new file mode 100644
index 0000000..a334301
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/utils/config/KafkaConsumerConfig.java
@@ -0,0 +1,27 @@
+package com.rm.mynotes.utils.config;
+
+import com.rm.mynotes.model.Notification;
+import com.rm.mynotes.model.Reminder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.annotation.EnableKafka;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.messaging.simp.SimpMessagingTemplate;
+
+@Configuration
+@EnableKafka
+public class KafkaConsumerConfig {
+ @Autowired
+ private SimpMessagingTemplate messagingTemplate;
+
+ @KafkaListener(topics = "remindersTopic", groupId = "group1")
+ public void listen(String message) {
+ Reminder reminder = parseReminder(message);
+ Notification notification = new Notification();
+ messagingTemplate.convertAndSend("/app/topic/notifications", notification);
+ }
+
+ private Reminder parseReminder(String message) {
+ return new Reminder();
+ }
+}
diff --git a/src/main/java/com/rm/mynotes/utils/config/KafkaProducerConfig.java b/src/main/java/com/rm/mynotes/utils/config/KafkaProducerConfig.java
new file mode 100644
index 0000000..67d6e59
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/utils/config/KafkaProducerConfig.java
@@ -0,0 +1,32 @@
+package com.rm.mynotes.utils.config;
+
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class KafkaProducerConfig {
+ @Value("${spring.kafka.bootstrap-servers}")
+ private String bootstrapServers;
+
+ @Bean
+ public ProducerFactory producerFactory() {
+ Map configProps = new HashMap<>();
+ configProps.put(org.apache.kafka.clients.producer.ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
+ configProps.put(org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
+ configProps.put(org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
+ return new DefaultKafkaProducerFactory<>(configProps);
+ }
+
+ @Bean
+ public KafkaTemplate kafkaTemplate() {
+ return new KafkaTemplate<>(producerFactory());
+ }
+}
diff --git a/src/main/java/com/rm/mynotes/utils/config/WebSocketConfig.java b/src/main/java/com/rm/mynotes/utils/config/WebSocketConfig.java
new file mode 100644
index 0000000..cbbb086
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/utils/config/WebSocketConfig.java
@@ -0,0 +1,22 @@
+package com.rm.mynotes.utils.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.messaging.simp.config.MessageBrokerRegistry;
+import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
+import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
+import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
+
+@Configuration
+@EnableWebSocketMessageBroker
+public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
+ @Override
+ public void configureMessageBroker(MessageBrokerRegistry config) {
+ config.enableSimpleBroker("/topic");
+ config.setApplicationDestinationPrefixes("/app");
+ }
+
+ @Override
+ public void registerStompEndpoints(StompEndpointRegistry registry) {
+ registry.addEndpoint("/websocket").withSockJS();
+ }
+}
diff --git a/src/main/java/com/rm/mynotes/utils/constants/RoutePaths.java b/src/main/java/com/rm/mynotes/utils/constants/RoutePaths.java
index d03e7bc..788d6e3 100644
--- a/src/main/java/com/rm/mynotes/utils/constants/RoutePaths.java
+++ b/src/main/java/com/rm/mynotes/utils/constants/RoutePaths.java
@@ -1,6 +1,7 @@
package com.rm.mynotes.utils.constants;
public class RoutePaths {
+ public static final String REMINDER = "/app/reminder";
public static final String LOGIN = "/api/auth/login";
public static final String SIGNUP = "/api/auth/signup";
public static final String GET_NOTE = "/api/note/{id}";
diff --git a/src/main/java/com/rm/mynotes/utils/constants/StatusTypes.java b/src/main/java/com/rm/mynotes/utils/constants/StatusTypes.java
new file mode 100644
index 0000000..de0771c
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/utils/constants/StatusTypes.java
@@ -0,0 +1,5 @@
+package com.rm.mynotes.utils.constants;
+
+public enum StatusTypes {
+ PENDING, LATE, DONE, DONELATE
+}
diff --git a/src/main/java/com/rm/mynotes/utils/dto/requests/ReminderDTO.java b/src/main/java/com/rm/mynotes/utils/dto/requests/ReminderDTO.java
new file mode 100644
index 0000000..9810c90
--- /dev/null
+++ b/src/main/java/com/rm/mynotes/utils/dto/requests/ReminderDTO.java
@@ -0,0 +1,16 @@
+package com.rm.mynotes.utils.dto.requests;
+
+import com.rm.mynotes.utils.constants.StatusTypes;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.time.OffsetDateTime;
+
+@Data
+public class ReminderDTO {
+ @NotNull(message = "O título não pode está vázio")
+ private String title;
+
+ @NotNull(message = "Deve haver ao menos uma data")
+ private OffsetDateTime reminderDate;
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index a466188..b3af907 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -12,6 +12,8 @@ spring:
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
+ kafka:
+ bootstrap-servers: ${KAFKA_SERVERS}
jpa:
hibernate:
ddl-auto: create-drop