i think i added push notifications to backend but im going to sleep now and ill finish frontend later

This commit is contained in:
2026-06-02 00:47:47 +02:00
parent 4db77055ed
commit a9278b8269
10 changed files with 255 additions and 38 deletions
+2 -2
View File
@@ -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
+26
View File
@@ -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();
}
}