diff --git a/backend/src/main/java/com/softspm/webinar/mail/MailService.java b/backend/src/main/java/com/softspm/webinar/mail/MailService.java index f66bd82..4d842fb 100644 --- a/backend/src/main/java/com/softspm/webinar/mail/MailService.java +++ b/backend/src/main/java/com/softspm/webinar/mail/MailService.java @@ -52,17 +52,20 @@ public class MailService { private final IcsGenerator icsGenerator; private final ReminderTextBuilder reminderTextBuilder; private final SafeDelivery safeDelivery; + private final com.softspm.webinar.video.VideoProviderFactory videoProviderFactory; public MailService(SettingsService settings, SpringTemplateEngine mailTemplateEngine, IcsGenerator icsGenerator, ReminderTextBuilder reminderTextBuilder, - SafeDelivery safeDelivery) { + SafeDelivery safeDelivery, + com.softspm.webinar.video.VideoProviderFactory videoProviderFactory) { this.settings = settings; this.mailTemplateEngine = mailTemplateEngine; this.icsGenerator = icsGenerator; this.reminderTextBuilder = reminderTextBuilder; this.safeDelivery = safeDelivery; + this.videoProviderFactory = videoProviderFactory; } /** @@ -234,11 +237,22 @@ public class MailService { context.setVariable("cancelUrl", publicUrl + "/zapis/" + registration.getConfirmationToken()); context.setVariable("brandName", settings.get(SettingKey.APP_BRAND_NAME)); - // Uzywane przez szablon przypomnienia. Pusty joinUrl przekazujemy jako null, - // zeby warunek th:if w szablonie zadzialal i pokazal informacje zastepcza. + /* + * Link do spotkania. Uzywany przez szablon potwierdzenia i przypomnienia. + * + * Adres sklada `VideoProviderFactory.meetingUrlFor`, bo zalezy od + * dostawcy: tryb NONE oddaje `join_url` wklejony przez organizatora, + * a kazdy inny wlasna trase `/spotkanie/{token}`. Samo czytanie + * `webinar.getJoinUrl()` dawalo przy LiveKit null, wiec uczestnik + * dostawal wylacznie odnosnik do strony zapisu, gdzie przycisk wejscia + * takze sie nie pokazywal: nie mial ZADNEJ drogi na sale. + * + * Pusty adres przekazujemy jako null, zeby warunek th:if w szablonie + * zadzialal i pokazal informacje zastepcza. + */ context.setVariable("countdown", reminderTextBuilder.countdown(webinar, OffsetDateTime.now())); context.setVariable("joinUrl", - webinar.getJoinUrl() == null || webinar.getJoinUrl().isBlank() ? null : webinar.getJoinUrl()); + videoProviderFactory.meetingUrlFor(webinar, registration.getConfirmationToken())); String html = mailTemplateEngine.process("mail/" + template + ".html", context); String text = mailTemplateEngine.process("mail/" + template + ".txt", context); diff --git a/backend/src/main/java/com/softspm/webinar/registration/RegistrationService.java b/backend/src/main/java/com/softspm/webinar/registration/RegistrationService.java index 603ea17..be404b5 100644 --- a/backend/src/main/java/com/softspm/webinar/registration/RegistrationService.java +++ b/backend/src/main/java/com/softspm/webinar/registration/RegistrationService.java @@ -58,6 +58,7 @@ public class RegistrationService { private final com.softspm.webinar.contact.InvitationService invitationService; private final com.softspm.webinar.repository.InvitationRepository invitationRepository; private final com.softspm.webinar.repository.CertificateRepository certificateRepository; + private final com.softspm.webinar.video.VideoProviderFactory videoProviderFactory; public RegistrationService(WebinarRepository webinarRepository, RegistrationRepository registrationRepository, @@ -71,7 +72,8 @@ public class RegistrationService { com.softspm.webinar.contact.ContactService contactService, com.softspm.webinar.contact.InvitationService invitationService, com.softspm.webinar.repository.InvitationRepository invitationRepository, - com.softspm.webinar.repository.CertificateRepository certificateRepository) { + com.softspm.webinar.repository.CertificateRepository certificateRepository, + com.softspm.webinar.video.VideoProviderFactory videoProviderFactory) { this.webinarRepository = webinarRepository; this.registrationRepository = registrationRepository; this.consentRepository = consentRepository; @@ -85,6 +87,7 @@ public class RegistrationService { this.invitationService = invitationService; this.invitationRepository = invitationRepository; this.certificateRepository = certificateRepository; + this.videoProviderFactory = videoProviderFactory; } /** @@ -272,10 +275,19 @@ public class RegistrationService { Registration registration = requireByToken(token); Webinar webinar = registration.getWebinar(); - // link do spotkania tylko dla zapisu aktywnego + /* + * Link do spotkania tylko dla zapisu aktywnego. + * + * Adres sklada `VideoProviderFactory.meetingUrlFor`, bo zalezy od + * dostawcy: tryb NONE oddaje `join_url` wklejony przez organizatora, + * a kazdy inny wlasna trase `/spotkanie/{token}`. Samo czytanie + * `webinar.getJoinUrl()` dawalo przy LiveKit null, wiec przycisk + * "Dolacz do spotkania" nie renderowal sie wcale i uczestnik nie mial + * jak wejsc na sale. + */ String joinUrl = registration.getStatus() == RegistrationStatus.CANCELLED ? null - : webinar.getJoinUrl(); + : videoProviderFactory.meetingUrlFor(webinar, registration.getConfirmationToken()); /* * Nagrania i materialy pokazujemy dopiero po zakonczeniu webinaru diff --git a/backend/src/main/java/com/softspm/webinar/video/VideoProviderFactory.java b/backend/src/main/java/com/softspm/webinar/video/VideoProviderFactory.java index a9f27d5..02c89d9 100644 --- a/backend/src/main/java/com/softspm/webinar/video/VideoProviderFactory.java +++ b/backend/src/main/java/com/softspm/webinar/video/VideoProviderFactory.java @@ -120,6 +120,47 @@ public class VideoProviderFactory { return existing != null && !existing.name().equals(forWebinar(webinar).name()); } + /** + * Adres, pod ktorym uczestnik wchodzi na spotkanie. + * + * DWA PRZYPADKI, DWA ROZNE ADRESY + * ------------------------------------------------------------------ + * Tryb NONE nie ma wlasnej sali: obraz jest w narzedziu organizatora, + * a uczestnik idzie prosto pod `join_url` wklejony w panelu. Kazdy inny + * dostawca ma sale U NAS, pod trasa `/spotkanie/{token}`, i wlasnie ten + * adres ma dostac uczestnik. + * + * Wczesniej mail i strona zgloszenia pokazywaly wylacznie `join_url`, + * ktory przy LiveKit jest pusty (pokoj jest wewnetrzny). Skutek: + * **uczestnik nie mial ZADNEJ drogi na sale**. Mail obiecywal "link do + * spotkania znajdziesz na stronie swojego zgloszenia", a tam przycisk + * nie renderowal sie wcale, bo warunek sprawdzal puste pole. + * + * Zwraca null, gdy adresu nie da sie zlozyc: przy pustym `APP_PUBLIC_URL` + * powstalby link wzgledny, w kliencie poczty prowadzacy donikad. + */ + public String meetingUrlFor(Webinar webinar, String registrationToken) { + if (webinar == null || registrationToken == null || registrationToken.isBlank()) { + return null; + } + + // Dostawca bez wlasnych pokoi: obowiazuje link wklejony przez organizatora. + if (!forWebinar(webinar).supportsRooms()) { + String joinUrl = webinar.getJoinUrl(); + return joinUrl == null || joinUrl.isBlank() ? null : joinUrl; + } + + String publicUrl = settings.get(SettingKey.APP_PUBLIC_URL); + if (publicUrl == null || publicUrl.isBlank()) { + return null; + } + while (publicUrl.endsWith("/")) { + publicUrl = publicUrl.substring(0, publicUrl.length() - 1); + } + // sciezka musi odpowiadac trasie frontendu /spotkanie/:token + return publicUrl + "/spotkanie/" + registrationToken; + } + /** Nazwy wszystkich dostepnych dostawcow, do listy wyboru w panelu. */ public List availableNames() { return providers.keySet().stream().sorted().toList(); diff --git a/backend/src/main/resources/templates/mail/registration-confirmation.html b/backend/src/main/resources/templates/mail/registration-confirmation.html index d85634e..cbe5337 100644 --- a/backend/src/main/resources/templates/mail/registration-confirmation.html +++ b/backend/src/main/resources/templates/mail/registration-confirmation.html @@ -65,12 +65,40 @@ + + + + + +
+ + Dołącz do spotkania + +
+ + +

+ Spotkanie otworzy się na krótko przed rozpoczęciem. Jeśli otworzysz + link wcześniej, zobaczysz odliczanie do startu. +

+ - diff --git a/backend/src/main/resources/templates/mail/registration-confirmation.txt b/backend/src/main/resources/templates/mail/registration-confirmation.txt index d6fa7cb..87405f2 100644 --- a/backend/src/main/resources/templates/mail/registration-confirmation.txt +++ b/backend/src/main/resources/templates/mail/registration-confirmation.txt @@ -10,7 +10,13 @@ potwierdzamy Twój zapis na webinar: [(${webinarTitle})] Termin: [(${startsAt})] Strefa czasowa: [(${timezone})] -Strona webinaru: +[# th:if="${joinUrl}"]Link do spotkania: +[(${joinUrl})] + +Spotkanie otworzy się na krótko przed rozpoczęciem. Jeśli otworzysz +link wcześniej, zobaczysz odliczanie do startu. + +[/]Strona webinaru: [(${webinarUrl})] Do wiadomości dołączyliśmy plik invite.ics. Otwórz go, aby dodać diff --git a/backend/src/test/java/com/softspm/webinar/mail/MailAttachmentTest.java b/backend/src/test/java/com/softspm/webinar/mail/MailAttachmentTest.java index 10ac00c..822f8b4 100644 --- a/backend/src/test/java/com/softspm/webinar/mail/MailAttachmentTest.java +++ b/backend/src/test/java/com/softspm/webinar/mail/MailAttachmentTest.java @@ -55,7 +55,9 @@ class MailAttachmentTest { // SafeDelivery z tym samym mockiem ustawien: test sklada wiadomosc, // a nie wysyla jej, wiec straznik nie zmienia tu niczego mailService = new MailService(settings, engine, new IcsGenerator(settings), - new ReminderTextBuilder(settings), new SafeDelivery(settings)); + new ReminderTextBuilder(settings), new SafeDelivery(settings), + new com.softspm.webinar.video.VideoProviderFactory( + java.util.List.of(new com.softspm.webinar.video.NoneVideoProvider()), settings)); } private ClassLoaderTemplateResolver resolver(TemplateMode mode, String pattern) { diff --git a/backend/src/test/java/com/softspm/webinar/mail/MailPreviewTest.java b/backend/src/test/java/com/softspm/webinar/mail/MailPreviewTest.java index 14743c5..2aa0fb4 100644 --- a/backend/src/test/java/com/softspm/webinar/mail/MailPreviewTest.java +++ b/backend/src/test/java/com/softspm/webinar/mail/MailPreviewTest.java @@ -62,7 +62,9 @@ class MailPreviewTest { // SafeDelivery z tym samym mockiem ustawien: test sklada wiadomosc, // a nie wysyla jej, wiec straznik nie zmienia tu niczego mailService = new MailService(settings, engine, new IcsGenerator(settings), - new ReminderTextBuilder(settings), new SafeDelivery(settings)); + new ReminderTextBuilder(settings), new SafeDelivery(settings), + new com.softspm.webinar.video.VideoProviderFactory( + java.util.List.of(new com.softspm.webinar.video.NoneVideoProvider()), settings)); } private ClassLoaderTemplateResolver resolver(TemplateMode mode, String pattern) {
+ + style="display:inline-block; padding:13px 27px; font-size:16px; font-weight:600; color:#1d4ed8; text-decoration:none;"> Otwórz stronę webinaru