Skip to content

Commit

Permalink
Merge pull request #18 from samply/feature/email-context
Browse files Browse the repository at this point in the history
Feature/email context
  • Loading branch information
djuarezgf authored Nov 28, 2024
2 parents 4a7890f + cfbd832 commit 5bb89ee
Show file tree
Hide file tree
Showing 27 changed files with 465 additions and 65 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [0.0.1 - 2024-11-27]
## [0.0.1 - 2024-11-28]
### Added
- First version of the project
- Spring Application
Expand Down Expand Up @@ -159,3 +159,5 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Integration of action explanatios in fetch Actions
- Action explanations templates
- Email Context
- Extend email context
- Thymeleaf Dialect for email context variables
13 changes: 9 additions & 4 deletions src/main/java/de/samply/aop/EmailSenderAspect.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
Expand All @@ -41,6 +42,7 @@ public class EmailSenderAspect {
private final OrganisationRoleToProjectRoleMapper organisationRoleToProjectRoleMapper;
private final ProjectBridgeheadRepository projectBridgeheadRepository;
private final ProjectRepository projectRepository;
private final EmailKeyValuesFactory emailKeyValuesFactory;

public EmailSenderAspect(EmailService emailService,
SessionUser sessionUser,
Expand All @@ -49,7 +51,8 @@ public EmailSenderAspect(EmailService emailService,
ProjectManagerAdminUserRepository projectManagerAdminUserRepository,
OrganisationRoleToProjectRoleMapper organisationRoleToProjectRoleMapper,
ProjectBridgeheadRepository projectBridgeheadRepository,
ProjectRepository projectRepository) {
ProjectRepository projectRepository,
EmailKeyValuesFactory emailKeyValuesFactory) {
this.emailService = emailService;
this.sessionUser = sessionUser;
this.bridgeheadAdminUserRepository = bridgeheadAdminUserRepository;
Expand All @@ -58,6 +61,7 @@ public EmailSenderAspect(EmailService emailService,
this.organisationRoleToProjectRoleMapper = organisationRoleToProjectRoleMapper;
this.projectBridgeheadRepository = projectBridgeheadRepository;
this.projectRepository = projectRepository;
this.emailKeyValuesFactory = emailKeyValuesFactory;
}

@Pointcut("@annotation(de.samply.annotations.EmailSender)")
Expand Down Expand Up @@ -148,10 +152,11 @@ private void sendEmailFromEmailSenderIfError(ProceedingJoinPoint joinPoint, Opti
.forEach(emailRecipient -> sendEmail(emailRecipient, () -> emailSenderIfError.templateType())));
}

private void sendEmail(EmailRecipient emailRecipient, Supplier<EmailTemplateType> emailTemplateTypeSupplier) {
@Async(ProjectManagerConst.ASYNC_EMAIL_SENDER_EXECUTOR)
protected void sendEmail(EmailRecipient emailRecipient, Supplier<EmailTemplateType> emailTemplateTypeSupplier) {
try {
Map<String, String> keyValues = (emailRecipient.getMessage().isPresent()) ? Map.of(ProjectManagerConst.EMAIL_CONTEXT_MESSAGE, emailRecipient.getMessage().get()) : new HashMap<>();
emailService.sendEmail(emailRecipient.getEmail(), emailRecipient.getProjectCode(), emailRecipient.getBridgehead(), emailRecipient.getRole(), emailTemplateTypeSupplier.get(), keyValues);
emailService.sendEmail(emailRecipient.getEmail(), emailRecipient.getProjectCode(), emailRecipient.getBridgehead(),
emailRecipient.getRole(), emailTemplateTypeSupplier.get(), emailKeyValuesFactory.newInstance().add(emailRecipient));
} catch (EmailServiceException e) {
throw new RuntimeException(e);
}
Expand Down
60 changes: 54 additions & 6 deletions src/main/java/de/samply/app/ProjectManagerConst.java
Original file line number Diff line number Diff line change
Expand Up @@ -223,21 +223,54 @@ public class ProjectManagerConst {
public final static String REDIRECT_EXPLORER_URL = "explorer-url";
public final static String QUERY_CONTEXT = "query-context";

// Email context properties
// Variables for Email Templates:
public final static String EMAIL_CONTEXT_BRIDGEHEAD = "bridgehead";
public final static String EMAIL_CONTEXT_PROJECT_CODE = "projectCode";
public final static String EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_EMAIL = "projectBridgeheadUserEmail";
public final static String EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_FIRST_NAME = "projectBridgeheadUserFirstName";
public final static String EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_LAST_NAME = "projectBridgeheadUserLastName";
public final static String EMAIL_CONTEXT_PROJECT_ROLE = "projectRole";
public final static String EMAIL_CONTEXT_PROJECT_TYPE = "projectType";
public final static String EMAIL_CONTEXT_PROJECT_CREATOR_EMAIL = "projectCreatorEmail";
public final static String EMAIL_CONTEXT_PROJECT_CREATOR_FIRST_NAME = "projectCreatorFirstName";
public final static String EMAIL_CONTEXT_PROJECT_CREATOR_LAST_NAME = "projectCreatorLastName";
public final static String EMAIL_CONTEXT_QUERY = "query";
public final static String EMAIL_CONTEXT_QUERY_LABEL = "queryLabel";
public final static String EMAIL_CONTEXT_QUERY_DESCRIPTION = "queryDescription";
public final static String EMAIL_CONTEXT_PROJECT_VIEW_URL = "projectViewUrl";
public final static String EMAIL_CONTEXT_MESSAGE = "message";

public final static String EMAIL_CONTEXT_EMAIL_TO = "emailTo";
public final static String EMAIL_CONTEXT_EMAIL_TO_FIRST_NAME = "emailToFirstName";
public final static String EMAIL_CONTEXT_EMAIL_TO_LAST_NAME = "emailToLastName";

public final static String[] EMAIL_CONTEXT_VARIABLES = {
EMAIL_CONTEXT_BRIDGEHEAD,
EMAIL_CONTEXT_PROJECT_CODE,
EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_EMAIL,
EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_FIRST_NAME,
EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_LAST_NAME,
EMAIL_CONTEXT_PROJECT_ROLE,
EMAIL_CONTEXT_PROJECT_TYPE,
EMAIL_CONTEXT_PROJECT_CREATOR_EMAIL,
EMAIL_CONTEXT_PROJECT_CREATOR_FIRST_NAME,
EMAIL_CONTEXT_PROJECT_CREATOR_LAST_NAME,
EMAIL_CONTEXT_QUERY,
EMAIL_CONTEXT_QUERY_LABEL,
EMAIL_CONTEXT_QUERY_DESCRIPTION,
EMAIL_CONTEXT_PROJECT_VIEW_URL,
EMAIL_CONTEXT_MESSAGE,
EMAIL_CONTEXT_EMAIL_TO,
EMAIL_CONTEXT_EMAIL_TO_FIRST_NAME,
EMAIL_CONTEXT_EMAIL_TO_LAST_NAME
};

// Application Properties
public final static String JWT_GROUPS_CLAIM_PROPERTY = "jwt.groups.claim";
public final static String JWKS_URI_PROPERTY = "spring.security.oauth2.client.provider.oidc.jwk-set-uri";
public final static String REGISTERED_BRIDGEHEADS = "bridgeheads";
public final static String FRONTEND_CONFIG = "frontend";
public final static String HTTP_PROXY_PREFIX = "http.proxy";
public final static String HTTPS_PROXY_PREFIX = "https.proxy";
public final static String EMAIL_CONTEXT = "email";
public final static String EMAIL_CONTEXT_PREFIX = "email";

// Exporter
public final static String SECURITY_ENABLED = "SECURITY_ENABLED";
Expand Down Expand Up @@ -350,6 +383,11 @@ public class ProjectManagerConst {
public final static String MAX_TIME_TO_WAIT_FOCUS_TASK_IN_MINUTES = "MAX_TIME_TO_WAIT_FOCUS_TASK_IN_MINUTES";
public final static String DEFAULT_LANGUAGE = "DEFAULT_LANGUAGE";

public final static String JWT_GROUPS_CLAIM = "JWT_GROUPS_CLAIM";
public final static String JWT_EMAIL_CLAIM = "JWT_EMAIL_CLAIM";
public final static String JWT_FIRST_NAME_CLAIM = "JWT_FIRST_NAME_CLAIM";
public final static String JWT_LAST_NAME_CLAIM = "JWT_LAST_NAME_CLAIM";

public final static String CODER_BASE_URL = "CODER_BASE_URL";
public final static String CODER_ORGANISATION_ID = "CODER_ORGANISATION_ID";
public final static String CODER_MEMBER_ID = "CODER_MEMBER_ID";
Expand Down Expand Up @@ -385,7 +423,10 @@ public class ProjectManagerConst {
public final static String HEAD_SV = "${";
public final static String BOTTOM_SV = "}";
public final static String PM_ADMIN_GROUPS_SV = HEAD_SV + PM_ADMIN_GROUPS + BOTTOM_SV;
public final static String JWT_GROUPS_CLAIM_PROPERTY_SV = HEAD_SV + JWT_GROUPS_CLAIM_PROPERTY + BOTTOM_SV;
public final static String JWT_GROUPS_CLAIM_SV = HEAD_SV + JWT_GROUPS_CLAIM + ":groups" + BOTTOM_SV;
public final static String JWT_EMAIL_CLAIM_SV = HEAD_SV + JWT_EMAIL_CLAIM + ":email" + BOTTOM_SV;
public final static String JWT_FIRST_NAME_CLAIM_SV = HEAD_SV + JWT_FIRST_NAME_CLAIM + ":given_name" + BOTTOM_SV;
public final static String JWT_LAST_NAME_CLAIM_SV = HEAD_SV + JWT_LAST_NAME_CLAIM + ":family_name" + BOTTOM_SV;
public final static String JWKS_URI_PROPERTY_SV = HEAD_SV + JWKS_URI_PROPERTY + BOTTOM_SV;
public final static String BK_USER_GROUP_PREFIX_SV = HEAD_SV + BK_USER_GROUP_PREFIX + BOTTOM_SV;
public final static String BK_USER_GROUP_SUFFIX_SV = HEAD_SV + BK_USER_GROUP_SUFFIX + BOTTOM_SV;
Expand Down Expand Up @@ -487,6 +528,13 @@ public class ProjectManagerConst {
public final static String ASYNC_NOTIFICATION_EXECUTOR = "notification";
public final static String ASYNC_EXPORTER_EXECUTOR = "exporter";

// Thymeleaf
public final static int THYMELEAF_PROCESSOR_PRECEDENCE = 1000;
public final static int THYMELEAF_DIALECT_PRECEDENCE = 1000;
public final static String THYMELEAF_DIALECT_NAME = "Project Manager";
public final static String THYMELEAF_DIALECT_PREFIX = "pm";


// Others
public final static String TEST_EMAIL = "[email protected]";
public final static String TEST_BRIDGEHEAD = "bridgehead-test";
Expand All @@ -495,12 +543,12 @@ public class ProjectManagerConst {
public final static int QUERY_CODE_SIZE = 20;
public final static String NO_BRIDGEHEAD = "NONE";
public final static String THIS_IS_A_TEST = "This is a test";
public final static String OIDC_EMAIL_CLAIM = "email";
public final static String CUSTOM_PROJECT_CONFIGURATION = "CUSTOM";
public final static String EMAIL_SERVICE = "EMAIL_SERVICE";
public final static String HYPHEN = "minus";
public final static String HTTP_PROTOCOL_SCHEMA = "http";
public final static String HTTPS_PROTOCOL_SCHEMA = "https";



}
29 changes: 29 additions & 0 deletions src/main/java/de/samply/db/model/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package de.samply.db.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "user", schema = "samply")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;

@Column(name = "email", nullable = false, unique = true)
private String email;

@Column(name = "first_name", nullable = false)
private String firstName;

@Column(name = "last_name", nullable = false)
private String lastName;

}
14 changes: 14 additions & 0 deletions src/main/java/de/samply/db/repository/UserRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package de.samply.db.repository;

import de.samply.db.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long>
{
Optional<User> findByEmail(String email);

}
2 changes: 1 addition & 1 deletion src/main/java/de/samply/email/EmailContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@Slf4j
@Component
@ConfigurationProperties(prefix = ProjectManagerConst.EMAIL_CONTEXT)
@ConfigurationProperties(prefix = ProjectManagerConst.EMAIL_CONTEXT_PREFIX)
@Data
public class EmailContext {

Expand Down
163 changes: 163 additions & 0 deletions src/main/java/de/samply/email/EmailKeyValues.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package de.samply.email;

import de.samply.app.ProjectManagerConst;
import de.samply.db.model.Project;
import de.samply.db.model.ProjectBridgehead;
import de.samply.db.model.ProjectBridgeheadUser;
import de.samply.db.model.Query;
import de.samply.db.repository.ProjectBridgeheadRepository;
import de.samply.db.repository.ProjectRepository;
import de.samply.db.repository.UserRepository;
import de.samply.frontend.FrontendService;
import de.samply.user.roles.ProjectRole;
import jakarta.validation.constraints.NotNull;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

public class EmailKeyValues {

private Map<String, String> keyValues = new HashMap<>();
private final FrontendService frontendService;
private final ProjectBridgeheadRepository projectBridgeheadRepository;
private final ProjectRepository projectRepository;
private final UserRepository userRepository;


public EmailKeyValues(FrontendService frontendService,
EmailContext emailContext,
ProjectBridgeheadRepository projectBridgeheadRepository,
ProjectRepository projectRepository,
UserRepository userRepository) {
this.frontendService = frontendService;
this.projectBridgeheadRepository = projectBridgeheadRepository;
this.projectRepository = projectRepository;
this.userRepository = userRepository;
keyValues.putAll(emailContext.getContext());
}

public EmailKeyValues add(EmailRecipient emailRecipient) {
if (emailRecipient != null) {
addEmailData(emailRecipient.getEmail(), ProjectManagerConst.EMAIL_CONTEXT_EMAIL_TO,
ProjectManagerConst.EMAIL_CONTEXT_EMAIL_TO_FIRST_NAME, ProjectManagerConst.EMAIL_CONTEXT_EMAIL_TO_LAST_NAME);
emailRecipient.getMessage().ifPresent(this::addMessage);
add(emailRecipient.getRole());
addProjectBridgeheadOrProject(emailRecipient);
}
return this;
}

private void addProjectBridgeheadOrProject(EmailRecipient emailRecipient) {
AtomicReference<Optional<ProjectBridgehead>> projectBridgeheadOptional = new AtomicReference<>(Optional.empty());
AtomicReference<Optional<Project>> projectOptional = new AtomicReference<>(Optional.empty());
emailRecipient.getProjectCode().ifPresent(projectCode ->
projectRepository.findByCode(projectCode).ifPresent(project ->
emailRecipient.getBridgehead().ifPresent(bridgehead ->
projectBridgeheadRepository.findFirstByBridgeheadAndProject(bridgehead, project).ifPresentOrElse(projectBridgehead ->
projectBridgeheadOptional.set(Optional.of(projectBridgehead)),
() -> projectOptional.set(Optional.of(project))))));
if (projectBridgeheadOptional.get().isPresent()) {
add(projectBridgeheadOptional.get().get());
} else if (projectOptional.get().isPresent()) {
add(projectOptional.get().get());
} else {
emailRecipient.getProjectCode().ifPresent(this::addProjectCode);
emailRecipient.getBridgehead().ifPresent(this::addBridgehead);
}
}

public EmailKeyValues add(ProjectBridgeheadUser projectBridgeheadUser) {
if (projectBridgeheadUser != null) {
addEmailData(projectBridgeheadUser.getEmail(), ProjectManagerConst.EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_EMAIL,
ProjectManagerConst.EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_FIRST_NAME, ProjectManagerConst.EMAIL_CONTEXT_PROJECT_BRIDGEHEAD_USER_LAST_NAME);
add(projectBridgeheadUser.getProjectRole());
add(projectBridgeheadUser.getProjectBridgehead());
}
return this;
}

public EmailKeyValues add(ProjectRole projectRole) {
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_PROJECT_ROLE, projectRole.toString());
return this;
}

public EmailKeyValues addMessage(String message) {
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_MESSAGE, message);
return this;
}

public EmailKeyValues addProjectCode(String projectCode) {
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_PROJECT_CODE, projectCode);
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_PROJECT_VIEW_URL,
this.frontendService.fetchUrl(ProjectManagerConst.PROJECT_VIEW_SITE,
Map.of(ProjectManagerConst.PROJECT_CODE, projectCode)));
return this;
}

public EmailKeyValues add(ProjectBridgehead projectBridgehead) {
if (projectBridgehead != null) {
addBridgehead(projectBridgehead.getBridgehead());
add(projectBridgehead.getProject());
}
return this;
}

public EmailKeyValues addBridgehead(String bridgehead) {
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_BRIDGEHEAD, bridgehead);
return this;
}


public EmailKeyValues add(Project project) {
if (project != null) {
addProjectCode(project.getCode());
addEmailData(project.getCreatorEmail(), ProjectManagerConst.EMAIL_CONTEXT_PROJECT_CREATOR_EMAIL,
ProjectManagerConst.EMAIL_CONTEXT_PROJECT_CREATOR_FIRST_NAME, ProjectManagerConst.EMAIL_CONTEXT_PROJECT_CREATOR_LAST_NAME);
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_QUERY,
(project.getQuery().getHumanReadable()) != null ?
project.getQuery().getHumanReadable() : project.getQuery().getQuery());
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_PROJECT_TYPE, () -> project.getType().toString());
add(project.getQuery());
}
return this;
}

public EmailKeyValues add(Query query) {
if (query != null) {
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_QUERY,
(query.getHumanReadable()) != null ? query.getHumanReadable() : query.getQuery());
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_QUERY_LABEL, query::getLabel);
addKeyValue(ProjectManagerConst.EMAIL_CONTEXT_QUERY_DESCRIPTION, query::getDescription);
}
return this;
}

private void addKeyValue(@NotNull String key, Supplier<String> valueGetter) {
String value = valueGetter.get();
if (value != null) {
keyValues.put(key, value);
}
}

public void addKeyValue(@NotNull String key, @NotNull String value) {
keyValues.put(key, value);
}

public Map<String, String> getKeyValues() {
return keyValues;
}

private void addEmailData(String email, @NotNull String emailKey, @NotNull String emailFirstNameKey, @NotNull String emailLastNameKey) {
if (email != null) {
addKeyValue(emailKey, email);
userRepository.findByEmail(email).ifPresent(user -> {
addKeyValue(emailFirstNameKey, user::getFirstName);
addKeyValue(emailLastNameKey, user::getLastName);
});
}
}

}
Loading

0 comments on commit 5bb89ee

Please sign in to comment.