diff --git a/pom.xml b/pom.xml
index 6a8c65e..683deaa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,20 +51,20 @@
runtime
true
-
+
io.jsonwebtoken
jjwt-api
0.11.5
-
+
io.jsonwebtoken
jjwt-impl
0.11.5
runtime
-
+
io.jsonwebtoken
jjwt-jackson
@@ -93,13 +93,13 @@
org.springframework.boot
spring-boot-starter-security
-
+
org.springframework.security
spring-security-test
test
-
+
org.springframework.security
spring-security-oauth2-client
diff --git a/src/main/java/com/libraryman_api/LibrarymanApiApplication.java b/src/main/java/com/libraryman_api/LibrarymanApiApplication.java
index 89e3ed7..a2c9156 100644
--- a/src/main/java/com/libraryman_api/LibrarymanApiApplication.java
+++ b/src/main/java/com/libraryman_api/LibrarymanApiApplication.java
@@ -7,13 +7,13 @@
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
-@EnableAsync
+@EnableAsync(proxyTargetClass = true)
@EnableScheduling
-@EnableCaching
+@EnableCaching(proxyTargetClass = true)
public class LibrarymanApiApplication {
public static void main(String[] args) {
SpringApplication.run(LibrarymanApiApplication.class, args);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/libraryman_api/book/BookController.java b/src/main/java/com/libraryman_api/book/BookController.java
index 3cd3af3..0e2660f 100644
--- a/src/main/java/com/libraryman_api/book/BookController.java
+++ b/src/main/java/com/libraryman_api/book/BookController.java
@@ -100,4 +100,4 @@ public BookDto updateBook(@PathVariable int id, @RequestBody BookDto bookDtoDeta
public void deleteBook(@PathVariable int id) {
bookService.deleteBook(id);
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/libraryman_api/borrowing/BorrowingService.java b/src/main/java/com/libraryman_api/borrowing/BorrowingService.java
index 6975e71..0b05b17 100644
--- a/src/main/java/com/libraryman_api/borrowing/BorrowingService.java
+++ b/src/main/java/com/libraryman_api/borrowing/BorrowingService.java
@@ -349,4 +349,4 @@ public BorrowingsDto EntityToDto(Borrowings borrowings) {
borrowingsDto.setBook(bookService.EntityToDto(borrowings.getBook()));
return borrowingsDto;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/libraryman_api/email/EmailService.java b/src/main/java/com/libraryman_api/email/EmailService.java
index f5f80b3..c59e644 100644
--- a/src/main/java/com/libraryman_api/email/EmailService.java
+++ b/src/main/java/com/libraryman_api/email/EmailService.java
@@ -17,62 +17,88 @@
import org.springframework.stereotype.Service;
/**
- * Service class for sending emails asynchronously.
- * This class handles the construction and sending of MIME email messages.
+ * Unified service class for sending emails asynchronously.
+ * Handles both general email sending and notifications.
*/
@Service
public class EmailService implements EmailSender {
private static final Logger LOGGER = LoggerFactory.getLogger(EmailService.class);
+
private final NotificationRepository notificationRepository;
private final JavaMailSender mailSender;
- @Value("${spring.mail.properties.domain_name}")
+
+ @Value("${spring.mail.properties.domain_name}") // Domain name from application properties
private String domainName;
- /**
- * Constructs a new {@code EmailService} with the specified {@link NotificationRepository} and {@link JavaMailSender}.
- *
- * @param notificationRepository the repository for managing notification entities
- * @param mailSender the mail sender for sending email messages
- */
public EmailService(NotificationRepository notificationRepository, JavaMailSender mailSender) {
this.notificationRepository = notificationRepository;
this.mailSender = mailSender;
}
/**
- * Sends an email asynchronously to the specified recipient.
- * If the email is successfully sent, the notification status is updated to SENT.
- * If the email fails to send, the notification status is updated to FAILED and an exception is thrown.
+ * Sends a general email asynchronously.
*
- * @param to the recipient's email address
- * @param email the content of the email to send
- * @param subject the subject of the email
- * @param notification the {@link Notifications} object representing the email notification
+ * @param to recipient's email
+ * @param body email content (HTML supported)
+ * @param subject subject of the email
*/
- @Override
@Async
- public void send(String to, String email, String subject, Notifications notification) {
+ public void sendEmail(String to, String body, String subject) {
+ sendEmail(to, body, subject, null); // Default 'from' to null
+ }
+
+ /**
+ * Sends a general email asynchronously.
+ *
+ * @param to recipient's email
+ * @param body email content (HTML supported)
+ * @param subject subject of the email
+ * @param from sender's email address (overrides default if provided)
+ */
+ @Async
+ public void sendEmail(String to, String body, String subject, String from) {
try {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, "utf-8");
- helper.setText(email, true);
+
+ helper.setText(body, true); // true = enable HTML content
helper.setTo(to);
helper.setSubject(subject);
- helper.setFrom(domainName);
+ helper.setFrom(from != null ? from : domainName); // Use provided sender or default domain
+
mailSender.send(mimeMessage);
+ } catch (MessagingException e) {
+ LOGGER.error("Failed to send email", e);
+ throw new IllegalStateException("Failed to send email", e);
+ }
+ }
+
+ /**
+ * Sends a notification email and updates notification status.
+ *
+ * @param to recipient's email
+ * @param email email content
+ * @param subject subject of the email
+ * @param notification notification entity to update status
+ */
+ @Override
+ @Async
+ public void send(String to, String email, String subject, Notifications notification) {
+ try {
+ sendEmail(to, email, subject); // Reuse sendEmail method for notifications
// Update notification status to SENT
notification.setNotificationStatus(NotificationStatus.SENT);
notificationRepository.save(notification);
- } catch (MessagingException e) {
- LOGGER.error("Failed to send email", e);
+ } catch (Exception e) {
+ LOGGER.error("Failed to send notification email", e);
// Update notification status to FAILED
notification.setNotificationStatus(NotificationStatus.FAILED);
notificationRepository.save(notification);
- throw new IllegalStateException("Failed to send email", e);
+ throw new IllegalStateException("Failed to send notification email", e);
}
}
}
diff --git a/src/main/java/com/libraryman_api/member/MemberService.java b/src/main/java/com/libraryman_api/member/MemberService.java
index 553d650..518b7f1 100644
--- a/src/main/java/com/libraryman_api/member/MemberService.java
+++ b/src/main/java/com/libraryman_api/member/MemberService.java
@@ -225,4 +225,4 @@ public MembersDto EntityToDto(Members members) {
membersDto.setMembershipDate(members.getMembershipDate());
return membersDto;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/libraryman_api/newsletter/NewsletterController.java b/src/main/java/com/libraryman_api/newsletter/NewsletterController.java
index fe038fe..c1cbc8b 100644
--- a/src/main/java/com/libraryman_api/newsletter/NewsletterController.java
+++ b/src/main/java/com/libraryman_api/newsletter/NewsletterController.java
@@ -1,44 +1,71 @@
package com.libraryman_api.newsletter;
-import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
-import java.util.Map;
-
@RestController
@RequestMapping("/api/newsletter")
public class NewsletterController {
- @Autowired
- private NewsletterService newsletterService;
+ private final NewsletterService newsletterService;
+
+ public NewsletterController(NewsletterService newsletterService) {
+ this.newsletterService = newsletterService;
+ }
- // Subscribe endpoint
+ // Subscribe Endpoint
@PostMapping("/subscribe")
- public ResponseEntity subscribe(@RequestBody Map requestBody) {
- String email = requestBody.get("email");
+ public ResponseEntity subscribe(@RequestParam String email) {
+ try {
+ String result = newsletterService.subscribe(email);
- // Call the service to handle subscription
- String response = newsletterService.subscribe(email);
+ switch (result) {
+ case "Invalid email format.":
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result); // 400 Bad Request
- // Return response from the service
- if (response.equals("Invalid email format.") || response.equals("Email is already subscribed.")) {
- return ResponseEntity.badRequest().body(response);
- }
+ case "Email is already subscribed.":
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(result); // 409 Conflict
- return ResponseEntity.ok(response);
- }
+ case "You have successfully subscribed!":
+ return ResponseEntity.status(HttpStatus.CREATED).body(result); // 201 Created
- // Unsubscribe endpoint using token
- @GetMapping("/unsubscribe/{token}")
- public ResponseEntity unsubscribe(@PathVariable String token) {
- String response = newsletterService.unsubscribe(token);
+ case "You have successfully re-subscribed!":
+ return ResponseEntity.status(HttpStatus.OK).body(result); // 200 OK
- // Check if the response indicates an error
- if (response.equals("Invalid or expired token.") || response.equals("You are already unsubscribed.")) {
- return ResponseEntity.badRequest().body(response);
+ default:
+ return ResponseEntity.status(HttpStatus.OK).body(result); // Default 200 OK
+ }
+ } catch (Exception e) {
+ // Handle unexpected errors
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body("An error occurred while processing your subscription.");
}
+ }
- return ResponseEntity.ok(response);
+ // Unsubscribe Endpoint
+ @GetMapping("/unsubscribe")
+ public ResponseEntity unsubscribe(@RequestParam String token) {
+ try {
+ String result = newsletterService.unsubscribe(token);
+
+ switch (result) {
+ case "Invalid or expired token.":
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body(result); // 404 Not Found
+
+ case "You are already unsubscribed.":
+ return ResponseEntity.status(HttpStatus.CONFLICT).body(result); // 409 Conflict
+
+ case "You have successfully unsubscribed!":
+ return ResponseEntity.status(HttpStatus.OK).body(result); // 200 OK
+
+ default:
+ return ResponseEntity.status(HttpStatus.OK).body(result); // Default 200 OK
+ }
+ } catch (Exception e) {
+ // Handle unexpected errors
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
+ .body("An error occurred while processing your unsubscription.");
+ }
}
}
diff --git a/src/main/java/com/libraryman_api/newsletter/NewsletterService.java b/src/main/java/com/libraryman_api/newsletter/NewsletterService.java
index 034fd78..5c12cd1 100644
--- a/src/main/java/com/libraryman_api/newsletter/NewsletterService.java
+++ b/src/main/java/com/libraryman_api/newsletter/NewsletterService.java
@@ -1,74 +1,76 @@
package com.libraryman_api.newsletter;
+import com.libraryman_api.email.EmailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-
import java.util.Optional;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class NewsletterService {
+ private final NewsletterSubscriberRepository subscriberRepository;
+ private final EmailService emailService;
+
@Autowired
- private NewsletterSubscriberRepository subscriberRepository;
+ public NewsletterService(NewsletterSubscriberRepository subscriberRepository, EmailService emailService) {
+ this.subscriberRepository = subscriberRepository;
+ this.emailService = emailService;
+ }
- // Subscribe user after validating email
public String subscribe(String email) {
- if (!isValidEmail(email)) {
- return "Invalid email format.";
- }
+ if (!isValidEmail(email)) return "Invalid email format.";
- // Check if the email already exists
Optional optionalSubscriber = subscriberRepository.findByEmail(email);
-
if (optionalSubscriber.isPresent()) {
NewsletterSubscriber subscriber = optionalSubscriber.get();
-
- // If the subscriber is inactive, reactivate them
if (!subscriber.isActive()) {
- subscriber.setActive(true); // Reactivate the subscriber
- subscriber.regenerateToken(); // Generate a new token
- subscriberRepository.save(subscriber); // Save the updated subscriber
+ subscriber.setActive(true);
+ subscriber.regenerateToken();
+ subscriberRepository.save(subscriber);
+ sendSubscriptionEmail(email, subscriber.getUnsubscribeToken());
return "You have successfully re-subscribed!";
- } else {
- return "Email is already subscribed.";
}
+ return "Email is already subscribed.";
}
- // Save new subscriber if not present
- NewsletterSubscriber subscriber = new NewsletterSubscriber(email);
- subscriberRepository.save(subscriber);
+ NewsletterSubscriber newSubscriber = new NewsletterSubscriber(email);
+ subscriberRepository.save(newSubscriber);
+ sendSubscriptionEmail(email, newSubscriber.getUnsubscribeToken());
return "You have successfully subscribed!";
}
- // Unsubscribe user using the token
public String unsubscribe(String token) {
Optional optionalSubscriber = subscriberRepository.findByUnsubscribeToken(token);
-
- if (optionalSubscriber.isEmpty()) {
- return "Invalid or expired token.";
- }
+ if (optionalSubscriber.isEmpty()) return "Invalid or expired token.";
NewsletterSubscriber subscriber = optionalSubscriber.get();
+ if (!subscriber.isActive()) return "You are already unsubscribed.";
- if (!subscriber.isActive()) {
- return "You are already unsubscribed.";
- }
-
- subscriber.setActive(false); // Set active to false
- subscriberRepository.save(subscriber); // Save the updated subscriber
+ subscriber.setActive(false);
+ subscriberRepository.save(subscriber);
+ sendUnsubscribeEmail(subscriber.getEmail());
return "You have successfully unsubscribed!";
}
- // Email validation logic
private boolean isValidEmail(String email) {
- if (email == null || email.trim().isEmpty()) {
- return false;
- }
String emailRegex = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$";
- Pattern pattern = Pattern.compile(emailRegex);
- Matcher matcher = pattern.matcher(email);
- return matcher.matches();
+ return Pattern.compile(emailRegex).matcher(email).matches();
+ }
+
+ private void sendSubscriptionEmail(String email, String token) {
+ String subject = "Welcome to Our Newsletter!";
+ String body = "Thank you for subscribing! " +
+ "To unsubscribe, click the link:\n" +
+ "http://localhost:8080/api/newsletter/unsubscribe?token=" + token;
+
+ emailService.sendEmail(email, body, subject); // No need to change this line
+ }
+
+ private void sendUnsubscribeEmail(String email) {
+ String subject = "You have been unsubscribed";
+ String body = "You have successfully unsubscribed. If this was a mistake, you can re-subscribe.";
+
+ emailService.sendEmail(email, body, subject); // No need to change this line
}
}
diff --git a/src/main/java/com/libraryman_api/newsletter/NewsletterSubscriber.java b/src/main/java/com/libraryman_api/newsletter/NewsletterSubscriber.java
index aa099c0..e6c73a2 100644
--- a/src/main/java/com/libraryman_api/newsletter/NewsletterSubscriber.java
+++ b/src/main/java/com/libraryman_api/newsletter/NewsletterSubscriber.java
@@ -1,7 +1,6 @@
package com.libraryman_api.newsletter;
import jakarta.persistence.*;
-
import java.util.UUID;
@Entity
@@ -16,53 +15,28 @@ public class NewsletterSubscriber {
private String email;
@Column(nullable = false)
- private boolean active = true; // Manage subscription status
+ private boolean active = true;
@Column(name = "unsubscribe_token", nullable = false, unique = true)
- private String unsubscribeToken; // Token for unsubscribing
+ private String unsubscribeToken;
- // Default constructor that initializes the token
+ // Default constructor initializing unsubscribe token
public NewsletterSubscriber() {
- this.unsubscribeToken = UUID.randomUUID().toString(); // Generate token by default
+ this.unsubscribeToken = UUID.randomUUID().toString();
}
- // Constructor to initialize with email
+ // Constructor initializing with email
public NewsletterSubscriber(String email) {
this();
this.email = email;
}
- // Getters and setters
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getEmail() {
- return email;
- }
-
- public void setEmail(String email) {
- this.email = email;
- }
-
- public boolean isActive() {
- return active;
- }
-
- public void setActive(boolean active) {
- this.active = active;
- }
-
- public String getUnsubscribeToken() {
- return unsubscribeToken;
- }
-
- // Method to regenerate a new token
- public void regenerateToken() {
- this.unsubscribeToken = UUID.randomUUID().toString();
- }
+ // Getters and Setters
+ public Long getId() { return id; }
+ public String getEmail() { return email; }
+ public void setEmail(String email) { this.email = email; }
+ public boolean isActive() { return active; }
+ public void setActive(boolean active) { this.active = active; }
+ public String getUnsubscribeToken() { return unsubscribeToken; }
+ public void regenerateToken() { this.unsubscribeToken = UUID.randomUUID().toString(); }
}
diff --git a/src/main/java/com/libraryman_api/newsletter/NewsletterSubscriberRepository.java b/src/main/java/com/libraryman_api/newsletter/NewsletterSubscriberRepository.java
index 1e6d215..2604620 100644
--- a/src/main/java/com/libraryman_api/newsletter/NewsletterSubscriberRepository.java
+++ b/src/main/java/com/libraryman_api/newsletter/NewsletterSubscriberRepository.java
@@ -1,14 +1,9 @@
package com.libraryman_api.newsletter;
import org.springframework.data.jpa.repository.JpaRepository;
-
import java.util.Optional;
public interface NewsletterSubscriberRepository extends JpaRepository {
-
- // Find a subscriber by email
Optional findByEmail(String email);
-
- // Find a subscriber by unsubscribe token
Optional findByUnsubscribeToken(String unsubscribeToken);
}
diff --git a/src/main/java/com/libraryman_api/security/config/WebConfiguration.java b/src/main/java/com/libraryman_api/security/config/WebConfiguration.java
index 61b4002..413db45 100644
--- a/src/main/java/com/libraryman_api/security/config/WebConfiguration.java
+++ b/src/main/java/com/libraryman_api/security/config/WebConfiguration.java
@@ -86,4 +86,4 @@ public CorsFilter corsFilter() {
return new CorsFilter(corsConfigurationSource());
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/com/libraryman_api/security/services/SignupService.java b/src/main/java/com/libraryman_api/security/services/SignupService.java
index cb7bb64..4d6b0e8 100644
--- a/src/main/java/com/libraryman_api/security/services/SignupService.java
+++ b/src/main/java/com/libraryman_api/security/services/SignupService.java
@@ -84,4 +84,4 @@ public void signupLibrarian(Members members) {
new_members.setUsername(members.getUsername());
memberRepository.save(new_members);
}
-}
+}
\ No newline at end of file
diff --git a/src/main/resources/application-development.properties b/src/main/resources/application-development.properties
index 3f7d168..7175765 100644
--- a/src/main/resources/application-development.properties
+++ b/src/main/resources/application-development.properties
@@ -42,10 +42,8 @@ spring.mail.properties.mail.smtp.auth=Add_Your_Mail_Service_SMTP
spring.mail.properties.mail.starttls.enable=Add_Your_Mail_Service_Start_TLS
spring.mail.properties.domain_name=Add_Your_Mail_Service_Domain_Name
-
-
# --- Oauth 2.0 Configurations ---
spring.security.oauth2.client.registration.google.client-name=google
spring.security.oauth2.client.registration.google.client-id=ADD_YOUR_CLIENT_ID
spring.security.oauth2.client.registration.google.client-secret=ADD_YOUR_SECRET_KEY
-spring.security.oauth2.client.registration.google.scope=email,profile
+spring.security.oauth2.client.registration.google.scope=email,profile
\ No newline at end of file