Skip to content

Commit

Permalink
♻️ Refactor admin authentication closes #80
Browse files Browse the repository at this point in the history
Signed-off-by: Marcus Fihlon <[email protected]>
  • Loading branch information
McPringle committed Apr 9, 2024
1 parent ddfa2b0 commit 54dc937
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 184 deletions.
145 changes: 2 additions & 143 deletions src/main/java/swiss/fihlon/apus/ui/view/MessageView.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,39 +22,23 @@
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.avatar.Avatar;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.contextmenu.ContextMenu;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Footer;
import com.vaadin.flow.component.html.Header;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.data.value.ValueChangeMode;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.ocpsoft.prettytime.PrettyTime;
import swiss.fihlon.apus.configuration.Configuration;
import swiss.fihlon.apus.service.SocialService;
import swiss.fihlon.apus.social.Message;

@CssImport(value = "./themes/apus/views/message-view.css")
public final class MessageView extends Div {

private static final int MAX_LENGTH = 500;
private static final String TRUNC_INDICATOR = " […]";
private final transient SocialService socialService;
private final transient Configuration configuration;

public MessageView(@NotNull final Message message,
@NotNull final SocialService socialService,
@NotNull final Configuration configuration) {
this.socialService = socialService;
this.configuration = configuration;
public MessageView(@NotNull final Message message) {
setId("message-" + message.id());
addClassName("message-view");
add(createHeaderComponent(message));
Expand All @@ -75,132 +59,7 @@ public MessageView(@NotNull final Message message,
}

private Component createAvatarComponent(@NotNull final Message message) {
final var avatar = new Avatar(message.author(), message.avatar());
if (!configuration.getAdmin().password().isBlank()) {
final var menu = new ContextMenu();
menu.addItem(getTranslation("social.message.contextmenu.hide.message"), event -> confirmHideMessage(message));
menu.addItem(getTranslation("social.message.contextmenu.hide.profile"), event -> confirmHideProfile(message));
menu.setTarget(avatar);
}
return avatar;
}

private void confirmHideMessage(@NotNull final Message message) {
final var dialog = new ConfirmDialog();
dialog.setHeader(getTranslation("social.message.dialog.hide.message.confirm.title"));
dialog.setText(getTranslation("social.message.dialog.hide.message.confirm.text", message.author(), message.date()));
dialog.setCloseOnEsc(true);

dialog.setCancelable(true);
dialog.setCancelButton(getTranslation("social.message.dialog.hide.message.confirm.cancel"), event -> dialog.close());

dialog.setConfirmText(getTranslation("social.message.dialog.hide.message.confirm.button"));
dialog.addConfirmListener(event -> {
dialog.close();
authorizeHideMessage(message);
});

dialog.open();
}

private void authorizeHideMessage(@NotNull final Message message) {
final Dialog dialog = new Dialog();
dialog.setHeaderTitle(getTranslation("social.message.dialog.hide.message.authorize.title"));
dialog.setCloseOnEsc(true);
dialog.setCloseOnOutsideClick(true);

final PasswordField passwordField = new PasswordField();
passwordField.setPlaceholder(getTranslation("social.message.dialog.hide.message.authorize.password"));
passwordField.setRequired(true);
passwordField.setValueChangeMode(ValueChangeMode.EAGER);

final Button hideButton = new Button(getTranslation("social.message.dialog.hide.message.authorize.button"), event -> {
dialog.close();
hideMessage(message, passwordField.getValue());
});
hideButton.setEnabled(false);
hideButton.setDisableOnClick(true);
hideButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

final Button cancelButton = new Button(getTranslation("social.message.dialog.hide.message.authorize.cancel"), event -> dialog.close());
cancelButton.setDisableOnClick(true);
cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
dialog.getFooter().add(hideButton, cancelButton);

passwordField.addKeyDownListener(event -> hideButton.setEnabled(!passwordField.isEmpty()));
dialog.add(passwordField);

dialog.open();
passwordField.focus();
}

private void hideMessage(@NotNull final Message message, @NotNull final String password) {
if (password.equals(configuration.getAdmin().password())) {
socialService.hideMessage(message);
removeFromParent();
Notification.show(getTranslation("social.message.notification.hide.message.success", message.author()));
} else {
Notification.show(getTranslation("social.message.notification.hide.message.rejected"));
}
}

private void confirmHideProfile(@NotNull final Message message) {
final var dialog = new ConfirmDialog();
dialog.setHeader(getTranslation("social.message.dialog.hide.profile.confirm.title"));
dialog.setText(getTranslation("social.message.dialog.hide.profile.confirm.text", message.profile(), message.author()));
dialog.setCloseOnEsc(true);

dialog.setCancelable(true);
dialog.setCancelButton(getTranslation("social.message.dialog.hide.profile.confirm.cancel"), event -> dialog.close());

dialog.setConfirmText(getTranslation("social.message.dialog.hide.profile.confirm.button"));
dialog.addConfirmListener(event -> {
dialog.close();
authorizeHideProfile(message);
});

dialog.open();
}

private void authorizeHideProfile(@NotNull final Message message) {
final Dialog dialog = new Dialog();
dialog.setHeaderTitle(getTranslation("social.message.dialog.hide.profile.authorize.title"));
dialog.setCloseOnEsc(true);
dialog.setCloseOnOutsideClick(true);

final PasswordField passwordField = new PasswordField();
passwordField.setPlaceholder(getTranslation("social.message.dialog.hide.profile.authorize.password"));
passwordField.setRequired(true);
passwordField.setValueChangeMode(ValueChangeMode.EAGER);

final Button hideButton = new Button(getTranslation("social.message.dialog.hide.profile.authorize.button"), event -> {
dialog.close();
hideProfile(message, passwordField.getValue());
});
hideButton.setEnabled(false);
hideButton.setDisableOnClick(true);
hideButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

final Button cancelButton = new Button(getTranslation("social.message.dialog.hide.profile.authorize.cancel"), event -> dialog.close());
cancelButton.setDisableOnClick(true);
cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
dialog.getFooter().add(hideButton, cancelButton);

passwordField.addKeyDownListener(event -> hideButton.setEnabled(!passwordField.isEmpty()));
dialog.add(passwordField);

dialog.open();
passwordField.focus();
}

private void hideProfile(@NotNull final Message message, @NotNull final String password) {
if (password.equals(configuration.getAdmin().password())) {
socialService.hideProfile(message);
removeFromParent();
Notification.show(getTranslation("social.message.notification.hide.profile.success", message.profile(), message.author()));
} else {
Notification.show(getTranslation("social.message.notification.hide.profile.rejected"));
}
return new Avatar(message.author(), message.avatar());
}

@NotNull
Expand Down
89 changes: 88 additions & 1 deletion src/main/java/swiss/fihlon/apus/ui/view/SocialView.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@
*/
package swiss.fihlon.apus.ui.view;

import com.vaadin.flow.component.Key;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.contextmenu.ContextMenu;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.PasswordField;
import com.vaadin.flow.data.value.ValueChangeMode;
import org.jetbrains.annotations.NotNull;
import org.springframework.scheduling.TaskScheduler;
import swiss.fihlon.apus.configuration.Configuration;
Expand All @@ -38,6 +46,8 @@ public final class SocialView extends Div {
private final transient SocialService socialService;
private final transient Configuration configuration;
private final Div messageContainer = new Div();
private final ContextMenu contextMenu;
private boolean adminModeEnabled = false;

public SocialView(@NotNull final SocialService socialService,
@NotNull final TaskScheduler taskScheduler,
Expand All @@ -49,19 +59,96 @@ public SocialView(@NotNull final SocialService socialService,
add(new H2(getTranslation("social.heading", configuration.getMastodon().hashtag())));
add(messageContainer);
messageContainer.addClassName("masonry");

if (adminModeEnabled || configuration.getAdmin().password().isBlank()) {
contextMenu = null;
} else {
contextMenu = new ContextMenu();
contextMenu.addItem(getTranslation("social.admin.login.menu"), event -> showLoginDialog());
contextMenu.setTarget(messageContainer);
}

final ScheduledFuture<?> updateScheduler = taskScheduler.scheduleAtFixedRate(
this::updateScheduler, Instant.now().plusSeconds(1), UPDATE_FREQUENCY);
addDetachListener(event -> updateScheduler.cancel(true));
}

private void showLoginDialog() {
final Dialog dialog = new Dialog();
dialog.setHeaderTitle(getTranslation("social.admin.login.title"));
dialog.setCloseOnEsc(true);
dialog.setCloseOnOutsideClick(true);

final PasswordField passwordField = new PasswordField();
passwordField.setPlaceholder(getTranslation("social.admin.login.password"));
passwordField.setRequired(true);
passwordField.setValueChangeMode(ValueChangeMode.EAGER);

final Button loginButton = new Button(getTranslation("social.admin.login.button"), event -> {
handleLogin(passwordField.getValue());
dialog.close();
});
loginButton.setEnabled(false);
loginButton.setDisableOnClick(true);
loginButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);

final Button cancelButton = new Button(getTranslation("social.admin.login.cancel"), event -> dialog.close());
cancelButton.setDisableOnClick(true);
cancelButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);

dialog.getFooter().add(loginButton, cancelButton);

passwordField.addKeyDownListener(event -> {
loginButton.setEnabled(!passwordField.isEmpty());
if (event.getKey().equals(Key.ENTER)) {
handleLogin(passwordField.getValue());
dialog.close();
}
});
dialog.add(passwordField);

dialog.open();
passwordField.focus();
}

private void handleLogin(@NotNull final String password) {
if (configuration.getAdmin().password().equals(password)) {
adminModeEnabled = true;
contextMenu.setTarget(null);
updateMessages();
Notification.show(getTranslation("social.admin.login.successful"));
} else {
Notification.show(getTranslation("social.admin.login.rejected"));
}
}

private void updateScheduler() {
getUI().ifPresent(ui -> ui.access(this::updateMessages));
}

private void updateMessages() {
messageContainer.removeAll();
for (final Message message : socialService.getMessages(30)) {
messageContainer.add(new MessageView(message, socialService, configuration));
final var messageView = new MessageView(message);
if (adminModeEnabled) {
final var messageMenu = new ContextMenu();
messageMenu.addItem(getTranslation("social.message.contextmenu.hide.message"), event -> hideMessage(message));
messageMenu.addItem(getTranslation("social.message.contextmenu.hide.profile"), event -> blockProfile(message));
messageMenu.setTarget(messageView);
}
messageContainer.add(messageView);
}
}

private void hideMessage(@NotNull final Message message) {
socialService.hideMessage(message);
Notification.show(getTranslation("social.message.contextmenu.hide.message.done"));
updateMessages();
}

private void blockProfile(@NotNull final Message message) {
socialService.hideProfile(message);
Notification.show(getTranslation("social.message.contextmenu.hide.profile.done"));
updateMessages();
}
}
29 changes: 9 additions & 20 deletions src/main/resources/vaadin-i18n/translations.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,15 @@ conference.legend.running-session=running session
conference.room.empty=closed
conference.session.countdown.plural=ends in {0} minutes
conference.session.countdown.singular=ends in {0} minute
social.admin.login.button=Login
social.admin.login.cancel=Cancel
social.admin.login.menu=Login as Administrator
social.admin.login.password=Password
social.admin.login.rejected=Your authentication was not successful!
social.admin.login.successful=You have administrative rights.
social.admin.login.title=Authentification
social.heading=Posts with #{0} on Mastodon
social.message.contextmenu.hide.message.done=The message was hidden as requested.
social.message.contextmenu.hide.message=Hide this message
social.message.dialog.hide.message.authorize.button=Hide
social.message.dialog.hide.message.authorize.cancel=Cancel
social.message.dialog.hide.message.authorize.password=Password
social.message.dialog.hide.message.authorize.title=Authorize
social.message.dialog.hide.message.confirm.button=Hide
social.message.dialog.hide.message.confirm.cancel=Cancel
social.message.dialog.hide.message.confirm.text=Do you really want to hide this message from {0} posted at {1}?
social.message.dialog.hide.message.confirm.title=Confirm
social.message.notification.hide.message.rejected=You are not authorized to hide messages!
social.message.notification.hide.message.success=This message from {0} was hidden as requested.
social.message.contextmenu.hide.profile.done=All messages from this profile have been hidden.
social.message.contextmenu.hide.profile=Hide this profile
social.message.dialog.hide.profile.authorize.button=Hide
social.message.dialog.hide.profile.authorize.cancel=Cancel
social.message.dialog.hide.profile.authorize.password=Password
social.message.dialog.hide.profile.authorize.title=Authorize
social.message.dialog.hide.profile.confirm.button=Hide
social.message.dialog.hide.profile.confirm.cancel=Cancel
social.message.dialog.hide.profile.confirm.text=Do you really want to hide the profile {0} ({1})?
social.message.dialog.hide.profile.confirm.title=Confirm
social.message.notification.hide.profile.rejected=You are not authorized to hide profiles!
social.message.notification.hide.profile.success=The profile {0} ({1}) was hidden as requested.
29 changes: 9 additions & 20 deletions src/main/resources/vaadin-i18n/translations_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,15 @@ conference.legend.running-session=laufender Vortrag
conference.room.empty=geschlossen
conference.session.countdown.plural=endet in {0} Minuten
conference.session.countdown.singular=endet in {0} Minute
social.admin.login.button=Anmelden
social.admin.login.cancel=Abbrechen
social.admin.login.menu=Als Administrator anmelden
social.admin.login.password=Passwort
social.admin.login.rejected=Du hast dich nicht erfolgreich authentifiziert!
social.admin.login.successful=Du hast jetzt Administrationsrechte.
social.admin.login.title=Authentifizierung
social.heading=Schreibe einen Beitrag mit #{0} auf Mastodon
social.message.contextmenu.hide.message.done=Der Beitrag wurde wie gewünscht ausgeblendet.
social.message.contextmenu.hide.message=Diesen Beitrag ausblenden
social.message.dialog.hide.message.authorize.button=Ausblenden
social.message.dialog.hide.message.authorize.cancel=Abbrechen
social.message.dialog.hide.message.authorize.password=Passwort
social.message.dialog.hide.message.authorize.title=Authorisieren
social.message.dialog.hide.message.confirm.button=Ausblenden
social.message.dialog.hide.message.confirm.cancel=Abbrechen
social.message.dialog.hide.message.confirm.text=Möchtest du diesen am {1} geschriebenen Beitrag von {0} ausblenden?
social.message.dialog.hide.message.confirm.title=Bestätigen
social.message.notification.hide.message.rejected=Du bist nicht authorisiert, Beiträge auszublenden!
social.message.notification.hide.message.success=Dieser Beitrag von {0} wurde wie gewünscht ausgeblendet.
social.message.contextmenu.hide.profile.done=Alle Beiträge dieses Profils wurden wie gewünscht ausgeblendet.
social.message.contextmenu.hide.profile=Dieses Profil ausblenden
social.message.dialog.hide.profile.authorize.button=Ausblenden
social.message.dialog.hide.profile.authorize.cancel=Abbrechen
social.message.dialog.hide.profile.authorize.password=Passwort
social.message.dialog.hide.profile.authorize.title=Authorisieren
social.message.dialog.hide.profile.confirm.button=Ausblenden
social.message.dialog.hide.profile.confirm.cancel=Abbrechen
social.message.dialog.hide.profile.confirm.text=Möchtest du das Profil {1} von {0} ausblenden?
social.message.dialog.hide.profile.confirm.title=Bestätigen
social.message.notification.hide.profile.rejected=Du bist nicht authorisiert, Profile auszublenden!
social.message.notification.hide.profile.success=Das Profile {0} von {1} wurde wie gewünscht ausgeblendet.

0 comments on commit 54dc937

Please sign in to comment.