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/init/*
|
||||||
backend/**/*temp.java
|
backend/**/*temp.java
|
||||||
|
|
||||||
backend/google-services.json
|
backend/src/main/resources/google-services.json
|
||||||
backend/etf-oglasi-firebase.json
|
backend/src/main/resources/etf-oglasi-firebase.json
|
||||||
|
|
||||||
# Java / Eclipse / STS
|
# Java / Eclipse / STS
|
||||||
.classpath
|
.classpath
|
||||||
|
|||||||
@@ -55,6 +55,32 @@ CREATE TABLE IF NOT EXISTS public.users
|
|||||||
CONSTRAINT unique_email UNIQUE (email)
|
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
|
ALTER TABLE IF EXISTS public.alt_subject
|
||||||
ADD CONSTRAINT alt_subject_subject_id_fkey FOREIGN KEY (subject_id)
|
ADD CONSTRAINT alt_subject_subject_id_fkey FOREIGN KEY (subject_id)
|
||||||
REFERENCES public.subjects (id) MATCH SIMPLE
|
REFERENCES public.subjects (id) MATCH SIMPLE
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import org.springframework.boot.SpringApplication;
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
|
@EnableAsync
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class EtfoglasiServerApplication {
|
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 dev.ksan.etfoglasiserver.dto.UserCreationDTO;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -24,18 +26,40 @@ public class User {
|
|||||||
nullable = false,
|
nullable = false,
|
||||||
updatable = false,
|
updatable = false,
|
||||||
insertable = false,
|
insertable = false,
|
||||||
columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
|
columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
||||||
|
)
|
||||||
private LocalDateTime regTime;
|
private LocalDateTime regTime;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@Column(name = "notification_type", nullable = false)
|
@Column(name = "notification_type", nullable = false)
|
||||||
private NotificationMethod notificationType = NotificationMethod.NO_NOTIFICATION;
|
private NotificationMethod notificationType =
|
||||||
|
NotificationMethod.PUSH_NOTIFICATION;
|
||||||
|
|
||||||
@ManyToMany(fetch = FetchType.LAZY)
|
@ManyToMany(fetch = FetchType.LAZY)
|
||||||
@JoinTable(name = "user-subject", joinColumns = @JoinColumn(name = "user_id"),
|
@JoinTable(
|
||||||
inverseJoinColumns = @JoinColumn(name = "subject_id"))
|
name = "user_subject",
|
||||||
|
joinColumns = @JoinColumn(name = "user_id"),
|
||||||
|
inverseJoinColumns = @JoinColumn(name = "subject_id")
|
||||||
|
)
|
||||||
private Set<Subject> subjectSet;
|
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() {}
|
||||||
|
|
||||||
public User(UserCreationDTO user) {
|
public User(UserCreationDTO user) {
|
||||||
@@ -89,4 +113,5 @@ public class User {
|
|||||||
public NotificationMethod getNotificationMethod() {
|
public NotificationMethod getNotificationMethod() {
|
||||||
return notificationType;
|
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");
|
System.out.println("Duplicate");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
subjectService.notify(entry);
|
subjectService.notifyAsync(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateEntry(EntryDTO 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.List;
|
||||||
import java.util.UUID;
|
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.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class SubjectService {
|
public class SubjectService {
|
||||||
|
|
||||||
@Autowired private SubjectRepo subjectRepo;
|
@Autowired private SubjectRepo subjectRepo;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserRepo userRepo;
|
private NotificationService notificationService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private UserService userService;
|
private UserService userService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MailService mailService;
|
|
||||||
|
|
||||||
public List<Subject> getSubjects() {
|
public List<Subject> getSubjects() {
|
||||||
return subjectRepo.findAll();
|
return subjectRepo.findAll();
|
||||||
@@ -67,29 +67,26 @@ public class SubjectService {
|
|||||||
return subjectRepo.findById(id).orElseThrow(() -> new RuntimeException("Subject not found"));
|
return subjectRepo.findById(id).orElseThrow(() -> new RuntimeException("Subject not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notify(Entry entry) {
|
|
||||||
List<User> users = userService.findUsersBySubjectId(entry.getSubject().getId());
|
//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;
|
if (users == null || users.isEmpty()) return;
|
||||||
|
|
||||||
for (User user : users) {
|
for (User user : users) {
|
||||||
if(user.getNotificationMethod() == NotificationMethod.EMAIL)
|
|
||||||
this.sendEmailNotification(entry, user);
|
if (user.getNotificationMethod() != NotificationMethod.PUSH_NOTIFICATION)
|
||||||
if(user.getNotificationMethod() == NotificationMethod.PUSH_NOTIFICATION)
|
continue;
|
||||||
this.sendPushNotification(entry, user);
|
|
||||||
System.out.println(user.getEmail());
|
notificationService.sendToUser(user, entry.getTitle(), entry.getParagraph());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendEmailNotification(Entry entry,User user) {
|
|
||||||
|
|
||||||
mailService.sendEmail(user.getEmail(),entry.getTitle(),entry.getParagraph());
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo
|
|
||||||
public void sendPushNotification(Entry entry,User user) {
|
|
||||||
System.out.println("Sending push notification");
|
|
||||||
}
|
|
||||||
|
|
||||||
public long count() {
|
public long count() {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user