To jest wiadomość testowa potwierdzająca poprawną konfigurację poczty SMTP.
"); + return TestResultResponse.from(result); + } + + @PostMapping("/sms/test") + public TestResultResponse testSms(@Valid @RequestBody TestSendRequest request) { + SendResult result = smsSender.send( + mailConfigService.getSms(), + request.recipient().trim(), + "Test konfiguracji bramki SMS – Polska Lokalnie"); + return TestResultResponse.from(result); + } + + public record TestSendRequest(@NotBlank String recipient) { + } + + public record TestResultResponse(boolean ok, String providerMessageId, String error) { + static TestResultResponse from(SendResult result) { + return new TestResultResponse(result.ok(), result.providerMessageId(), result.error()); + } + } +} diff --git a/backend/src/main/java/pl/polskalokalnie/mailconfig/MailConfigService.java b/backend/src/main/java/pl/polskalokalnie/mailconfig/MailConfigService.java new file mode 100644 index 0000000..baa314a --- /dev/null +++ b/backend/src/main/java/pl/polskalokalnie/mailconfig/MailConfigService.java @@ -0,0 +1,91 @@ +package pl.polskalokalnie.mailconfig; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Dostep do jednorekordowej konfiguracji SMTP i bramki SMS. Sekrety (haslo, klucz API) sa + * nadpisywane tylko przy jawnej zmianie; puste pole w zadaniu zachowuje dotychczasowa wartosc. + * Metody get* zwracaja encje z sekretami wylacznie na uzytek wewnetrzny (sendery) - nie sa + * serializowane do klienta. + */ +@Service +public class MailConfigService { + + private static final long SINGLETON_ID = 1L; + + private final SmtpConfigRepository smtpConfigRepository; + private final SmsGatewayConfigRepository smsGatewayConfigRepository; + + public MailConfigService(SmtpConfigRepository smtpConfigRepository, + SmsGatewayConfigRepository smsGatewayConfigRepository) { + this.smtpConfigRepository = smtpConfigRepository; + this.smsGatewayConfigRepository = smsGatewayConfigRepository; + } + + @Transactional + public SmtpConfig getSmtp() { + return smtpConfigRepository.findById(SINGLETON_ID).orElseGet(() -> { + SmtpConfig config = new SmtpConfig(); + config.setId(SINGLETON_ID); + return smtpConfigRepository.save(config); + }); + } + + @Transactional + public SmsGatewayConfig getSms() { + return smsGatewayConfigRepository.findById(SINGLETON_ID).orElseGet(() -> { + SmsGatewayConfig config = new SmsGatewayConfig(); + config.setId(SINGLETON_ID); + return smsGatewayConfigRepository.save(config); + }); + } + + @Transactional + public SmtpConfig updateSmtp(SmtpConfigRequest request) { + SmtpConfig config = getSmtp(); + config.setHost(trimToNull(request.host())); + config.setPort(request.port()); + if (request.sslEnabled() != null) { + config.setSslEnabled(request.sslEnabled()); + } + config.setUsername(trimToNull(request.username())); + config.setFromName(trimToNull(request.fromName())); + config.setContactFormRecipient(trimToNull(request.contactFormRecipient())); + if (request.enabled() != null) { + config.setEnabled(request.enabled()); + } + // Haslo zmieniamy tylko gdy podane (niepuste) - inaczej zachowujemy dotychczasowe. + String password = request.password(); + if (password != null && !password.isBlank()) { + config.setPassword(password); + } + return smtpConfigRepository.save(config); + } + + @Transactional + public SmsGatewayConfig updateSms(SmsConfigRequest request) { + SmsGatewayConfig config = getSms(); + config.setEndpointUrl(trimToNull(request.endpointUrl())); + config.setCreator(trimToNull(request.creator())); + if (request.timeoutSeconds() != null) { + config.setTimeoutSeconds(request.timeoutSeconds()); + } + if (request.enabled() != null) { + config.setEnabled(request.enabled()); + } + String apiKey = request.apiKey(); + if (apiKey != null && !apiKey.isBlank()) { + config.setApiKey(apiKey); + } + return smsGatewayConfigRepository.save(config); + } + + private static String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } +} diff --git a/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsConfigRequest.java b/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsConfigRequest.java new file mode 100644 index 0000000..7b76afa --- /dev/null +++ b/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsConfigRequest.java @@ -0,0 +1,13 @@ +package pl.polskalokalnie.mailconfig; + +/** + * Zapis konfiguracji bramki SMS. Pole {@code apiKey} nadpisuje klucz tylko gdy niepuste. + */ +public record SmsConfigRequest( + String endpointUrl, + String apiKey, + String creator, + Integer timeoutSeconds, + Boolean enabled +) { +} diff --git a/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsConfigResponse.java b/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsConfigResponse.java new file mode 100644 index 0000000..de6dafb --- /dev/null +++ b/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsConfigResponse.java @@ -0,0 +1,23 @@ +package pl.polskalokalnie.mailconfig; + +/** + * Odpowiedz konfiguracji bramki SMS - bez klucza API. {@code apiKeySet} sygnalizuje jedynie, czy + * klucz jest zapisany (badge "ustawione"). + */ +public record SmsConfigResponse( + String endpointUrl, + String creator, + Integer timeoutSeconds, + boolean enabled, + boolean apiKeySet +) { + public static SmsConfigResponse from(SmsGatewayConfig config) { + return new SmsConfigResponse( + config.getEndpointUrl(), + config.getCreator(), + config.getTimeoutSeconds(), + config.isEnabled(), + config.getApiKey() != null && !config.getApiKey().isBlank() + ); + } +} diff --git a/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsGatewayConfig.java b/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsGatewayConfig.java new file mode 100644 index 0000000..1357b88 --- /dev/null +++ b/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsGatewayConfig.java @@ -0,0 +1,81 @@ +package pl.polskalokalnie.mailconfig; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +/** + * Konfiguracja bramki SMS (SoftSPM) - pojedynczy rekord (id = 1). Klucz API trzymany w bazie, + * nigdy zwracany w odpowiedziach API (maskowany) ani logowany. + */ +@Entity +@Table(name = "sms_gateway_config") +public class SmsGatewayConfig { + + @Id + private Long id = 1L; + + @Column(length = 400) + private String endpointUrl; + + @Column(length = 400) + private String apiKey; + + // Pole "creator" bramki - znacznik zrodla wiadomosci, max 50 znakow. + @Column(length = 50) + private String creator; + + private Integer timeoutSeconds = 10; + + @Column(nullable = false) + private boolean enabled = false; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getEndpointUrl() { + return endpointUrl; + } + + public void setEndpointUrl(String endpointUrl) { + this.endpointUrl = endpointUrl; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + public Integer getTimeoutSeconds() { + return timeoutSeconds; + } + + public void setTimeoutSeconds(Integer timeoutSeconds) { + this.timeoutSeconds = timeoutSeconds; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } +} diff --git a/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsGatewayConfigRepository.java b/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsGatewayConfigRepository.java new file mode 100644 index 0000000..cd2d90d --- /dev/null +++ b/backend/src/main/java/pl/polskalokalnie/mailconfig/SmsGatewayConfigRepository.java @@ -0,0 +1,6 @@ +package pl.polskalokalnie.mailconfig; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SmsGatewayConfigRepository extends JpaRepository" + + "Otrzymujesz tę wiadomość, ponieważ wyraziłeś zgodę na kontakt marketingowy. " + + "Zrezygnuj z otrzymywania wiadomości.
"; + return "" + message + "
{error}
} + + {loading ? ( +Ładowanie...
+ ) : leads.length === 0 ? ( +Brak leadów. Dodaj kontakt lub zaimportuj listę.
+ ) : ( +| Nazwa | Kontakt | Miasto | Źródło | Status | Zgody | + |
|---|---|---|---|---|---|---|
| {lead.name || '—'} | +
+
+ {lead.email && {lead.email}}
+ {lead.phone && {lead.phone}}
+
+ |
+ {lead.city || '—'} | +{SOURCE_LABEL[lead.source]} | +{STATUS_LABEL[lead.status]} | +
+
+ @
+ SMS
+
+ |
+ + {!(lead.emailOptOut && lead.smsOptOut) && ( + + )} + + | +
Kolumny: nazwa;email;telefon;miasto — jeden kontakt w wierszu, nagłówek opcjonalny.
Grupa to dynamiczny segment — odbiorcy wyliczają się na bieżąco z kryteriów.
+{error}
} + +Ładowanie...
+ ) : groups.length === 0 ? ( +Brak grup. Utwórz pierwszy segment.
+ ) : ( +{error}
} + +Ładowanie...
+ ) : campaigns.length === 0 ? ( +Brak kampanii.
+ ) : ( +Ładowanie konfiguracji SMTP...
{error}
} + {note &&{note}
} +{testResult}
} +Ładowanie konfiguracji SMS...
{error}
} + {note &&{note}
} +{testResult}
} +