i think i added push notifications to backend but im going to sleep now and ill finish frontend later
This commit is contained in:
+2
-2
@@ -30,8 +30,8 @@ backend/.db/
|
||||
backend/init/*
|
||||
backend/**/*temp.java
|
||||
|
||||
backend/google-services.json
|
||||
backend/etf-oglasi-firebase.json
|
||||
backend/src/main/resources/google-services.json
|
||||
backend/src/main/resources/etf-oglasi-firebase.json
|
||||
|
||||
# Java / Eclipse / STS
|
||||
.classpath
|
||||
|
||||
@@ -55,6 +55,32 @@ CREATE TABLE IF NOT EXISTS public.users
|
||||
CONSTRAINT unique_email UNIQUE (email)
|
||||
);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.device_tokens
|
||||
(
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
user_id uuid NOT NULL,
|
||||
fcm_token text NOT NULL,
|
||||
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT device_tokens_pkey PRIMARY KEY (id),
|
||||
|
||||
CONSTRAINT fk_device_tokens_user
|
||||
FOREIGN KEY (user_id)
|
||||
REFERENCES public.users(id)
|
||||
ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT uq_device_tokens_fcm_token
|
||||
UNIQUE (fcm_token)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_device_tokens_user_id
|
||||
ON public.device_tokens(user_id);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
ALTER TABLE IF EXISTS public.alt_subject
|
||||
ADD CONSTRAINT alt_subject_subject_id_fkey FOREIGN KEY (subject_id)
|
||||
REFERENCES public.subjects (id) MATCH SIMPLE
|
||||
|
||||
@@ -11,7 +11,9 @@ import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@EnableAsync
|
||||
@SpringBootApplication
|
||||
public class EtfoglasiServerApplication {
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package dev.ksan.etfoglasiserver.controller;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.DeviceToken;
|
||||
import dev.ksan.etfoglasiserver.model.User;
|
||||
import dev.ksan.etfoglasiserver.repository.DeviceTokenRepo;
|
||||
import dev.ksan.etfoglasiserver.repository.UserRepo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/device-tokens")
|
||||
public class DeviceTokenController {
|
||||
|
||||
@Autowired
|
||||
private DeviceTokenRepo deviceTokenRepo;
|
||||
@Autowired
|
||||
private UserRepo userRepo;
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> register(
|
||||
@RequestParam UUID userId,
|
||||
@RequestParam String token
|
||||
) {
|
||||
|
||||
User user = userRepo.findById(userId)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
DeviceToken deviceToken = deviceTokenRepo
|
||||
.findByFcmToken(token)
|
||||
.orElse(new DeviceToken());
|
||||
|
||||
deviceToken.setUser(user);
|
||||
deviceToken.setFcmToken(token);
|
||||
|
||||
deviceTokenRepo.save(deviceToken);
|
||||
|
||||
return ResponseEntity.ok("Token saved");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package dev.ksan.etfoglasiserver.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "device_tokens",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = "fcm_token")
|
||||
}
|
||||
)
|
||||
public class DeviceToken {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private UUID id;
|
||||
|
||||
@Setter
|
||||
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
||||
@JoinColumn(name = "user_id", nullable = false)
|
||||
private User user;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Column(name = "fcm_token", nullable = false)
|
||||
private String fcmToken;
|
||||
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
@PreUpdate
|
||||
public void touch() {
|
||||
this.updatedAt = LocalDateTime.now();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package dev.ksan.etfoglasiserver.model;
|
||||
import dev.ksan.etfoglasiserver.dto.UserCreationDTO;
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -21,21 +23,43 @@ public class User {
|
||||
private String password;
|
||||
|
||||
@Column(
|
||||
nullable = false,
|
||||
updatable = false,
|
||||
insertable = false,
|
||||
columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
|
||||
nullable = false,
|
||||
updatable = false,
|
||||
insertable = false,
|
||||
columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||
)
|
||||
private LocalDateTime regTime;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "notification_type", nullable = false)
|
||||
private NotificationMethod notificationType = NotificationMethod.NO_NOTIFICATION;
|
||||
private NotificationMethod notificationType =
|
||||
NotificationMethod.PUSH_NOTIFICATION;
|
||||
|
||||
@ManyToMany(fetch = FetchType.LAZY )
|
||||
@JoinTable(name = "user-subject", joinColumns = @JoinColumn(name = "user_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "subject_id"))
|
||||
@ManyToMany(fetch = FetchType.LAZY)
|
||||
@JoinTable(
|
||||
name = "user_subject",
|
||||
joinColumns = @JoinColumn(name = "user_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "subject_id")
|
||||
)
|
||||
private Set<Subject> subjectSet;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "user",
|
||||
cascade = CascadeType.ALL,
|
||||
orphanRemoval = true,
|
||||
fetch = FetchType.LAZY
|
||||
)
|
||||
private List<DeviceToken> deviceTokens = new ArrayList<>();
|
||||
|
||||
public void addDeviceToken(DeviceToken token) {
|
||||
deviceTokens.add(token);
|
||||
token.setUser(this);
|
||||
}
|
||||
|
||||
public void removeDeviceToken(DeviceToken token) {
|
||||
deviceTokens.remove(token);
|
||||
token.setUser(null);
|
||||
}
|
||||
public User() {}
|
||||
|
||||
public User(UserCreationDTO user) {
|
||||
@@ -89,4 +113,5 @@ public class User {
|
||||
public NotificationMethod getNotificationMethod() {
|
||||
return notificationType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package dev.ksan.etfoglasiserver.repository;
|
||||
|
||||
import dev.ksan.etfoglasiserver.model.DeviceToken;
|
||||
import dev.ksan.etfoglasiserver.model.Entry;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Repository
|
||||
public interface DeviceTokenRepo extends JpaRepository<DeviceToken, UUID> {
|
||||
|
||||
List<DeviceToken> findByUserId(UUID userId);
|
||||
|
||||
Optional<DeviceToken> findByFcmToken(String fcmToken);
|
||||
|
||||
void deleteByFcmToken(String fcmToken);
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class EntryService {
|
||||
System.out.println("Duplicate");
|
||||
return;
|
||||
}
|
||||
subjectService.notify(entry);
|
||||
subjectService.notifyAsync(entry);
|
||||
}
|
||||
|
||||
public void updateEntry(EntryDTO entry) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package dev.ksan.etfoglasiserver.service;
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessaging;
|
||||
import com.google.firebase.messaging.FirebaseMessagingException;
|
||||
import com.google.firebase.messaging.Message;
|
||||
import com.google.firebase.messaging.Notification;
|
||||
import dev.ksan.etfoglasiserver.model.DeviceToken;
|
||||
import dev.ksan.etfoglasiserver.model.User;
|
||||
import dev.ksan.etfoglasiserver.repository.DeviceTokenRepo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class NotificationService {
|
||||
|
||||
private final DeviceTokenRepo deviceTokenRepository;
|
||||
|
||||
|
||||
public void sendToUser(User user, String title, String body) {
|
||||
|
||||
List<DeviceToken> tokens =
|
||||
deviceTokenRepository.findByUserId(user.getId());
|
||||
|
||||
for (DeviceToken token : tokens) {
|
||||
|
||||
try {
|
||||
Message message = Message.builder()
|
||||
.setToken(token.getFcmToken())
|
||||
.setNotification(
|
||||
Notification.builder()
|
||||
.setTitle(title)
|
||||
.setBody(body)
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
|
||||
FirebaseMessaging.getInstance().send(message);
|
||||
|
||||
} catch (FirebaseMessagingException e) {
|
||||
|
||||
String errorCode = e.getMessagingErrorCode() != null
|
||||
? e.getMessagingErrorCode().name()
|
||||
: "";
|
||||
|
||||
System.out.println("Failed token: " + token.getFcmToken());
|
||||
|
||||
if (errorCode.equals("UNREGISTERED")
|
||||
|| errorCode.equals("INVALID_ARGUMENT")) {
|
||||
|
||||
deviceTokenRepository.delete(token);
|
||||
System.out.println("Deleted dead token");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,22 +8,22 @@ import dev.ksan.etfoglasiserver.repository.SubjectRepo;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import dev.ksan.etfoglasiserver.repository.UserRepo;
|
||||
import org.simplejavamail.api.mailer.Mailer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SubjectService {
|
||||
|
||||
@Autowired private SubjectRepo subjectRepo;
|
||||
|
||||
@Autowired
|
||||
private UserRepo userRepo;
|
||||
private NotificationService notificationService;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private MailService mailService;
|
||||
|
||||
|
||||
public List<Subject> getSubjects() {
|
||||
return subjectRepo.findAll();
|
||||
@@ -67,32 +67,29 @@ public class SubjectService {
|
||||
return subjectRepo.findById(id).orElseThrow(() -> new RuntimeException("Subject not found"));
|
||||
}
|
||||
|
||||
public void notify(Entry entry) {
|
||||
List<User> users = userService.findUsersBySubjectId(entry.getSubject().getId());
|
||||
|
||||
if(users == null || users.isEmpty()) return;
|
||||
for(User user : users) {
|
||||
if(user.getNotificationMethod() == NotificationMethod.EMAIL)
|
||||
this.sendEmailNotification(entry, user);
|
||||
if(user.getNotificationMethod() == NotificationMethod.PUSH_NOTIFICATION)
|
||||
this.sendPushNotification(entry, user);
|
||||
System.out.println(user.getEmail());
|
||||
//TODO move this and make an EventListener for this
|
||||
|
||||
@Async
|
||||
public void notifyAsync(Entry entry) {
|
||||
|
||||
List<User> users =
|
||||
userService.findUsersBySubjectId(entry.getSubject().getId());
|
||||
|
||||
if (users == null || users.isEmpty()) return;
|
||||
|
||||
for (User user : users) {
|
||||
|
||||
if (user.getNotificationMethod() != NotificationMethod.PUSH_NOTIFICATION)
|
||||
continue;
|
||||
|
||||
notificationService.sendToUser(user, entry.getTitle(), entry.getParagraph());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void sendEmailNotification(Entry entry,User user) {
|
||||
|
||||
mailService.sendEmail(user.getEmail(),entry.getTitle(),entry.getParagraph());
|
||||
public long count() {
|
||||
|
||||
return subjectRepo.count();
|
||||
}
|
||||
|
||||
//todo
|
||||
public void sendPushNotification(Entry entry,User user) {
|
||||
System.out.println("Sending push notification");
|
||||
}
|
||||
|
||||
public long count() {
|
||||
|
||||
return subjectRepo.count();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user