mirror of
https://github.com/booklore-app/booklore.git
synced 2025-12-23 22:28:11 -05:00
Remove obsolete Email v1 implementation (#1365)
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
package com.adityachandel.booklore.controller;
|
||||
|
||||
import com.adityachandel.booklore.model.dto.request.SendBookByEmailRequest;
|
||||
import com.adityachandel.booklore.service.email.EmailService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Deprecated
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/emails")
|
||||
public class EmailController {
|
||||
|
||||
private final EmailService emailService;
|
||||
|
||||
@PreAuthorize("@securityUtil.canEmailBook() or @securityUtil.isAdmin()")
|
||||
@PostMapping("/send-book")
|
||||
public ResponseEntity<?> sendEmail(@Validated @RequestBody SendBookByEmailRequest request) {
|
||||
emailService.emailBook(request);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.canEmailBook() or @securityUtil.isAdmin()")
|
||||
@PostMapping("/send-book/{bookId}")
|
||||
public ResponseEntity<?> emailBookQuick(@PathVariable Long bookId) {
|
||||
emailService.emailBookQuick(bookId);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.adityachandel.booklore.controller;
|
||||
|
||||
import com.adityachandel.booklore.model.dto.EmailProvider;
|
||||
import com.adityachandel.booklore.model.dto.request.CreateEmailProviderRequest;
|
||||
import com.adityachandel.booklore.service.email.EmailProviderService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Deprecated
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/email/providers")
|
||||
public class EmailProviderController {
|
||||
|
||||
private final EmailProviderService emailProviderService;
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin() or @securityUtil.canEmailBook()")
|
||||
@GetMapping
|
||||
public ResponseEntity<List<EmailProvider>> getEmailProviders() {
|
||||
return ResponseEntity.ok(emailProviderService.getEmailProviders());
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin() or @securityUtil.canEmailBook()")
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<EmailProvider> getEmailProvider(@PathVariable Long id) {
|
||||
return ResponseEntity.ok(emailProviderService.getEmailProvider(id));
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin()")
|
||||
@PostMapping
|
||||
public ResponseEntity<EmailProvider> createEmailProvider(@RequestBody CreateEmailProviderRequest createEmailProviderRequest) {
|
||||
return ResponseEntity.ok(emailProviderService.createEmailProvider(createEmailProviderRequest));
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin()")
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<EmailProvider> updateEmailProvider(@PathVariable Long id, @RequestBody CreateEmailProviderRequest updateRequest) {
|
||||
return ResponseEntity.ok(emailProviderService.updateEmailProvider(id, updateRequest));
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin()")
|
||||
@PatchMapping("/{id}/set-default")
|
||||
public ResponseEntity<Void> setDefaultEmailProvider(@PathVariable Long id) {
|
||||
emailProviderService.setDefaultEmailProvider(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin()")
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<?> deleteEmailProvider(@PathVariable Long id) {
|
||||
emailProviderService.deleteEmailProvider(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.adityachandel.booklore.controller;
|
||||
|
||||
import com.adityachandel.booklore.model.dto.EmailRecipient;
|
||||
import com.adityachandel.booklore.model.dto.request.CreateEmailRecipientRequest;
|
||||
import com.adityachandel.booklore.service.email.EmailRecipientService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Deprecated
|
||||
@AllArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/email/recipients")
|
||||
public class EmailRecipientController {
|
||||
|
||||
private final EmailRecipientService emailRecipientService;
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin() or @securityUtil.canEmailBook()")
|
||||
@GetMapping
|
||||
public ResponseEntity<List<EmailRecipient>> getEmailRecipients() {
|
||||
return ResponseEntity.ok(emailRecipientService.getEmailRecipients());
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin() or @securityUtil.canEmailBook()")
|
||||
@GetMapping("/{id}")
|
||||
public ResponseEntity<EmailRecipient> getEmailRecipient(@PathVariable Long id) {
|
||||
return ResponseEntity.ok(emailRecipientService.getEmailRecipient(id));
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin()")
|
||||
@PostMapping
|
||||
public ResponseEntity<EmailRecipient> createEmailRecipient(@RequestBody CreateEmailRecipientRequest createEmailRecipientRequest) {
|
||||
return ResponseEntity.ok(emailRecipientService.createEmailRecipient(createEmailRecipientRequest));
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin()")
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<EmailRecipient> updateEmailRecipient(@PathVariable Long id, @RequestBody CreateEmailRecipientRequest updateRequest) {
|
||||
return ResponseEntity.ok(emailRecipientService.updateEmailRecipient(id, updateRequest));
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin()")
|
||||
@PatchMapping("/{id}/set-default")
|
||||
public ResponseEntity<Void> setDefaultEmailRecipient(@PathVariable Long id) {
|
||||
emailRecipientService.setDefaultRecipient(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PreAuthorize("@securityUtil.isAdmin()")
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteEmailRecipient(@PathVariable Long id) {
|
||||
emailRecipientService.deleteEmailRecipient(id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.adityachandel.booklore.controller;
|
||||
|
||||
import com.adityachandel.booklore.model.dto.request.SendBookByEmailRequest;
|
||||
import com.adityachandel.booklore.service.email.EmailService;
|
||||
import com.adityachandel.booklore.service.email.SendEmailV2Service;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.adityachandel.booklore.mapper;
|
||||
|
||||
import com.adityachandel.booklore.model.dto.EmailProvider;
|
||||
import com.adityachandel.booklore.model.dto.request.CreateEmailProviderRequest;
|
||||
import com.adityachandel.booklore.model.entity.EmailProviderEntity;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface EmailProviderMapper {
|
||||
|
||||
EmailProvider toDTO(EmailProviderEntity emailProviderEntity);
|
||||
EmailProviderEntity toEntity(EmailProvider emailProvider);
|
||||
EmailProviderEntity toEntity(CreateEmailProviderRequest createRequest);
|
||||
void updateEntityFromRequest(CreateEmailProviderRequest request, @MappingTarget EmailProviderEntity entity);
|
||||
}
|
||||
@@ -3,14 +3,24 @@ package com.adityachandel.booklore.mapper;
|
||||
import com.adityachandel.booklore.model.dto.EmailProviderV2;
|
||||
import com.adityachandel.booklore.model.dto.request.CreateEmailProviderRequest;
|
||||
import com.adityachandel.booklore.model.entity.EmailProviderV2Entity;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.*;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface EmailProviderV2Mapper {
|
||||
|
||||
EmailProviderV2 toDTO(EmailProviderV2Entity entity);
|
||||
EmailProviderV2Entity toEntity(EmailProviderV2 emailProvider);
|
||||
EmailProviderV2Entity toEntity(CreateEmailProviderRequest createRequest);
|
||||
@Mapping(target = "defaultProvider", expression = "java(entity.getId() != null && entity.getId().equals(defaultProviderId))")
|
||||
EmailProviderV2 toDTO(EmailProviderV2Entity entity, @Context Long defaultProviderId);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "userId", ignore = true)
|
||||
@Mapping(target = "defaultProvider", ignore = true)
|
||||
@Mapping(target = "shared", ignore = true)
|
||||
EmailProviderV2Entity toEntity(CreateEmailProviderRequest request);
|
||||
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "userId", ignore = true)
|
||||
@Mapping(target = "defaultProvider", ignore = true)
|
||||
@Mapping(target = "shared", ignore = true)
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
void updateEntityFromRequest(CreateEmailProviderRequest request, @MappingTarget EmailProviderV2Entity entity);
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package com.adityachandel.booklore.mapper;
|
||||
|
||||
import com.adityachandel.booklore.model.dto.EmailRecipient;
|
||||
import com.adityachandel.booklore.model.dto.request.CreateEmailRecipientRequest;
|
||||
import com.adityachandel.booklore.model.entity.EmailRecipientEntity;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface EmailRecipientMapper {
|
||||
|
||||
EmailRecipient toDTO(EmailRecipientEntity emailRecipientEntity);
|
||||
|
||||
EmailRecipientEntity toEntity(EmailRecipient emailRecipient);
|
||||
|
||||
EmailRecipientEntity toEntity(CreateEmailRecipientRequest createRequest);
|
||||
|
||||
void updateEntityFromRequest(CreateEmailRecipientRequest request, @MappingTarget EmailRecipientEntity entity);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.adityachandel.booklore.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Deprecated
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class EmailProvider {
|
||||
private Long id;
|
||||
private String name;
|
||||
private String host;
|
||||
private Integer port;
|
||||
private String username;
|
||||
private String password;
|
||||
private String fromAddress;
|
||||
private Boolean auth;
|
||||
private Boolean startTls;
|
||||
private Boolean defaultProvider;
|
||||
}
|
||||
@@ -16,7 +16,6 @@ public class EmailProviderV2 {
|
||||
private String host;
|
||||
private Integer port;
|
||||
private String username;
|
||||
private String password;
|
||||
private String fromAddress;
|
||||
private Boolean auth;
|
||||
private Boolean startTls;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.adityachandel.booklore.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Deprecated
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class EmailRecipient {
|
||||
private Long id;
|
||||
private String email;
|
||||
private String name;
|
||||
private boolean defaultRecipient;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.adityachandel.booklore.model.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
@Deprecated
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "email_provider")
|
||||
public class EmailProviderEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "name", nullable = false, unique = true)
|
||||
private String name;
|
||||
|
||||
@Column(name = "host", nullable = false)
|
||||
private String host;
|
||||
|
||||
@Column(name = "port", nullable = false)
|
||||
private int port;
|
||||
|
||||
@Column(name = "username", nullable = false)
|
||||
private String username;
|
||||
|
||||
@Column(name = "password", nullable = false)
|
||||
private String password;
|
||||
|
||||
@Column(name = "from_address")
|
||||
private String fromAddress;
|
||||
|
||||
@Column(name = "auth", nullable = false)
|
||||
private boolean auth;
|
||||
|
||||
@Column(name = "start_tls", nullable = false)
|
||||
private boolean startTls;
|
||||
|
||||
@Column(name = "is_default", nullable = false)
|
||||
private boolean defaultProvider;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package com.adityachandel.booklore.model.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
@Deprecated
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "email_recipient")
|
||||
public class EmailRecipientEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "email", nullable = false, unique = true)
|
||||
private String email;
|
||||
|
||||
@Column(name = "name", nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "is_default", nullable = false)
|
||||
private boolean defaultRecipient;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.adityachandel.booklore.model.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Table(name = "user_email_provider_preference", uniqueConstraints = {
|
||||
@UniqueConstraint(columnNames = {"user_id"})
|
||||
})
|
||||
public class UserEmailProviderPreferenceEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "user_id", nullable = false)
|
||||
private Long userId;
|
||||
|
||||
@Column(name = "default_provider_id", nullable = false)
|
||||
private Long defaultProviderId;
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package com.adityachandel.booklore.repository;
|
||||
|
||||
import com.adityachandel.booklore.model.entity.EmailProviderEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Deprecated
|
||||
@Repository
|
||||
public interface EmailProviderRepository extends JpaRepository<EmailProviderEntity, Long> {
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE EmailProviderEntity e SET e.defaultProvider = false")
|
||||
void updateAllProvidersToNonDefault();
|
||||
|
||||
@Query("SELECT e FROM EmailProviderEntity e WHERE e.defaultProvider = true")
|
||||
Optional<EmailProviderEntity> findDefaultEmailProvider();
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package com.adityachandel.booklore.repository;
|
||||
|
||||
import com.adityachandel.booklore.model.entity.EmailProviderV2Entity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -17,16 +16,12 @@ public interface EmailProviderV2Repository extends JpaRepository<EmailProviderV2
|
||||
|
||||
List<EmailProviderV2Entity> findAllByUserId(Long userId);
|
||||
|
||||
@Modifying
|
||||
@Query("UPDATE EmailProviderV2Entity e SET e.defaultProvider = false")
|
||||
void updateAllProvidersToNonDefault();
|
||||
|
||||
@Query("SELECT e FROM EmailProviderV2Entity e WHERE e.userId = :userId AND e.defaultProvider = true")
|
||||
Optional<EmailProviderV2Entity> findDefaultEmailProvider(@Param("userId") Long userId);
|
||||
|
||||
@Query("SELECT e FROM EmailProviderV2Entity e WHERE e.shared = true AND e.userId IN (SELECT u.id FROM BookLoreUserEntity u WHERE u.permissions.permissionAdmin = true)")
|
||||
List<EmailProviderV2Entity> findAllBySharedTrueAndAdmin();
|
||||
|
||||
@Query("SELECT e FROM EmailProviderV2Entity e WHERE e.id = :id AND e.shared = true AND e.userId IN (SELECT u.id FROM BookLoreUserEntity u WHERE u.permissions.permissionAdmin = true)")
|
||||
Optional<EmailProviderV2Entity> findSharedProviderById(@Param("id") Long id);
|
||||
|
||||
@Query("SELECT e FROM EmailProviderV2Entity e WHERE e.id = :id AND (e.userId = :userId OR (e.shared = true AND e.userId IN (SELECT u.id FROM BookLoreUserEntity u WHERE u.permissions.permissionAdmin = true)))")
|
||||
Optional<EmailProviderV2Entity> findAccessibleProvider(@Param("id") Long id, @Param("userId") Long userId);
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package com.adityachandel.booklore.repository;
|
||||
|
||||
import com.adityachandel.booklore.model.entity.EmailRecipientEntity;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Deprecated
|
||||
@Repository
|
||||
public interface EmailRecipientRepository extends JpaRepository<EmailRecipientEntity, Long> {
|
||||
|
||||
Optional<EmailRecipientEntity> findById(long id);
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE EmailRecipientEntity e SET e.defaultRecipient = false WHERE e.defaultRecipient = true")
|
||||
void updateAllRecipientsToNonDefault();
|
||||
|
||||
@Query("SELECT e FROM EmailRecipientEntity e WHERE e.defaultRecipient = true")
|
||||
Optional<EmailRecipientEntity> findDefaultEmailRecipient();
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.adityachandel.booklore.repository;
|
||||
|
||||
import com.adityachandel.booklore.model.entity.EmailRecipientEntity;
|
||||
import com.adityachandel.booklore.model.entity.EmailRecipientV2Entity;
|
||||
import jakarta.transaction.Transactional;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
@@ -20,9 +19,9 @@ public interface EmailRecipientV2Repository extends JpaRepository<EmailRecipient
|
||||
|
||||
@Modifying
|
||||
@Transactional
|
||||
@Query("UPDATE EmailRecipientV2Entity e SET e.defaultRecipient = false WHERE e.defaultRecipient = true")
|
||||
void updateAllRecipientsToNonDefault();
|
||||
@Query("UPDATE EmailRecipientV2Entity e SET e.defaultRecipient = false WHERE e.defaultRecipient = true AND e.userId = :userId")
|
||||
void updateAllRecipientsToNonDefault(Long userId);
|
||||
|
||||
@Query("SELECT e FROM EmailRecipientV2Entity e WHERE e.defaultRecipient = true")
|
||||
Optional<EmailRecipientV2Entity> findDefaultEmailRecipient();
|
||||
@Query("SELECT e FROM EmailRecipientV2Entity e WHERE e.defaultRecipient = true AND e.userId = :userId")
|
||||
Optional<EmailRecipientV2Entity> findDefaultEmailRecipientByUserId(Long userId);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.adityachandel.booklore.repository;
|
||||
|
||||
import com.adityachandel.booklore.model.entity.UserEmailProviderPreferenceEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface UserEmailProviderPreferenceRepository extends JpaRepository<UserEmailProviderPreferenceEntity, Long> {
|
||||
|
||||
Optional<UserEmailProviderPreferenceEntity> findByUserId(Long userId);
|
||||
|
||||
void deleteByUserId(Long userId);
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.adityachandel.booklore.service.email;
|
||||
|
||||
import com.adityachandel.booklore.exception.ApiError;
|
||||
import com.adityachandel.booklore.mapper.EmailProviderMapper;
|
||||
import com.adityachandel.booklore.model.dto.EmailProvider;
|
||||
import com.adityachandel.booklore.model.dto.request.CreateEmailProviderRequest;
|
||||
import com.adityachandel.booklore.model.entity.EmailProviderEntity;
|
||||
import com.adityachandel.booklore.repository.EmailProviderRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@Deprecated
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class EmailProviderService {
|
||||
|
||||
private final EmailProviderRepository emailProviderRepository;
|
||||
private final EmailProviderMapper emailProviderMapper;
|
||||
|
||||
public EmailProvider getEmailProvider(Long id) {
|
||||
EmailProviderEntity emailProvider = emailProviderRepository.findById(id).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
return emailProviderMapper.toDTO(emailProvider);
|
||||
}
|
||||
|
||||
public EmailProvider createEmailProvider(CreateEmailProviderRequest request) {
|
||||
boolean isFirstProvider = emailProviderRepository.count() == 0;
|
||||
EmailProviderEntity emailProviderEntity = emailProviderMapper.toEntity(request);
|
||||
emailProviderEntity.setDefaultProvider(isFirstProvider);
|
||||
EmailProviderEntity savedEntity = emailProviderRepository.save(emailProviderEntity);
|
||||
return emailProviderMapper.toDTO(savedEntity);
|
||||
}
|
||||
|
||||
public EmailProvider updateEmailProvider(Long id, CreateEmailProviderRequest request) {
|
||||
EmailProviderEntity existingProvider = emailProviderRepository.findById(id).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
emailProviderMapper.updateEntityFromRequest(request, existingProvider);
|
||||
EmailProviderEntity updatedEntity = emailProviderRepository.save(existingProvider);
|
||||
return emailProviderMapper.toDTO(updatedEntity);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setDefaultEmailProvider(Long id) {
|
||||
EmailProviderEntity emailProvider = emailProviderRepository.findById(id).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
emailProviderRepository.updateAllProvidersToNonDefault();
|
||||
emailProvider.setDefaultProvider(true);
|
||||
emailProviderRepository.save(emailProvider);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteEmailProvider(Long id) {
|
||||
EmailProviderEntity emailProviderToDelete = emailProviderRepository.findById(id).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
boolean isDefaultProvider = emailProviderToDelete.isDefaultProvider();
|
||||
if (isDefaultProvider) {
|
||||
List<EmailProviderEntity> allProviders = emailProviderRepository.findAll();
|
||||
if (allProviders.size() > 1) {
|
||||
allProviders.remove(emailProviderToDelete);
|
||||
EmailProviderEntity newDefaultProvider = allProviders.get(ThreadLocalRandom.current().nextInt(allProviders.size()));
|
||||
newDefaultProvider.setDefaultProvider(true);
|
||||
emailProviderRepository.save(newDefaultProvider);
|
||||
}
|
||||
}
|
||||
emailProviderRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public List<EmailProvider> getEmailProviders() {
|
||||
return emailProviderRepository.findAll().stream().map(emailProviderMapper::toDTO).toList();
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,9 @@ import com.adityachandel.booklore.model.dto.BookLoreUser;
|
||||
import com.adityachandel.booklore.model.dto.EmailProviderV2;
|
||||
import com.adityachandel.booklore.model.dto.request.CreateEmailProviderRequest;
|
||||
import com.adityachandel.booklore.model.entity.EmailProviderV2Entity;
|
||||
import com.adityachandel.booklore.model.entity.UserEmailProviderPreferenceEntity;
|
||||
import com.adityachandel.booklore.repository.EmailProviderV2Repository;
|
||||
import com.adityachandel.booklore.repository.UserEmailProviderPreferenceRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -22,71 +24,120 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
public class EmailProviderV2Service {
|
||||
|
||||
private final EmailProviderV2Repository repository;
|
||||
private final UserEmailProviderPreferenceRepository preferenceRepository;
|
||||
private final EmailProviderV2Mapper mapper;
|
||||
private final AuthenticationService authService;
|
||||
|
||||
public List<EmailProviderV2> getEmailProviders() {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
List<EmailProviderV2Entity> userProviders = repository.findAllByUserId(user.getId());
|
||||
if (user.getPermissions().isAdmin()) {
|
||||
return userProviders.stream().map(mapper::toDTO).toList();
|
||||
if (!user.getPermissions().isAdmin()) {
|
||||
List<EmailProviderV2Entity> sharedProviders = repository.findAllBySharedTrueAndAdmin();
|
||||
userProviders.addAll(sharedProviders);
|
||||
}
|
||||
List<EmailProviderV2Entity> sharedProviders = repository.findAllBySharedTrueAndAdmin();
|
||||
userProviders.addAll(sharedProviders);
|
||||
return userProviders.stream().map(mapper::toDTO).toList();
|
||||
|
||||
Long defaultProviderId = getDefaultProviderIdForUser(user.getId());
|
||||
return userProviders.stream()
|
||||
.map(entity -> mapper.toDTO(entity, defaultProviderId))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public EmailProviderV2 getEmailProvider(Long id) {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
EmailProviderV2Entity entity = repository.findByIdAndUserId(id, user.getId()).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
return mapper.toDTO(entity);
|
||||
EmailProviderV2Entity entity = repository.findAccessibleProvider(id, user.getId())
|
||||
.orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
|
||||
Long defaultProviderId = getDefaultProviderIdForUser(user.getId());
|
||||
return mapper.toDTO(entity, defaultProviderId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public EmailProviderV2 createEmailProvider(CreateEmailProviderRequest request) {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
boolean isFirstProvider = repository.count() == 0;
|
||||
EmailProviderV2Entity entity = mapper.toEntity(request);
|
||||
entity.setDefaultProvider(isFirstProvider);
|
||||
entity.setUserId(user.getId());
|
||||
entity.setShared(user.getPermissions().isAdmin() && request.isShared());
|
||||
EmailProviderV2Entity savedEntity = repository.save(entity);
|
||||
return mapper.toDTO(savedEntity);
|
||||
|
||||
if (preferenceRepository.findByUserId(user.getId()).isEmpty()) {
|
||||
setDefaultProviderForUser(user.getId(), savedEntity.getId());
|
||||
}
|
||||
|
||||
Long defaultProviderId = getDefaultProviderIdForUser(user.getId());
|
||||
return mapper.toDTO(savedEntity, defaultProviderId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public EmailProviderV2 updateEmailProvider(Long id, CreateEmailProviderRequest request) {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
EmailProviderV2Entity existingProvider = repository.findByIdAndUserId(id, user.getId()).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
EmailProviderV2Entity existingProvider = repository.findByIdAndUserId(id, user.getId())
|
||||
.orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
|
||||
mapper.updateEntityFromRequest(request, existingProvider);
|
||||
if (user.getPermissions().isAdmin()) {
|
||||
existingProvider.setShared(request.isShared());
|
||||
}
|
||||
EmailProviderV2Entity updatedEntity = repository.save(existingProvider);
|
||||
return mapper.toDTO(updatedEntity);
|
||||
|
||||
Long defaultProviderId = getDefaultProviderIdForUser(user.getId());
|
||||
return mapper.toDTO(updatedEntity, defaultProviderId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setDefaultEmailProvider(Long id) {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
EmailProviderV2Entity emailProvider = repository.findByIdAndUserId(id, user.getId()).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
repository.updateAllProvidersToNonDefault();
|
||||
emailProvider.setDefaultProvider(true);
|
||||
repository.save(emailProvider);
|
||||
// Verify user has access to this provider
|
||||
repository.findAccessibleProvider(id, user.getId())
|
||||
.orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
|
||||
setDefaultProviderForUser(user.getId(), id);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteEmailProvider(Long id) {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
EmailProviderV2Entity emailProviderToDelete = repository.findByIdAndUserId(id, user.getId()).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
boolean isDefaultProvider = emailProviderToDelete.isDefaultProvider();
|
||||
if (isDefaultProvider) {
|
||||
List<EmailProviderV2Entity> allProviders = repository.findAll();
|
||||
if (allProviders.size() > 1) {
|
||||
allProviders.remove(emailProviderToDelete);
|
||||
EmailProviderV2Entity newDefaultProvider = allProviders.get(ThreadLocalRandom.current().nextInt(allProviders.size()));
|
||||
newDefaultProvider.setDefaultProvider(true);
|
||||
repository.save(newDefaultProvider);
|
||||
repository.findByIdAndUserId(id, user.getId())
|
||||
.orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(id));
|
||||
|
||||
List<UserEmailProviderPreferenceEntity> preferencesUsingProvider =
|
||||
preferenceRepository.findAll().stream()
|
||||
.filter(pref -> pref.getDefaultProviderId().equals(id))
|
||||
.toList();
|
||||
|
||||
for (UserEmailProviderPreferenceEntity preference : preferencesUsingProvider) {
|
||||
List<EmailProviderV2Entity> availableProviders = getAccessibleProvidersForUser(preference.getUserId());
|
||||
availableProviders.removeIf(p -> p.getId().equals(id));
|
||||
|
||||
if (!availableProviders.isEmpty()) {
|
||||
EmailProviderV2Entity newDefault = availableProviders.get(ThreadLocalRandom.current().nextInt(availableProviders.size()));
|
||||
preference.setDefaultProviderId(newDefault.getId());
|
||||
preferenceRepository.save(preference);
|
||||
} else {
|
||||
preferenceRepository.delete(preference);
|
||||
}
|
||||
}
|
||||
|
||||
repository.deleteById(id);
|
||||
}
|
||||
|
||||
private Long getDefaultProviderIdForUser(Long userId) {
|
||||
return preferenceRepository.findByUserId(userId)
|
||||
.map(UserEmailProviderPreferenceEntity::getDefaultProviderId)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private void setDefaultProviderForUser(Long userId, Long providerId) {
|
||||
UserEmailProviderPreferenceEntity preference = preferenceRepository.findByUserId(userId)
|
||||
.orElse(UserEmailProviderPreferenceEntity.builder()
|
||||
.userId(userId)
|
||||
.build());
|
||||
preference.setDefaultProviderId(providerId);
|
||||
preferenceRepository.save(preference);
|
||||
}
|
||||
|
||||
private List<EmailProviderV2Entity> getAccessibleProvidersForUser(Long userId) {
|
||||
List<EmailProviderV2Entity> providers = repository.findAllByUserId(userId);
|
||||
providers.addAll(repository.findAllBySharedTrueAndAdmin());
|
||||
return providers;
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.adityachandel.booklore.service.email;
|
||||
|
||||
import com.adityachandel.booklore.exception.ApiError;
|
||||
import com.adityachandel.booklore.mapper.EmailRecipientMapper;
|
||||
import com.adityachandel.booklore.model.dto.EmailRecipient;
|
||||
import com.adityachandel.booklore.model.dto.request.CreateEmailRecipientRequest;
|
||||
import com.adityachandel.booklore.model.entity.EmailRecipientEntity;
|
||||
import com.adityachandel.booklore.repository.EmailRecipientRepository;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
@Deprecated
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class EmailRecipientService {
|
||||
|
||||
private final EmailRecipientRepository emailRecipientRepository;
|
||||
private final EmailRecipientMapper emailRecipientMapper;
|
||||
|
||||
public EmailRecipient getEmailRecipient(Long id) {
|
||||
EmailRecipientEntity emailRecipient = emailRecipientRepository.findById(id).orElseThrow(() -> ApiError.EMAIL_RECIPIENT_NOT_FOUND.createException(id));
|
||||
return emailRecipientMapper.toDTO(emailRecipient);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public EmailRecipient createEmailRecipient(CreateEmailRecipientRequest request) {
|
||||
boolean isFirstRecipient = emailRecipientRepository.count() == 0;
|
||||
if (request.isDefaultRecipient() || isFirstRecipient) {
|
||||
emailRecipientRepository.updateAllRecipientsToNonDefault();
|
||||
}
|
||||
EmailRecipientEntity emailRecipientEntity = emailRecipientMapper.toEntity(request);
|
||||
emailRecipientEntity.setDefaultRecipient(request.isDefaultRecipient() || isFirstRecipient);
|
||||
EmailRecipientEntity savedEntity = emailRecipientRepository.save(emailRecipientEntity);
|
||||
return emailRecipientMapper.toDTO(savedEntity);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public EmailRecipient updateEmailRecipient(Long id, CreateEmailRecipientRequest request) {
|
||||
EmailRecipientEntity existingRecipient = emailRecipientRepository.findById(id).orElseThrow(() -> ApiError.EMAIL_RECIPIENT_NOT_FOUND.createException(id));
|
||||
if (request.isDefaultRecipient()) {
|
||||
emailRecipientRepository.updateAllRecipientsToNonDefault();
|
||||
}
|
||||
emailRecipientMapper.updateEntityFromRequest(request, existingRecipient);
|
||||
EmailRecipientEntity updatedEntity = emailRecipientRepository.save(existingRecipient);
|
||||
return emailRecipientMapper.toDTO(updatedEntity);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void setDefaultRecipient(Long id) {
|
||||
EmailRecipientEntity emailRecipient = emailRecipientRepository.findById(id).orElseThrow(() -> ApiError.EMAIL_RECIPIENT_NOT_FOUND.createException(id));
|
||||
emailRecipientRepository.updateAllRecipientsToNonDefault();
|
||||
emailRecipient.setDefaultRecipient(true);
|
||||
emailRecipientRepository.save(emailRecipient);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteEmailRecipient(Long id) {
|
||||
EmailRecipientEntity emailRecipientToDelete = emailRecipientRepository.findById(id).orElseThrow(() -> ApiError.EMAIL_RECIPIENT_NOT_FOUND.createException(id));
|
||||
boolean isDefaultRecipient = emailRecipientToDelete.isDefaultRecipient();
|
||||
if (isDefaultRecipient) {
|
||||
List<EmailRecipientEntity> allRecipients = emailRecipientRepository.findAll();
|
||||
if (allRecipients.size() > 1) {
|
||||
allRecipients.remove(emailRecipientToDelete);
|
||||
int randomIndex = ThreadLocalRandom.current().nextInt(allRecipients.size());
|
||||
EmailRecipientEntity newDefaultRecipient = allRecipients.get(randomIndex);
|
||||
newDefaultRecipient.setDefaultRecipient(true);
|
||||
emailRecipientRepository.save(newDefaultRecipient);
|
||||
}
|
||||
}
|
||||
emailRecipientRepository.deleteById(id);
|
||||
}
|
||||
|
||||
public List<EmailRecipient> getEmailRecipients() {
|
||||
return emailRecipientRepository.findAll().stream()
|
||||
.map(emailRecipientMapper::toDTO)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public class EmailRecipientV2Service {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
boolean isFirstRecipient = repository.count() == 0;
|
||||
if (request.isDefaultRecipient() || isFirstRecipient) {
|
||||
repository.updateAllRecipientsToNonDefault();
|
||||
repository.updateAllRecipientsToNonDefault(user.getId());
|
||||
}
|
||||
EmailRecipientV2Entity entity = mapper.toEntity(request);
|
||||
entity.setDefaultRecipient(request.isDefaultRecipient() || isFirstRecipient);
|
||||
@@ -58,7 +58,7 @@ public class EmailRecipientV2Service {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
EmailRecipientV2Entity existingRecipient = repository.findByIdAndUserId(id, user.getId()).orElseThrow(() -> ApiError.EMAIL_RECIPIENT_NOT_FOUND.createException(id));
|
||||
if (request.isDefaultRecipient()) {
|
||||
repository.updateAllRecipientsToNonDefault();
|
||||
repository.updateAllRecipientsToNonDefault(user.getId());
|
||||
}
|
||||
mapper.updateEntityFromRequest(request, existingRecipient);
|
||||
EmailRecipientV2Entity updatedEntity = repository.save(existingRecipient);
|
||||
@@ -69,7 +69,7 @@ public class EmailRecipientV2Service {
|
||||
public void setDefaultRecipient(Long id) {
|
||||
BookLoreUser user = authService.getAuthenticatedUser();
|
||||
EmailRecipientV2Entity emailRecipient = repository.findByIdAndUserId(id, user.getId()).orElseThrow(() -> ApiError.EMAIL_RECIPIENT_NOT_FOUND.createException(id));
|
||||
repository.updateAllRecipientsToNonDefault();
|
||||
repository.updateAllRecipientsToNonDefault(user.getId());
|
||||
emailRecipient.setDefaultRecipient(true);
|
||||
repository.save(emailRecipient);
|
||||
}
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
package com.adityachandel.booklore.service.email;
|
||||
|
||||
import com.adityachandel.booklore.exception.ApiError;
|
||||
import com.adityachandel.booklore.model.dto.request.SendBookByEmailRequest;
|
||||
import com.adityachandel.booklore.model.entity.BookEntity;
|
||||
import com.adityachandel.booklore.model.entity.EmailProviderEntity;
|
||||
import com.adityachandel.booklore.model.entity.EmailRecipientEntity;
|
||||
import com.adityachandel.booklore.model.websocket.LogNotification;
|
||||
import com.adityachandel.booklore.model.websocket.Severity;
|
||||
import com.adityachandel.booklore.model.websocket.Topic;
|
||||
import com.adityachandel.booklore.repository.BookRepository;
|
||||
import com.adityachandel.booklore.repository.EmailProviderRepository;
|
||||
import com.adityachandel.booklore.repository.EmailRecipientRepository;
|
||||
import com.adityachandel.booklore.service.NotificationService;
|
||||
import com.adityachandel.booklore.util.SecurityContextVirtualThread;
|
||||
import com.adityachandel.booklore.util.FileUtils;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.adityachandel.booklore.model.websocket.LogNotification.createLogNotification;
|
||||
|
||||
@Deprecated
|
||||
@Slf4j
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class EmailService {
|
||||
|
||||
private final EmailProviderRepository emailProviderRepository;
|
||||
private final BookRepository bookRepository;
|
||||
private final EmailRecipientRepository emailRecipientRepository;
|
||||
private final NotificationService notificationService;
|
||||
|
||||
public void emailBookQuick(Long bookId) {
|
||||
BookEntity book = bookRepository.findById(bookId).orElseThrow(() -> ApiError.BOOK_NOT_FOUND.createException(bookId));
|
||||
EmailProviderEntity defaultEmailProvider = emailProviderRepository.findDefaultEmailProvider().orElseThrow(ApiError.DEFAULT_EMAIL_PROVIDER_NOT_FOUND::createException);
|
||||
EmailRecipientEntity defaultEmailRecipient = emailRecipientRepository.findDefaultEmailRecipient().orElseThrow(ApiError.DEFAULT_EMAIL_RECIPIENT_NOT_FOUND::createException);
|
||||
sendEmailInVirtualThread(defaultEmailProvider, defaultEmailRecipient.getEmail(), book);
|
||||
}
|
||||
|
||||
public void emailBook(SendBookByEmailRequest request) {
|
||||
EmailProviderEntity emailProvider = emailProviderRepository.findById(request.getProviderId()).orElseThrow(() -> ApiError.EMAIL_PROVIDER_NOT_FOUND.createException(request.getProviderId()));
|
||||
BookEntity book = bookRepository.findById(request.getBookId()).orElseThrow(() -> ApiError.BOOK_NOT_FOUND.createException(request.getBookId()));
|
||||
EmailRecipientEntity emailRecipient = emailRecipientRepository.findById(request.getRecipientId()).orElseThrow(() -> ApiError.EMAIL_RECIPIENT_NOT_FOUND.createException(request.getRecipientId()));
|
||||
sendEmailInVirtualThread(emailProvider, emailRecipient.getEmail(), book);
|
||||
}
|
||||
|
||||
private void sendEmailInVirtualThread(EmailProviderEntity emailProvider, String recipientEmail, BookEntity book) {
|
||||
String bookTitle = book.getMetadata().getTitle();
|
||||
String logMessage = "Email dispatch initiated for book: " + bookTitle + " to " + recipientEmail;
|
||||
notificationService.sendMessage(Topic.LOG, LogNotification.info(logMessage));
|
||||
log.info(logMessage);
|
||||
SecurityContextVirtualThread.runWithSecurityContext(() -> {
|
||||
try {
|
||||
sendEmail(emailProvider, recipientEmail, book);
|
||||
String successMessage = "The book: " + bookTitle + " has been successfully sent to " + recipientEmail;
|
||||
notificationService.sendMessage(Topic.LOG, LogNotification.info(successMessage));
|
||||
log.info(successMessage);
|
||||
} catch (Exception e) {
|
||||
String errorMessage = "An error occurred while sending the book: " + bookTitle + " to " + recipientEmail + ". Error: " + e.getMessage();
|
||||
notificationService.sendMessage(Topic.LOG, LogNotification.error(errorMessage));
|
||||
log.error(errorMessage, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sendEmail(EmailProviderEntity emailProvider, String recipientEmail, BookEntity book) throws MessagingException {
|
||||
JavaMailSenderImpl dynamicMailSender = setupMailSender(emailProvider);
|
||||
MimeMessage message = dynamicMailSender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
helper.setFrom(StringUtils.firstNonEmpty(emailProvider.getFromAddress(), emailProvider.getUsername()));
|
||||
helper.setTo(recipientEmail);
|
||||
helper.setSubject("Your Book from Booklore: " + book.getMetadata().getTitle());
|
||||
helper.setText(generateEmailBody(book.getMetadata().getTitle()));
|
||||
File bookFile = new File(FileUtils.getBookFullPath(book));
|
||||
helper.addAttachment(bookFile.getName(), bookFile);
|
||||
dynamicMailSender.send(message);
|
||||
log.info("Book sent successfully to {}", recipientEmail);
|
||||
}
|
||||
|
||||
private JavaMailSenderImpl setupMailSender(EmailProviderEntity emailProvider) {
|
||||
JavaMailSenderImpl dynamicMailSender = new JavaMailSenderImpl();
|
||||
dynamicMailSender.setHost(emailProvider.getHost());
|
||||
dynamicMailSender.setPort(emailProvider.getPort());
|
||||
dynamicMailSender.setUsername(emailProvider.getUsername());
|
||||
dynamicMailSender.setPassword(emailProvider.getPassword());
|
||||
|
||||
Properties mailProps = dynamicMailSender.getJavaMailProperties();
|
||||
mailProps.put("mail.smtp.auth", emailProvider.isAuth());
|
||||
|
||||
ConnectionType connectionType = determineConnectionType(emailProvider);
|
||||
configureConnectionType(mailProps, connectionType, emailProvider);
|
||||
configureTimeouts(mailProps);
|
||||
|
||||
String debugMode = System.getProperty("mail.debug", "false");
|
||||
mailProps.put("mail.debug", debugMode);
|
||||
|
||||
log.info("Email configuration: Host={}, Port={}, Type={}, Timeouts=60s", emailProvider.getHost(), emailProvider.getPort(), connectionType);
|
||||
|
||||
return dynamicMailSender;
|
||||
}
|
||||
|
||||
private ConnectionType determineConnectionType(EmailProviderEntity emailProvider) {
|
||||
if (emailProvider.getPort() == 465) {
|
||||
return ConnectionType.SSL;
|
||||
} else if (emailProvider.getPort() == 587 && emailProvider.isStartTls()) {
|
||||
return ConnectionType.STARTTLS;
|
||||
} else if (emailProvider.isStartTls()) {
|
||||
return ConnectionType.STARTTLS;
|
||||
} else {
|
||||
return ConnectionType.PLAIN;
|
||||
}
|
||||
}
|
||||
|
||||
private void configureConnectionType(Properties mailProps, ConnectionType connectionType, EmailProviderEntity emailProvider) {
|
||||
switch (connectionType) {
|
||||
case SSL -> {
|
||||
mailProps.put("mail.transport.protocol", "smtps");
|
||||
mailProps.put("mail.smtp.ssl.enable", "true");
|
||||
mailProps.put("mail.smtp.ssl.trust", emailProvider.getHost());
|
||||
mailProps.put("mail.smtp.starttls.enable", "false");
|
||||
mailProps.put("mail.smtp.ssl.protocols", "TLSv1.2,TLSv1.3");
|
||||
mailProps.put("mail.smtp.ssl.checkserveridentity", "false");
|
||||
mailProps.put("mail.smtp.ssl.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
|
||||
mailProps.put("mail.smtp.ssl.socketFactory.fallback", "false");
|
||||
}
|
||||
case STARTTLS -> {
|
||||
mailProps.put("mail.transport.protocol", "smtp");
|
||||
mailProps.put("mail.smtp.starttls.enable", "true");
|
||||
mailProps.put("mail.smtp.starttls.required", "true");
|
||||
mailProps.put("mail.smtp.ssl.enable", "false");
|
||||
}
|
||||
case PLAIN -> {
|
||||
mailProps.put("mail.transport.protocol", "smtp");
|
||||
mailProps.put("mail.smtp.starttls.enable", "false");
|
||||
mailProps.put("mail.smtp.ssl.enable", "false");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void configureTimeouts(Properties mailProps) {
|
||||
String connectionTimeout = System.getProperty("mail.smtp.connectiontimeout", "60000");
|
||||
String socketTimeout = System.getProperty("mail.smtp.timeout", "60000");
|
||||
String writeTimeout = System.getProperty("mail.smtp.writetimeout", "60000");
|
||||
|
||||
mailProps.put("mail.smtp.connectiontimeout", connectionTimeout);
|
||||
mailProps.put("mail.smtp.timeout", socketTimeout);
|
||||
mailProps.put("mail.smtp.writetimeout", writeTimeout);
|
||||
|
||||
log.debug("Configured email timeouts: connection={}, socket={}, write={}",
|
||||
connectionTimeout, socketTimeout, writeTimeout);
|
||||
}
|
||||
|
||||
private String generateEmailBody(String bookTitle) {
|
||||
return String.format("""
|
||||
Hey there,
|
||||
|
||||
You’ve received a new book from Booklore titled “%s” 📚
|
||||
|
||||
Grab a comfy spot, maybe a cup of tea ☕, and enjoy the story!
|
||||
""", bookTitle);
|
||||
}
|
||||
|
||||
private enum ConnectionType {
|
||||
SSL,
|
||||
STARTTLS,
|
||||
PLAIN
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.adityachandel.booklore.model.websocket.Topic;
|
||||
import com.adityachandel.booklore.repository.BookRepository;
|
||||
import com.adityachandel.booklore.repository.EmailProviderV2Repository;
|
||||
import com.adityachandel.booklore.repository.EmailRecipientV2Repository;
|
||||
import com.adityachandel.booklore.repository.UserEmailProviderPreferenceRepository;
|
||||
import com.adityachandel.booklore.service.NotificationService;
|
||||
import com.adityachandel.booklore.util.FileUtils;
|
||||
import com.adityachandel.booklore.util.SecurityContextVirtualThread;
|
||||
@@ -35,6 +36,7 @@ import static com.adityachandel.booklore.model.websocket.LogNotification.createL
|
||||
public class SendEmailV2Service {
|
||||
|
||||
private final EmailProviderV2Repository emailProviderRepository;
|
||||
private final UserEmailProviderPreferenceRepository preferenceRepository;
|
||||
private final BookRepository bookRepository;
|
||||
private final EmailRecipientV2Repository emailRecipientRepository;
|
||||
private final NotificationService notificationService;
|
||||
@@ -43,8 +45,8 @@ public class SendEmailV2Service {
|
||||
public void emailBookQuick(Long bookId) {
|
||||
BookLoreUser user = authenticationService.getAuthenticatedUser();
|
||||
BookEntity book = bookRepository.findById(bookId).orElseThrow(() -> ApiError.BOOK_NOT_FOUND.createException(bookId));
|
||||
EmailProviderV2Entity defaultEmailProvider = emailProviderRepository.findDefaultEmailProvider(user.getId()).orElseThrow(ApiError.DEFAULT_EMAIL_PROVIDER_NOT_FOUND::createException);
|
||||
EmailRecipientV2Entity defaultEmailRecipient = emailRecipientRepository.findDefaultEmailRecipient().orElseThrow(ApiError.DEFAULT_EMAIL_RECIPIENT_NOT_FOUND::createException);
|
||||
EmailProviderV2Entity defaultEmailProvider = getDefaultEmailProvider();
|
||||
EmailRecipientV2Entity defaultEmailRecipient = emailRecipientRepository.findDefaultEmailRecipientByUserId(user.getId()).orElseThrow(ApiError.DEFAULT_EMAIL_RECIPIENT_NOT_FOUND::createException);
|
||||
sendEmailInVirtualThread(defaultEmailProvider, defaultEmailRecipient.getEmail(), book);
|
||||
}
|
||||
|
||||
@@ -173,6 +175,17 @@ public class SendEmailV2Service {
|
||||
""", bookTitle);
|
||||
}
|
||||
|
||||
private EmailProviderV2Entity getDefaultEmailProvider() {
|
||||
BookLoreUser user = authenticationService.getAuthenticatedUser();
|
||||
|
||||
Long defaultProviderId = preferenceRepository.findByUserId(user.getId())
|
||||
.map(pref -> pref.getDefaultProviderId())
|
||||
.orElseThrow(ApiError.DEFAULT_EMAIL_PROVIDER_NOT_FOUND::createException);
|
||||
|
||||
return emailProviderRepository.findAccessibleProvider(defaultProviderId, user.getId())
|
||||
.orElseThrow(ApiError.DEFAULT_EMAIL_PROVIDER_NOT_FOUND::createException);
|
||||
}
|
||||
|
||||
private enum ConnectionType {
|
||||
SSL,
|
||||
STARTTLS,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE IF NOT EXISTS user_email_provider_preference
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
default_provider_id BIGINT NOT NULL,
|
||||
CONSTRAINT uq_user_id UNIQUE (user_id),
|
||||
CONSTRAINT fk_user_email_preference_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_user_email_preference_provider FOREIGN KEY (default_provider_id) REFERENCES email_provider_v2 (id) ON DELETE CASCADE
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ import {UrlHelperService} from '../../../../../shared/service/url-helper.service
|
||||
import {NgClass} from '@angular/common';
|
||||
import {UserService} from '../../../../settings/user-management/user.service';
|
||||
import {filter, Subject} from 'rxjs';
|
||||
import {EmailService} from '../../../../settings/email/email.service';
|
||||
import {EmailService} from '../../../../settings/email-v2/email.service';
|
||||
import {TieredMenu} from 'primeng/tieredmenu';
|
||||
import {BookSenderComponent} from '../../book-sender/book-sender.component';
|
||||
import {Router} from '@angular/router';
|
||||
@@ -269,7 +269,7 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (this.hasEmailBookPermission()) {
|
||||
items.push(
|
||||
{
|
||||
label: 'Send Book',
|
||||
label: 'Email Book',
|
||||
icon: 'pi pi-envelope',
|
||||
items: [{
|
||||
label: 'Quick Send',
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<p-button
|
||||
icon="pi pi-envelope"
|
||||
label="Send Book"
|
||||
label="Email Book"
|
||||
[disabled]="!selectedProvider || !selectedRecipient"
|
||||
(onClick)="sendBook()">
|
||||
</p-button>
|
||||
|
||||
@@ -2,9 +2,9 @@ import {Component, inject, OnInit} from '@angular/core';
|
||||
import {Button} from 'primeng/button';
|
||||
import {Select} from 'primeng/select';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {EmailProvider} from '../../../settings/email/email-provider/email-provider.model';
|
||||
import {EmailRecipient} from '../../../settings/email/email-recipient/email-recipient.model';
|
||||
import {EmailService} from '../../../settings/email/email.service';
|
||||
import {EmailProvider} from '../../../settings/email-v2/email-provider.model';
|
||||
import {EmailRecipient} from '../../../settings/email-v2/email-recipient.model';
|
||||
import {EmailService} from '../../../settings/email-v2/email.service';
|
||||
import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog';
|
||||
import {MessageService} from 'primeng/api';
|
||||
import {EmailV2ProviderService} from '../../../settings/email-v2/email-v2-provider/email-v2-provider.service';
|
||||
|
||||
@@ -12,7 +12,7 @@ import {SplitButton} from 'primeng/splitbutton';
|
||||
import {ConfirmationService, MenuItem, MessageService} from 'primeng/api';
|
||||
import {BookSenderComponent} from '../../../../book/components/book-sender/book-sender.component';
|
||||
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
|
||||
import {EmailService} from '../../../../settings/email/email.service';
|
||||
import {EmailService} from '../../../../settings/email-v2/email.service';
|
||||
import {ShelfAssignerComponent} from '../../../../book/components/shelf-assigner/shelf-assigner.component';
|
||||
import {Tooltip} from 'primeng/tooltip';
|
||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||
|
||||
@@ -6,7 +6,7 @@ import {InputText} from 'primeng/inputtext';
|
||||
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {MessageService} from 'primeng/api';
|
||||
import {DynamicDialogRef} from 'primeng/dynamicdialog';
|
||||
import {EmailV2ProviderService} from '../../email-v2/email-v2-provider/email-v2-provider.service';
|
||||
import {EmailV2ProviderService} from '../email-v2-provider/email-v2-provider.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-email-provider-dialog',
|
||||
@@ -5,7 +5,7 @@ import {DynamicDialogRef} from 'primeng/dynamicdialog';
|
||||
import {Checkbox} from 'primeng/checkbox';
|
||||
import {Button} from 'primeng/button';
|
||||
import {InputText} from 'primeng/inputtext';
|
||||
import {EmailV2RecipientService} from '../../email-v2/email-v2-recipient/email-v2-recipient.service';
|
||||
import {EmailV2RecipientService} from '../email-v2-recipient/email-v2-recipient.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-email-recipient-dialog',
|
||||
@@ -9,8 +9,8 @@ import {TableModule} from 'primeng/table';
|
||||
import {Tooltip} from 'primeng/tooltip';
|
||||
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
|
||||
import {EmailV2ProviderService} from './email-v2-provider.service';
|
||||
import {CreateEmailProviderDialogComponent} from '../../email/create-email-provider-dialog/create-email-provider-dialog.component';
|
||||
import {EmailProvider} from '../../email/email-provider/email-provider.model';
|
||||
import {CreateEmailProviderDialogComponent} from '../create-email-provider-dialog/create-email-provider-dialog.component';
|
||||
import {EmailProvider} from '../email-provider.model';
|
||||
import {UserService} from '../../user-management/user.service';
|
||||
|
||||
@Component({
|
||||
@@ -138,13 +138,23 @@ export class EmailV2ProviderComponent implements OnInit {
|
||||
}
|
||||
|
||||
setDefaultProvider(provider: EmailProvider) {
|
||||
this.emailProvidersService.setDefaultProvider(provider.id).subscribe(() => {
|
||||
this.defaultProviderId = provider.id;
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Default Provider Set',
|
||||
detail: `${provider.name} is now the default email provider.`
|
||||
});
|
||||
this.emailProvidersService.setDefaultProvider(provider.id).subscribe({
|
||||
next: () => {
|
||||
this.defaultProviderId = provider.id;
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Default Provider Set',
|
||||
detail: `${provider.name} is now the default email provider.`
|
||||
});
|
||||
},
|
||||
error: (err) => {
|
||||
console.error('Failed to set default provider', err);
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: `Failed to set ${provider.name} as the default provider. Please try again.`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import {inject, Injectable} from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
import {API_CONFIG} from '../../../../core/config/api-config';
|
||||
import {EmailProvider} from '../../email/email-provider/email-provider.model';
|
||||
import {EmailProvider} from '../email-provider.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
||||
@@ -8,8 +8,8 @@ import {TableModule} from 'primeng/table';
|
||||
import {Tooltip} from 'primeng/tooltip';
|
||||
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
|
||||
import {EmailV2RecipientService} from './email-v2-recipient.service';
|
||||
import {EmailRecipient} from '../../email/email-recipient/email-recipient.model';
|
||||
import {CreateEmailRecipientDialogComponent} from '../../email/create-email-recipient-dialog/create-email-recipient-dialog.component';
|
||||
import {EmailRecipient} from '../email-recipient.model';
|
||||
import {CreateEmailRecipientDialogComponent} from '../create-email-recipient-dialog/create-email-recipient-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-email-v2-recipient',
|
||||
|
||||
@@ -2,7 +2,7 @@ import { inject, Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { API_CONFIG } from '../../../../core/config/api-config';
|
||||
import {EmailRecipient} from '../../email/email-recipient/email-recipient.model';
|
||||
import {EmailRecipient} from '../email-recipient.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
<app-external-doc-link docType="email"></app-external-doc-link>
|
||||
</h2>
|
||||
<p class="settings-description">
|
||||
Configure email settings to send books directly to your devices or recipients. Set up email providers (like Gmail, Outlook, or custom SMTP servers) and manage recipient email addresses. In v2, users with email permission can now set up their own email providers and recipients without interfering with other users' configurations.
|
||||
Configure email settings to send books directly to your devices or recipients. Set up email providers (like Gmail, Outlook, or custom SMTP servers) and manage recipient email addresses.
|
||||
</p>
|
||||
<p class="migration-notice">
|
||||
<strong>Migration Notice:</strong> Support for legacy email has been removed. Please migrate to Email v2 by recreating providers and recipients. In v2, users can create their own private providers and recipients. Optionally, Booklore admins can share providers with users.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -30,6 +30,31 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.migration-notice {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--p-yellow-50);
|
||||
border-left: 4px solid var(--p-yellow-500);
|
||||
color: var(--p-yellow-900);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
border-radius: 0 4px 4px 0;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
background-color: rgba(245, 158, 11, 0.15);
|
||||
color: var(--p-yellow-200);
|
||||
border-left-color: var(--p-yellow-400);
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--p-yellow-800);
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: var(--p-yellow-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.access-denied-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import {inject, Injectable} from '@angular/core';
|
||||
import {API_CONFIG} from '../../../core/config/api-config';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EmailV2Service {
|
||||
|
||||
private readonly apiUrl = `${API_CONFIG.BASE_URL}/api/v2/emails`;
|
||||
|
||||
private http = inject(HttpClient);
|
||||
|
||||
emailBook(request: { bookId: number, providerId: number, recipientId: number }): Observable<void> {
|
||||
return this.http.post<void>(`${this.apiUrl}/send-book`, request);
|
||||
}
|
||||
|
||||
emailBookQuick(bookId: number): Observable<void> {
|
||||
return this.http.post<void>(`${this.apiUrl}/send-book/${bookId}`, {});
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
<div class="main-container enclosing-container">
|
||||
<div class="deprecation-banner" style="background: rgba(220, 38, 38, 0.1); color: #fca5a5; border: 1px solid rgba(220, 38, 38, 0.3); padding: 16px; margin-bottom: 16px; border-radius: 4px;">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<i class="pi pi-exclamation-triangle" style="font-size: 1.5rem; color: red;"></i>
|
||||
<div>
|
||||
<strong style="font-size: 1.1rem; color: darkorange;">DEPRECATED - NO LONGER FUNCTIONAL</strong>
|
||||
<p style="margin: 8px 0 0 0; font-size: 0.95rem; color: #fca5a5;">
|
||||
This Email feature has been deprecated and is <b>no longer operational</b>. All functionality has been disabled.
|
||||
Please <b>migrate to Email v2 immediately</b> to continue sending emails.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-header">
|
||||
<h2 class="settings-title">
|
||||
<i class="pi pi-envelope"></i>
|
||||
Email Providers
|
||||
</h2>
|
||||
<p class="settings-description">
|
||||
Configure email-sending services like Gmail, Outlook, or custom SMTP servers for sending books via email. The default email provider will be used for 'Quick Book Send' located in the Book Card menu.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<div class="preferences-section">
|
||||
<div class="section-header">
|
||||
<div class="section-title-group">
|
||||
<h3 class="section-title">
|
||||
<i class="pi pi-server"></i>
|
||||
Current Providers
|
||||
</h3>
|
||||
<p-button
|
||||
icon="pi pi-plus"
|
||||
label="Create Provider"
|
||||
severity="success"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
(onClick)="openCreateProviderDialog()"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<p-table [value]="emailProviders" [scrollable]="true" scrollHeight="flex">
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th style="width: 6%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-star"></i>
|
||||
<span>Default</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 10%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-tag"></i>
|
||||
<span>Name</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 15%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-server"></i>
|
||||
<span>Host</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 6%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-link"></i>
|
||||
<span>Port</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 14%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-user"></i>
|
||||
<span>Username</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 12%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-lock"></i>
|
||||
<span>Password</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 12%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-envelope"></i>
|
||||
<span>From Address</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 5%;" class="permission-header">Auth</th>
|
||||
<th style="width: 5%;" class="permission-header">StartTLS</th>
|
||||
<th style="width: 7%;" class="actions-header">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-pencil"></i>
|
||||
<span>Edit</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 8%;" class="actions-header">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-trash"></i>
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="body" let-provider>
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<p-radioButton
|
||||
name="defaultProvider"
|
||||
[value]="provider.id"
|
||||
[(ngModel)]="defaultProviderId"
|
||||
(onClick)="setDefaultProvider(provider)"
|
||||
[disabled]="true">
|
||||
</p-radioButton>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if (provider.isEditing) {
|
||||
<input type="text" [(ngModel)]="provider.name" class="p-inputtext w-full" size="small"/>
|
||||
}
|
||||
@if (!provider.isEditing) {
|
||||
<span>{{ provider.name }}</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if (provider.isEditing) {
|
||||
<input type="text" [(ngModel)]="provider.host" class="p-inputtext w-full" size="small"/>
|
||||
}
|
||||
@if (!provider.isEditing) {
|
||||
<span>{{ provider.host }}</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if (provider.isEditing) {
|
||||
<input type="number" [(ngModel)]="provider.port" class="p-inputtext w-full" size="small"/>
|
||||
}
|
||||
@if (!provider.isEditing) {
|
||||
<span>{{ provider.port }}</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if (provider.isEditing) {
|
||||
<input type="text" [(ngModel)]="provider.username" class="p-inputtext w-full" size="small"/>
|
||||
}
|
||||
@if (!provider.isEditing) {
|
||||
<span>{{ provider.username }}</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if (provider.isEditing) {
|
||||
<input [(ngModel)]="provider.password" class="p-inputtext w-full" size="small"/>
|
||||
}
|
||||
@if (!provider.isEditing) {
|
||||
<span class="password-hidden">Hidden</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if (provider.isEditing) {
|
||||
<input type="text" [(ngModel)]="provider.fromAddress" class="p-inputtext w-full" size="small"/>
|
||||
}
|
||||
@if (!provider.isEditing) {
|
||||
<span>{{ provider.fromAddress }}</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td class="text-center">
|
||||
<p-checkbox
|
||||
[(ngModel)]="provider.auth"
|
||||
[binary]="true"
|
||||
[disabled]="true">
|
||||
</p-checkbox>
|
||||
</td>
|
||||
|
||||
<td class="text-center">
|
||||
<p-checkbox
|
||||
[(ngModel)]="provider.startTls"
|
||||
[binary]="true"
|
||||
[disabled]="true">
|
||||
</p-checkbox>
|
||||
</td>
|
||||
|
||||
<td class="actions-cell">
|
||||
@if (!provider.isEditing) {
|
||||
<p-button
|
||||
icon="pi pi-pencil"
|
||||
severity="info"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
[rounded]="true"
|
||||
(onClick)="toggleEdit(provider)"
|
||||
pTooltip="Edit provider"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
}
|
||||
@if (provider.isEditing) {
|
||||
<div class="flex gap-1">
|
||||
<p-button
|
||||
icon="pi pi-check"
|
||||
severity="success"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
[rounded]="true"
|
||||
(onClick)="saveProvider(provider)"
|
||||
pTooltip="Save changes"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
<p-button
|
||||
icon="pi pi-times"
|
||||
severity="danger"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
[rounded]="true"
|
||||
(onClick)="toggleEdit(provider)"
|
||||
pTooltip="Cancel"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td class="actions-cell">
|
||||
<p-button
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
[rounded]="true"
|
||||
(onClick)="deleteProvider(provider)"
|
||||
pTooltip="Delete provider"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr>
|
||||
<td colspan="11">
|
||||
<div class="empty-message">
|
||||
<i class="pi pi-envelope"></i>
|
||||
<p class="empty-title">No email providers found</p>
|
||||
<p class="empty-subtitle">Create your first email provider to start sending books</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,167 +0,0 @@
|
||||
.enclosing-container {
|
||||
border-color: var(--p-content-border-color);
|
||||
background: var(--p-content-background);
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--p-text-color);
|
||||
margin: 0 0 0.75rem 0;
|
||||
|
||||
.pi {
|
||||
color: var(--p-primary-color);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-description {
|
||||
color: var(--p-text-muted-color);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.preferences-section {
|
||||
@media (min-width: 768px) {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.section-title-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media (max-width: 767px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
|
||||
p-button {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--p-text-color);
|
||||
|
||||
.pi {
|
||||
color: var(--p-primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
border: 1px solid var(--p-content-border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: var(--p-content-background);
|
||||
}
|
||||
|
||||
.p-datatable {
|
||||
.p-datatable-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.p-datatable-thead > tr > th {
|
||||
background: var(--p-surface-100);
|
||||
border-bottom: 2px solid var(--p-content-border-color);
|
||||
padding: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--p-text-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.p-datatable-tbody > tr {
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--p-surface-50);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-tbody > tr > td {
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable th .header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.permission-header {
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
padding: 0.75rem 0.5rem !important;
|
||||
min-width: 80px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.actions-header {
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.password-hidden {
|
||||
color: var(--p-text-muted-color);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
color: var(--p-text-muted-color);
|
||||
|
||||
.pi {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--p-surface-400);
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.empty-subtitle {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import {Button} from 'primeng/button';
|
||||
import {Checkbox} from 'primeng/checkbox';
|
||||
|
||||
import {MessageService, PrimeTemplate} from 'primeng/api';
|
||||
import {RadioButton} from 'primeng/radiobutton';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {TableModule} from 'primeng/table';
|
||||
import {Tooltip} from 'primeng/tooltip';
|
||||
import {EmailProvider} from './email-provider.model';
|
||||
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
|
||||
import {EmailProviderService} from './email-provider.service';
|
||||
import {CreateEmailProviderDialogComponent} from '../create-email-provider-dialog/create-email-provider-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-email-provider',
|
||||
imports: [
|
||||
Button,
|
||||
Checkbox,
|
||||
PrimeTemplate,
|
||||
RadioButton,
|
||||
ReactiveFormsModule,
|
||||
TableModule,
|
||||
Tooltip,
|
||||
FormsModule
|
||||
],
|
||||
templateUrl: './email-provider.component.html',
|
||||
styleUrl: './email-provider.component.scss'
|
||||
})
|
||||
export class EmailProviderComponent implements OnInit {
|
||||
emailProviders: EmailProvider[] = [];
|
||||
editingProviderIds: number[] = [];
|
||||
ref: DynamicDialogRef | undefined;
|
||||
private dialogService = inject(DialogService);
|
||||
private emailProvidersService = inject(EmailProviderService);
|
||||
private messageService = inject(MessageService);
|
||||
defaultProviderId: any;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadEmailProviders();
|
||||
}
|
||||
|
||||
loadEmailProviders(): void {
|
||||
this.emailProvidersService.getEmailProviders().subscribe({
|
||||
next: (emailProviders: EmailProvider[]) => {
|
||||
this.emailProviders = emailProviders.map((provider) => ({
|
||||
...provider,
|
||||
isEditing: false,
|
||||
}));
|
||||
const defaultProvider = emailProviders.find((provider) => provider.defaultProvider);
|
||||
this.defaultProviderId = defaultProvider ? defaultProvider.id : null;
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to load Email Providers',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toggleEdit(provider: EmailProvider): void {
|
||||
provider.isEditing = !provider.isEditing;
|
||||
if (provider.isEditing) {
|
||||
this.editingProviderIds.push(provider.id);
|
||||
} else {
|
||||
this.editingProviderIds = this.editingProviderIds.filter((id) => id !== provider.id);
|
||||
}
|
||||
}
|
||||
|
||||
saveProvider(provider: EmailProvider): void {
|
||||
this.emailProvidersService.updateProvider(provider).subscribe({
|
||||
next: () => {
|
||||
provider.isEditing = false;
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Success',
|
||||
detail: 'Provider updated successfully',
|
||||
});
|
||||
this.loadEmailProviders();
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to update provider',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
deleteProvider(provider: EmailProvider): void {
|
||||
if (confirm(`Are you sure you want to delete provider "${provider.name}"?`)) {
|
||||
this.emailProvidersService.deleteProvider(provider.id).subscribe({
|
||||
next: () => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Success',
|
||||
detail: `Provider "${provider.name}" deleted successfully`,
|
||||
});
|
||||
this.loadEmailProviders();
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to delete provider',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
openCreateProviderDialog() {
|
||||
this.ref = this.dialogService.open(CreateEmailProviderDialogComponent, {
|
||||
header: 'Create Email Provider',
|
||||
modal: true,
|
||||
closable: true,
|
||||
style: {position: 'absolute', top: '15%'},
|
||||
});
|
||||
this.ref.onClose.subscribe((result) => {
|
||||
if (result) {
|
||||
this.loadEmailProviders();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDefaultProvider(provider: EmailProvider) {
|
||||
this.emailProvidersService.setDefaultProvider(provider.id).subscribe(() => {
|
||||
this.defaultProviderId = provider.id;
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Default Provider Set',
|
||||
detail: `${provider.name} is now the default email provider.`
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import {inject, Injectable} from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
import {EmailProvider} from './email-provider.model';
|
||||
import {API_CONFIG} from '../../../../core/config/api-config';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EmailProviderService {
|
||||
private readonly url = `${API_CONFIG.BASE_URL}/api/v1/email/providers`;
|
||||
|
||||
private http = inject(HttpClient);
|
||||
|
||||
getEmailProviders(): Observable<EmailProvider[]> {
|
||||
return this.http.get<EmailProvider[]>(this.url);
|
||||
}
|
||||
|
||||
createEmailProvider(provider: EmailProvider): Observable<EmailProvider> {
|
||||
return this.http.post<EmailProvider>(this.url, provider);
|
||||
}
|
||||
|
||||
updateProvider(provider: EmailProvider): Observable<EmailProvider> {
|
||||
return this.http.put<EmailProvider>(`${this.url}/${provider.id}`, provider);
|
||||
}
|
||||
|
||||
deleteProvider(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.url}/${id}`);
|
||||
}
|
||||
|
||||
setDefaultProvider(id: number): Observable<void> {
|
||||
return this.http.patch<void>(`${this.url}/${id}/set-default`, {});
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
<div class="main-container enclosing-container">
|
||||
<div class="settings-header">
|
||||
<h2 class="settings-title">
|
||||
<i class="pi pi-users"></i>
|
||||
Recipient Emails
|
||||
</h2>
|
||||
<p class="settings-description">
|
||||
Manage the list of recipients who will receive books via email. The 'Default' recipient will be used for 'Quick Book Send,' located in the Book Card menu.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-content">
|
||||
<div class="preferences-section">
|
||||
<div class="section-header">
|
||||
<div class="section-title-group">
|
||||
<h3 class="section-title">
|
||||
<i class="pi pi-envelope"></i>
|
||||
Current Recipients
|
||||
</h3>
|
||||
<p-button
|
||||
icon="pi pi-plus"
|
||||
label="Create Recipient"
|
||||
severity="success"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
(onClick)="openAddRecipientDialog()"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-card">
|
||||
<p-table [value]="recipientEmails" [scrollable]="true" scrollHeight="flex">
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th style="width: 10%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-star"></i>
|
||||
<span>Default</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 45%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-envelope"></i>
|
||||
<span>Email Address</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 30%;">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-user"></i>
|
||||
<span>Name</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 8%;" class="actions-header">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-pencil"></i>
|
||||
<span>Edit</span>
|
||||
</div>
|
||||
</th>
|
||||
<th style="width: 7%;" class="actions-header">
|
||||
<div class="header-content">
|
||||
<i class="pi pi-trash"></i>
|
||||
<span>Delete</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="body" let-recipient>
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<p-radioButton
|
||||
name="defaultRecipient"
|
||||
[value]="recipient.id"
|
||||
[(ngModel)]="defaultRecipientId"
|
||||
(onClick)="setDefaultRecipient(recipient)"
|
||||
[disabled]="true">
|
||||
</p-radioButton>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if (recipient.isEditing) {
|
||||
<input type="email" [(ngModel)]="recipient.email" class="p-inputtext w-full" size="small"/>
|
||||
}
|
||||
@if (!recipient.isEditing) {
|
||||
<span>{{ recipient.email }}</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td>
|
||||
@if (recipient.isEditing) {
|
||||
<input type="text" [(ngModel)]="recipient.name" class="p-inputtext w-full" size="small"/>
|
||||
}
|
||||
@if (!recipient.isEditing) {
|
||||
<span>{{ recipient.name }}</span>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td class="actions-cell">
|
||||
@if (!recipient.isEditing) {
|
||||
<p-button
|
||||
icon="pi pi-pencil"
|
||||
severity="info"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
[rounded]="true"
|
||||
(onClick)="toggleEditRecipient(recipient)"
|
||||
pTooltip="Edit recipient"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
}
|
||||
@if (recipient.isEditing) {
|
||||
<div class="flex gap-1">
|
||||
<p-button
|
||||
icon="pi pi-check"
|
||||
severity="success"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
[rounded]="true"
|
||||
(onClick)="saveRecipient(recipient)"
|
||||
pTooltip="Save changes"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
<p-button
|
||||
icon="pi pi-times"
|
||||
severity="danger"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
[rounded]="true"
|
||||
(onClick)="toggleEditRecipient(recipient)"
|
||||
pTooltip="Cancel"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
|
||||
<td class="actions-cell">
|
||||
<p-button
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
size="small"
|
||||
[outlined]="true"
|
||||
[rounded]="true"
|
||||
(onClick)="deleteRecipient(recipient)"
|
||||
pTooltip="Delete recipient"
|
||||
[disabled]="true">
|
||||
</p-button>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<div class="empty-message">
|
||||
<i class="pi pi-users"></i>
|
||||
<p class="empty-title">No recipients found</p>
|
||||
<p class="empty-subtitle">Add your first email recipient to start sending books</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,144 +0,0 @@
|
||||
.enclosing-container {
|
||||
border-color: var(--p-content-border-color);
|
||||
background: var(--p-content-background);
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--p-text-color);
|
||||
margin: 0 0 0.75rem 0;
|
||||
|
||||
.pi {
|
||||
color: var(--p-primary-color);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-description {
|
||||
color: var(--p-text-muted-color);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.settings-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.preferences-section {
|
||||
@media (min-width: 768px) {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.section-header {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.section-title-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--p-text-color);
|
||||
|
||||
.pi {
|
||||
color: var(--p-primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
.table-card {
|
||||
border: 1px solid var(--p-content-border-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: var(--p-content-background);
|
||||
}
|
||||
|
||||
.p-datatable {
|
||||
.p-datatable-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
.p-datatable-thead > tr > th {
|
||||
background: var(--p-surface-100);
|
||||
border-bottom: 2px solid var(--p-content-border-color);
|
||||
padding: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--p-text-color);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.p-datatable-tbody > tr {
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: var(--p-surface-50);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-tbody > tr > td {
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable th .header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.actions-header {
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.actions-cell {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-message {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem;
|
||||
color: var(--p-text-muted-color);
|
||||
|
||||
.pi {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--p-surface-400);
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.empty-subtitle {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import {Button} from 'primeng/button';
|
||||
|
||||
import {MessageService, PrimeTemplate} from 'primeng/api';
|
||||
import {RadioButton} from 'primeng/radiobutton';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {TableModule} from 'primeng/table';
|
||||
import {Tooltip} from 'primeng/tooltip';
|
||||
import {EmailProvider} from '../email-provider/email-provider.model';
|
||||
import {EmailRecipient} from './email-recipient.model';
|
||||
import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog';
|
||||
import {EmailProviderService} from '../email-provider/email-provider.service';
|
||||
import {EmailRecipientService} from './email-recipient.service';
|
||||
import {CreateEmailProviderDialogComponent} from '../create-email-provider-dialog/create-email-provider-dialog.component';
|
||||
import {CreateEmailRecipientDialogComponent} from '../create-email-recipient-dialog/create-email-recipient-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-email-recipient',
|
||||
imports: [
|
||||
Button,
|
||||
PrimeTemplate,
|
||||
RadioButton,
|
||||
ReactiveFormsModule,
|
||||
TableModule,
|
||||
Tooltip,
|
||||
FormsModule
|
||||
],
|
||||
templateUrl: './email-recipient.component.html',
|
||||
styleUrl: './email-recipient.component.scss'
|
||||
})
|
||||
export class EmailRecipientComponent implements OnInit {
|
||||
recipientEmails: EmailRecipient[] = [];
|
||||
editingRecipientIds: number[] = [];
|
||||
ref: DynamicDialogRef | undefined;
|
||||
private dialogService = inject(DialogService);
|
||||
private emailRecipientService = inject(EmailRecipientService);
|
||||
private messageService = inject(MessageService);
|
||||
defaultRecipientId: any;
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadRecipientEmails();
|
||||
}
|
||||
|
||||
loadRecipientEmails(): void {
|
||||
this.emailRecipientService.getRecipients().subscribe({
|
||||
next: (recipients: EmailRecipient[]) => {
|
||||
this.recipientEmails = recipients.map((recipient) => ({
|
||||
...recipient,
|
||||
isEditing: false,
|
||||
}));
|
||||
const defaultRecipient = recipients.find((recipient) => recipient.defaultRecipient);
|
||||
this.defaultRecipientId = defaultRecipient ? defaultRecipient.id : null;
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to load recipient emails',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
toggleEditRecipient(recipient: EmailRecipient): void {
|
||||
recipient.isEditing = !recipient.isEditing;
|
||||
if (recipient.isEditing) {
|
||||
this.editingRecipientIds.push(recipient.id);
|
||||
} else {
|
||||
this.editingRecipientIds = this.editingRecipientIds.filter((id) => id !== recipient.id);
|
||||
}
|
||||
}
|
||||
|
||||
saveRecipient(recipient: EmailRecipient): void {
|
||||
this.emailRecipientService.updateRecipient(recipient).subscribe({
|
||||
next: () => {
|
||||
recipient.isEditing = false;
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Success',
|
||||
detail: 'Recipient updated successfully',
|
||||
});
|
||||
this.loadRecipientEmails();
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to update recipient',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
deleteRecipient(recipient: EmailRecipient): void {
|
||||
if (confirm(`Are you sure you want to delete recipient "${recipient.email}"?`)) {
|
||||
this.emailRecipientService.deleteRecipient(recipient.id).subscribe({
|
||||
next: () => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Success',
|
||||
detail: `Recipient "${recipient.email}" deleted successfully`,
|
||||
});
|
||||
this.loadRecipientEmails();
|
||||
},
|
||||
error: () => {
|
||||
this.messageService.add({
|
||||
severity: 'error',
|
||||
summary: 'Error',
|
||||
detail: 'Failed to delete recipient',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
openAddRecipientDialog() {
|
||||
this.ref = this.dialogService.open(CreateEmailRecipientDialogComponent, {
|
||||
header: 'Add New Recipient',
|
||||
modal: true,
|
||||
closable: true,
|
||||
style: {position: 'absolute', top: '15%'},
|
||||
});
|
||||
this.ref.onClose.subscribe((result) => {
|
||||
if (result) {
|
||||
this.loadRecipientEmails();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setDefaultRecipient(recipient: EmailRecipient) {
|
||||
this.emailRecipientService.setDefaultRecipient(recipient.id).subscribe(() => {
|
||||
this.defaultRecipientId = recipient.id;
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'Default Recipient Set',
|
||||
detail: `${recipient.email} is now the default recipient.`
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { API_CONFIG } from '../../../../core/config/api-config';
|
||||
import {EmailRecipient} from './email-recipient.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EmailRecipientService {
|
||||
private readonly url = `${API_CONFIG.BASE_URL}/api/v1/email/recipients`;
|
||||
|
||||
private http = inject(HttpClient);
|
||||
|
||||
getRecipients(): Observable<EmailRecipient[]> {
|
||||
return this.http.get<EmailRecipient[]>(this.url);
|
||||
}
|
||||
|
||||
createRecipient(recipient: EmailRecipient): Observable<EmailRecipient> {
|
||||
return this.http.post<EmailRecipient>(this.url, recipient);
|
||||
}
|
||||
|
||||
updateRecipient(recipient: EmailRecipient): Observable<EmailRecipient> {
|
||||
return this.http.put<EmailRecipient>(`${this.url}/${recipient.id}`, recipient);
|
||||
}
|
||||
|
||||
deleteRecipient(id: number): Observable<void> {
|
||||
return this.http.delete<void>(`${this.url}/${id}`);
|
||||
}
|
||||
|
||||
setDefaultRecipient(id: number): Observable<void> {
|
||||
return this.http.patch<void>(`${this.url}/${id}/set-default`, {});
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
<div class="w-full h-[calc(100dvh-10.5rem)] md:h-[calc(100dvh-11.65rem)] overflow-y-auto border rounded-lg p-4 enclosing-container">
|
||||
<div class="pb-8">
|
||||
<app-email-provider></app-email-provider>
|
||||
</div>
|
||||
<p-divider></p-divider>
|
||||
<div class="pt-4">
|
||||
<app-email-recipient></app-email-recipient>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,3 +0,0 @@
|
||||
.enclosing-container {
|
||||
border-color: var(--p-content-border-color);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {TableModule} from 'primeng/table';
|
||||
import {EmailProviderComponent} from './email-provider/email-provider.component';
|
||||
import {EmailRecipientComponent} from './email-recipient/email-recipient.component';
|
||||
import {Divider} from 'primeng/divider';
|
||||
|
||||
@Component({
|
||||
selector: 'app-email',
|
||||
imports: [
|
||||
FormsModule,
|
||||
TableModule,
|
||||
EmailProviderComponent,
|
||||
EmailRecipientComponent,
|
||||
Divider
|
||||
],
|
||||
templateUrl: './email.component.html',
|
||||
styleUrls: ['./email.component.scss'],
|
||||
})
|
||||
export class EmailComponent {
|
||||
|
||||
}
|
||||
@@ -20,10 +20,7 @@
|
||||
<i class="pi pi-cog"></i> Application
|
||||
</p-tab>
|
||||
<p-tab [value]="SettingsTab.UserManagement">
|
||||
<i class="pi pi-users"></i> User
|
||||
</p-tab>
|
||||
<p-tab [value]="SettingsTab.EmailSettings">
|
||||
<i class="pi pi-envelope"></i> Email
|
||||
<i class="pi pi-users"></i> Users
|
||||
</p-tab>
|
||||
}
|
||||
<p-tab [value]="SettingsTab.EmailSettingsV2">
|
||||
@@ -68,9 +65,6 @@
|
||||
<p-tabpanel [value]="SettingsTab.UserManagement">
|
||||
<app-user-management></app-user-management>
|
||||
</p-tabpanel>
|
||||
<p-tabpanel [value]="SettingsTab.EmailSettings">
|
||||
<app-email></app-email>
|
||||
</p-tabpanel>
|
||||
}
|
||||
<p-tabpanel [value]="SettingsTab.EmailSettingsV2">
|
||||
<app-email-v2></app-email-v2>
|
||||
|
||||
@@ -2,7 +2,6 @@ import {Component, inject, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Tab, TabList, TabPanel, TabPanels, Tabs} from 'primeng/tabs';
|
||||
import {UserService} from './user-management/user.service';
|
||||
import {AsyncPipe} from '@angular/common';
|
||||
import {EmailComponent} from './email/email.component';
|
||||
import {GlobalPreferencesComponent} from './global-preferences/global-preferences.component';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {Subscription} from 'rxjs';
|
||||
@@ -23,7 +22,6 @@ export enum SettingsTab {
|
||||
ViewPreferences = 'view',
|
||||
DeviceSettings = 'device',
|
||||
UserManagement = 'user',
|
||||
EmailSettings = 'email',
|
||||
EmailSettingsV2 = 'email-v2',
|
||||
NamingPattern = 'naming-pattern',
|
||||
MetadataSettings = 'metadata',
|
||||
@@ -31,7 +29,7 @@ export enum SettingsTab {
|
||||
ApplicationSettings = 'application',
|
||||
AuthenticationSettings = 'authentication',
|
||||
OpdsV2 = 'opds',
|
||||
Tasks = 'tasks',
|
||||
Tasks = 'task',
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -43,7 +41,6 @@ export enum SettingsTab {
|
||||
TabPanels,
|
||||
TabPanel,
|
||||
AsyncPipe,
|
||||
EmailComponent,
|
||||
GlobalPreferencesComponent,
|
||||
UserManagementComponent,
|
||||
AuthenticationSettingsComponent,
|
||||
|
||||
Reference in New Issue
Block a user