• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DataBiosphere / consent / #6225

28 Jul 2025 12:14PM UTC coverage: 81.088% (+0.4%) from 80.694%
#6225

push

web-flow
DT-2026: Bring feature branch into develop (#2621)

Co-authored-by: Elliot Otchet <otchet@broadinstitute.org>
Co-authored-by: Rachel Johanek <55256690+rjohanek@users.noreply.github.com>
Co-authored-by: rjohanek <rjohanek@broadinstitute.org>
Co-authored-by: Matt Bemis <MatthewBemis@users.noreply.github.com>
Co-authored-by: otchet-broad <111771148+otchet-broad@users.noreply.github.com>

397 of 437 new or added lines in 34 files covered. (90.85%)

10775 of 13288 relevant lines covered (81.09%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.64
/src/main/java/org/broadinstitute/consent/http/service/EmailService.java
1
package org.broadinstitute.consent.http.service;
2

3
import com.google.common.annotations.VisibleForTesting;
4
import com.google.inject.Inject;
5
import com.sendgrid.Response;
6
import com.sendgrid.helpers.mail.Mail;
7
import com.sendgrid.helpers.mail.objects.Content;
8
import com.sendgrid.helpers.mail.objects.Email;
9
import freemarker.template.Template;
10
import freemarker.template.TemplateException;
11
import java.io.IOException;
12
import java.io.StringWriter;
13
import java.io.Writer;
14
import java.time.Instant;
15
import java.util.Date;
16
import java.util.List;
17
import java.util.Map;
18
import java.util.Objects;
19
import javax.annotation.Nullable;
20
import org.broadinstitute.consent.http.configurations.ConsentConfiguration;
21
import org.broadinstitute.consent.http.db.MailMessageDAO;
22
import org.broadinstitute.consent.http.db.UserDAO;
23
import org.broadinstitute.consent.http.enumeration.EmailType;
24
import org.broadinstitute.consent.http.mail.SendGridAPI;
25
import org.broadinstitute.consent.http.mail.freemarker.FreeMarkerTemplateHelper;
26
import org.broadinstitute.consent.http.mail.message.DACAutomationApprovalResearcherMessage;
27
import org.broadinstitute.consent.http.mail.message.DaaRequestMessage;
28
import org.broadinstitute.consent.http.mail.message.DarExpirationReminderMessage;
29
import org.broadinstitute.consent.http.mail.message.DarExpiredMessage;
30
import org.broadinstitute.consent.http.mail.message.DataCustodianApprovalMessage;
31
import org.broadinstitute.consent.http.mail.message.DatasetApprovedMessage;
32
import org.broadinstitute.consent.http.mail.message.DatasetDeniedMessage;
33
import org.broadinstitute.consent.http.mail.message.DatasetSubmittedMessage;
34
import org.broadinstitute.consent.http.mail.message.MailMessage;
35
import org.broadinstitute.consent.http.mail.message.NewCaseMessage;
36
import org.broadinstitute.consent.http.mail.message.NewDAAUploadResearcherMessage;
37
import org.broadinstitute.consent.http.mail.message.NewDAAUploadSOMessage;
38
import org.broadinstitute.consent.http.mail.message.NewDARRequestMessage;
39
import org.broadinstitute.consent.http.mail.message.NewLibraryCardIssuedMessage;
40
import org.broadinstitute.consent.http.mail.message.NewProgressReportCaseMessage;
41
import org.broadinstitute.consent.http.mail.message.NewProgressReportRequestMessage;
42
import org.broadinstitute.consent.http.mail.message.NewResearcherLibraryRequestMessage;
43
import org.broadinstitute.consent.http.mail.message.ReminderMessage;
44
import org.broadinstitute.consent.http.mail.message.ResearcherApprovedProgressReportMessage;
45
import org.broadinstitute.consent.http.mail.message.ResearcherCloseoutCompletedMessage;
46
import org.broadinstitute.consent.http.mail.message.ResearcherDarApprovedMessage;
47
import org.broadinstitute.consent.http.mail.message.SoDARApproved;
48
import org.broadinstitute.consent.http.mail.message.SoDARSubmitted;
49
import org.broadinstitute.consent.http.mail.message.SoPRApproved;
50
import org.broadinstitute.consent.http.mail.message.SoPRSubmitted;
51
import org.broadinstitute.consent.http.mail.message.SubmittedCloseoutMessage;
52
import org.broadinstitute.consent.http.models.DarCollection;
53
import org.broadinstitute.consent.http.models.Dataset;
54
import org.broadinstitute.consent.http.models.User;
55
import org.broadinstitute.consent.http.models.Vote;
56
import org.broadinstitute.consent.http.models.dto.DatasetMailDTO;
57
import org.broadinstitute.consent.http.util.ConsentLogger;
58

59
public class EmailService implements ConsentLogger {
60

61
  private final UserDAO userDAO;
62
  private final MailMessageDAO emailDAO;
63
  private final FreeMarkerTemplateHelper templateHelper;
64
  private final SendGridAPI sendGridAPI;
65
  private final String fromAccount;
66
  private final String serverUrl;
67

68
  @Inject
69
  public EmailService(
70
      UserDAO userDAO,
71
      MailMessageDAO emailDAO,
72
      SendGridAPI sendGridAPI,
73
      FreeMarkerTemplateHelper helper,
74
      ConsentConfiguration config) {
1✔
75
    this.userDAO = userDAO;
1✔
76
    this.templateHelper = helper;
1✔
77
    this.emailDAO = emailDAO;
1✔
78
    this.sendGridAPI = sendGridAPI;
1✔
79
    this.serverUrl = config.getServicesConfiguration().getLocalURL();
1✔
80
    this.fromAccount = config.getMailConfiguration().getGoogleAccount();
1✔
81
  }
1✔
82

83
  /**
84
   * This method saves an email (either sent or unsent) with all available metadata from the
85
   * SendGrid response.
86
   */
87
  private void saveEmailAndResponse(
88
      @Nullable Response response,
89
      @Nullable String entityReferenceId,
90
      @Nullable Integer voteId,
91
      Integer userId,
92
      EmailType emailType,
93
      String content) {
94
    Instant now = Instant.now();
1✔
95
    Instant dateSent = (Objects.nonNull(response) && response.getStatusCode() < 400) ? now : null;
1✔
96
    emailDAO.insert(
1✔
97
        entityReferenceId,
98
        voteId,
99
        userId,
100
        emailType.getTypeInt(),
1✔
101
        dateSent,
102
        content,
103
        Objects.nonNull(response) ? response.getBody() : null,
1✔
104
        Objects.nonNull(response) ? response.getStatusCode() : null,
1✔
105
        now);
106
  }
1✔
107

108
  @VisibleForTesting
109
  protected void sendMessage(MailMessage mailMessage, Integer userId)
110
      throws IOException, TemplateException {
111
    Writer out = new StringWriter();
1✔
112
    Template template = templateHelper.getTemplate(mailMessage.getTemplateName());
1✔
113
    template.process(mailMessage.createModel(serverUrl), out);
1✔
114
    String content = out.toString();
1✔
115
    Mail message = new Mail(new Email(fromAccount), mailMessage.createSubject(),
1✔
116
        new Email(mailMessage.toUser.getEmail()), new Content("text/html", content));
1✔
117
    Response response = sendGridAPI.sendMessage(message, mailMessage.toUser.getEmail());
1✔
118
    saveEmailAndResponse(
1✔
119
        response,
120
        mailMessage.getEntityReferenceId(),
1✔
121
        mailMessage.getVoteId(),
1✔
122
        userId,
123
        mailMessage.emailType,
124
        content);
125
  }
1✔
126

127
  public List<org.broadinstitute.consent.http.models.mail.MailMessage> fetchEmailMessagesByType(
128
      EmailType emailType, Integer limit,
129
      Integer offset) {
130
    return emailDAO.fetchMessagesByType(emailType.getTypeInt(), limit, offset);
1✔
131
  }
132

133
  public List<org.broadinstitute.consent.http.models.mail.MailMessage> fetchEmailMessagesByCreateDate(
134
      Date start, Date end, Integer limit,
135
      Integer offset) {
136
    return emailDAO.fetchMessagesByCreateDate(start, end, limit, offset);
1✔
137
  }
138

139

140
  public void sendResearcherDarApproved(
141
      String darCode,
142
      Integer researcherId,
143
      List<DatasetMailDTO> datasets,
144
      String dataUseRestriction)
145
      throws TemplateException, IOException {
146
    User user = userDAO.findUserById(researcherId);
×
147
    sendMessage(
×
148
        new ResearcherDarApprovedMessage(user, darCode, datasets, dataUseRestriction), researcherId);
149
  }
×
150

151
  public void sendResearcherProgressReportApproved(
152
      String darCode,
153
      Integer researcherId,
154
      List<DatasetMailDTO> datasets,
155
      String dataUseRestriction)
156
      throws TemplateException, IOException {
157
    User user = userDAO.findUserById(researcherId);
×
158
    sendMessage(
×
159
        new ResearcherApprovedProgressReportMessage(user, darCode, datasets, dataUseRestriction), researcherId);
160
  }
×
161

162
  public void sendDataCustodianApprovalMessage(
163
      User custodian,
164
      String darCode,
165
      List<DatasetMailDTO> datasets,
166
      String dataDepositorName,
167
      String researcherEmail)
168
      throws TemplateException, IOException {
169
    sendMessage(
×
170
        new DataCustodianApprovalMessage(
171
            custodian, darCode, datasets, dataDepositorName, researcherEmail),
172
        custodian.getUserId());
×
173
  }
×
174

175
  public void sendDatasetSubmittedMessage(
176
      User dacChair, User dataSubmitter, String dacName, String datasetName)
177
      throws TemplateException, IOException {
178
    sendMessage(
1✔
179
        new DatasetSubmittedMessage(dacChair, dataSubmitter.getDisplayName(), datasetName, dacName),
1✔
180
        dacChair.getUserId());
1✔
181
  }
1✔
182

183
  public void sendDatasetApprovedMessage(User user, String dacName, String datasetName)
184
      throws TemplateException, IOException {
185
    sendMessage(new DatasetApprovedMessage(user, dacName, datasetName), user.getUserId());
×
186
  }
×
187

188
  public void sendDatasetDeniedMessage(
189
      User user, String dacName, String datasetName, String dacEmail)
190
      throws TemplateException, IOException {
191
    sendMessage(new DatasetDeniedMessage(user, dacName, datasetName, dacEmail), user.getUserId());
×
192
  }
×
193

194
  public void sendNewResearcherMessage(User researcher, User signingOfficial)
195
      throws TemplateException, IOException {
196
    sendMessage(
1✔
197
        new NewResearcherLibraryRequestMessage(signingOfficial, researcher),
198
        researcher.getUserId());
1✔
199
  }
1✔
200

201
  public void sendDaaRequestMessage(
202
      User signingOfficial, User requestUser, String daaName, Integer daaId)
203
      throws TemplateException, IOException {
204
    sendMessage(
1✔
205
        new DaaRequestMessage(signingOfficial, requestUser, daaName, daaId),
206
        requestUser.getUserId());
1✔
207
  }
1✔
208

209
  public void sendNewDAAUploadSOMessage(
210
      User signingOfficial,
211
      String dacName,
212
      String previousDaaName,
213
      String newDaaName,
214
      Integer userId)
215
      throws TemplateException, IOException {
216
    sendMessage(
1✔
217
        new NewDAAUploadSOMessage(signingOfficial, dacName, previousDaaName, newDaaName), userId);
218
  }
1✔
219

220
  public void sendNewDAAUploadResearcherMessage(
221
      User researcher, String dacName, String previousDaaName, String newDaaName, Integer userId)
222
      throws TemplateException, IOException {
223
    sendMessage(
1✔
224
        new NewDAAUploadResearcherMessage(
225
            researcher, dacName, previousDaaName, newDaaName),
226
        userId);
227
  }
1✔
228

229
  public void sendDarNewCollectionElectionMessage(List<User> users, String darCode)
230
      throws IOException, TemplateException {
231
    String electionType = "Data Access Request";
×
232
    for (User user : users) {
×
233
      sendMessage(new NewCaseMessage(user, darCode, electionType), user.getUserId());
×
234
    }
×
235
  }
×
236

237
  public void sendProgressReportNewCollectionElectionMessage(List<User> users, String darCode)
238
      throws IOException, TemplateException {
239
    for (User user : users) {
×
240
      sendMessage(new NewProgressReportCaseMessage(user, darCode), user.getUserId());
×
241
    }
×
242
  }
×
243

244
  public void sendNewDARRequestEmail(
245
      User user, Map<String, List<String>> sendList, String researcherName, String darCode)
246
      throws TemplateException, IOException {
247
        sendMessage(new NewDARRequestMessage(user, darCode, sendList, researcherName),
×
248
        user.getUserId());
×
249
  }
×
250

251
  public void sendNewProgressReportRequestEmail(
252
      User user, Map<String, List<String>> sendList, String researcherName, String darCode, String referenceId)
253
      throws TemplateException, IOException {
254
      sendMessage(new NewProgressReportRequestMessage(user, darCode, referenceId, sendList, researcherName),
×
255
        user.getUserId());
×
256
  }
×
257

258
  /**
259
   * Send a message to a Signing Official that a new Data Access Request has been submitted.
260
   *
261
   * @param user The user to send the message to
262
   * @param darCode The Data Access Request code which is submitted
263
   * @param researcher The researcher whose DAR has been submitted
264
   * @param referenceId The reference ID of the DAR
265
   * @param datasets The datasets associated with the DAR
266
   * @throws TemplateException Template processing exception
267
   * @throws IOException IOException when processing the template or sending the email
268
   */
269
  public void sendNewSoDARSubmittedEmail(User user, String darCode, User researcher, String referenceId, List<Dataset> datasets)
270
      throws TemplateException, IOException {
271
        sendMessage(new SoDARSubmitted(user, darCode, researcher, referenceId, datasets),
×
272
        user.getUserId());
×
273
  }
×
274

275
  /**
276
   * Send a message to a Signing Official that a new progress report has been submitted.
277
   *
278
   * @param user The user to send the message to
279
   * @param darCode The Data Access Request code for which the progress report is submitted
280
   * @param researcher The researcher whose progress report has been submitted
281
   * @param referenceId The reference ID of the progress report
282
   * @param datasets The datasets associated with the progress report
283
   * @throws TemplateException Template processing exception
284
   * @throws IOException IOException when processing the template or sending the email
285
   */
286
  public void sendNewSoProgressReportSubmittedEmail(User user, String darCode, User researcher, String referenceId, List<Dataset> datasets)
287
      throws TemplateException, IOException {
288
      sendMessage(new SoPRSubmitted(user, darCode, researcher, referenceId, datasets),
×
289
        user.getUserId());
×
290
  }
×
291

292
  /**
293
   * Send a message to a Signing Official that a new Data Access Request has been approved.
294
   *
295
   * @param user The user to send the message to
296
   * @param darCode The Data Access Request code which is approved
297
   * @param researcher The researcher whose DAR has been approved
298
   * @param referenceId The reference ID of the DAR
299
   * @param datasets The datasets associated with the DAR
300
   * @param dataUseRestriction The data use restriction associated with the datasets in the DAR
301
   * @throws TemplateException Template processing exception
302
   * @throws IOException IOException when processing the template or sending the email
303
   */
304
  public void sendNewSoDARApprovedEmail(User user, String darCode, User researcher, String referenceId, List<Dataset> datasets, String dataUseRestriction)
305
      throws TemplateException, IOException {
306
        sendMessage(new SoDARApproved(user, darCode, researcher, referenceId, datasets, dataUseRestriction),
×
307
        user.getUserId());
×
308
  }
×
309

310
  /**
311
   * Send a message to a Signing Official that a new progress report has been approved.
312
   *
313
   * @param user The user to send the message to
314
   * @param darCode The Data Access Request code for which the progress report is approved
315
   * @param researcher The researcher whose progress report has been approved
316
   * @param referenceId The reference ID of the progress report
317
   * @param datasets The datasets associated with the progress report
318
   * @param dataUseRestriction The data use restriction associated with the datasets in the progress report
319
   * @throws TemplateException Template processing exception
320
   * @throws IOException IOException when processing the template or sending the email
321
   */
322
  public void sendNewSoProgressReportApprovedEmail(User user, String darCode, User researcher, String referenceId, List<Dataset> datasets, String dataUseRestriction)
323
      throws TemplateException, IOException {
324
      sendMessage(new SoPRApproved(user, darCode, researcher, referenceId, datasets, dataUseRestriction),
×
325
        user.getUserId());
×
326
  }
×
327

328
  public void sendDACAutomationApprovalResearcherMessage(
329
      User researcher,
330
      List<DatasetMailDTO> datasets,
331
      String darCode,
332
      String dataUseRestriction
333
    ) throws TemplateException, IOException {
NEW
334
    sendMessage(new DACAutomationApprovalResearcherMessage(researcher, darCode, datasets, dataUseRestriction), researcher.getUserId());
×
NEW
335
  }
×
336
  /**
337
   * Send a message to a researcher that their data access request has expired.
338
   *
339
   * @param researcher  the researcher to send the message to
340
   * @param darCode     the data access request code that's expired
341
   * @param userId      the user id of the person sending the message
342
   * @param referenceId the data access request reference id that's expired
343
   */
344
  public void sendDarExpiredMessage(User researcher, String darCode, Integer userId,
345
      String referenceId)
346
      throws TemplateException, IOException {
347
    sendMessage(new DarExpiredMessage(researcher, darCode, referenceId), userId);
1✔
348
  }
1✔
349

350
  /**
351
   * Remind the user that their data access request is about to expire.
352
   *
353
   * @param user        the user to send the message to
354
   * @param darCode     the data access request code that's about to expire
355
   * @param userId      the user id of the person sending the message
356
   * @param referenceId the data access request reference id that is expiring
357
   */
358
  public void sendDarExpirationReminderMessage(User user, String darCode, Integer userId,
359
      String referenceId)
360
      throws TemplateException, IOException {
361
    sendMessage(new DarExpirationReminderMessage(user, darCode, referenceId), userId);
1✔
362
  }
1✔
363

364
  public void sendReminderMessage(User user, Vote vote, String darCode, String electionType, String url)
365
      throws TemplateException, IOException {
366
    sendMessage(new ReminderMessage(user, vote, darCode, electionType, url), user.getUserId());
×
367
  }
×
368

369
  /**
370
   * Send a message to a user that their closeout has been completed.
371
   *
372
   * @param user        the user to send the message to
373
   * @param darCode     the data access request code for which closeout is completed
374
   * @param referenceId the data access request reference id for which closeout is completed
375
   */
376
  public void sendResearcherCloseoutCompletedMessage(User user, String darCode, String referenceId)
377
      throws TemplateException, IOException {
378
    sendMessage(new ResearcherCloseoutCompletedMessage(user, darCode, referenceId),
1✔
379
        user.getUserId());
1✔
380
  }
1✔
381

382
  /**
383
   * Send a message to a Signing Official or a DAC member that a closeout has been submitted for
384
   * review.
385
   *
386
   * @param toUser      The user to send the message to
387
   * @param darId       The Data Access Request ID associated with the closeout
388
   * @param referenceId The Reference ID of the closeout request
389
   * @param closeoutUrl The URL to the closeout request for review
390
   * @throws TemplateException Template processing exception
391
   * @throws IOException       IOException when processing the template or sending the email
392
   */
393
  public void sendSubmittedCloseoutMessage(User toUser, String darId, String referenceId, String closeoutUrl)
394
      throws TemplateException, IOException {
395
    sendMessage(new SubmittedCloseoutMessage(toUser, darId, referenceId, closeoutUrl), toUser.getUserId());
1✔
396
  }
1✔
397

398
  /**
399
   * Send a message to the user when they are issued a library card
400
   *
401
   * @param toUser The user to send the message to
402
   * @throws TemplateException Template processing exception
403
   * @throws IOException IOException when processing the template or sending the email
404
   */
405
  public void sendNewLibraryCardIssuedMessage(User toUser) throws TemplateException, IOException {
406
    sendMessage(new NewLibraryCardIssuedMessage(toUser), toUser.getUserId());
1✔
407
  }
1✔
408
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc