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

DataBiosphere / consent / #6333

15 Aug 2025 12:17PM UTC coverage: 83.37% (+0.03%) from 83.338%
#6333

push

web-flow
DT-2091: Update DAC Approve Dataset performance (#2636)

Co-authored-by: otchet-broad <111771148+otchet-broad@users.noreply.github.com>

83 of 105 new or added lines in 5 files covered. (79.05%)

4 existing lines in 1 file now uncovered.

10949 of 13133 relevant lines covered (83.37%)

0.83 hits per line

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

91.89
/src/main/java/org/broadinstitute/consent/http/resources/DacResource.java
1
package org.broadinstitute.consent.http.resources;
2

3
import com.google.api.client.http.HttpStatusCodes;
4
import com.google.inject.Inject;
5
import io.dropwizard.auth.Auth;
6
import jakarta.annotation.security.RolesAllowed;
7
import jakarta.ws.rs.BadRequestException;
8
import jakarta.ws.rs.Consumes;
9
import jakarta.ws.rs.DELETE;
10
import jakarta.ws.rs.GET;
11
import jakarta.ws.rs.NotAuthorizedException;
12
import jakarta.ws.rs.NotFoundException;
13
import jakarta.ws.rs.POST;
14
import jakarta.ws.rs.PUT;
15
import jakarta.ws.rs.Path;
16
import jakarta.ws.rs.PathParam;
17
import jakarta.ws.rs.Produces;
18
import jakarta.ws.rs.QueryParam;
19
import jakarta.ws.rs.ServerErrorException;
20
import jakarta.ws.rs.core.Response;
21
import java.util.List;
22
import java.util.Objects;
23
import java.util.Optional;
24
import org.broadinstitute.consent.http.enumeration.UserRoles;
25
import org.broadinstitute.consent.http.models.AuthUser;
26
import org.broadinstitute.consent.http.models.Dac;
27
import org.broadinstitute.consent.http.models.Dataset;
28
import org.broadinstitute.consent.http.models.DatasetApproval;
29
import org.broadinstitute.consent.http.models.Role;
30
import org.broadinstitute.consent.http.models.User;
31
import org.broadinstitute.consent.http.service.DacService;
32
import org.broadinstitute.consent.http.service.DatasetService;
33
import org.broadinstitute.consent.http.service.UserService;
34
import org.broadinstitute.consent.http.util.gson.GsonUtil;
35

36
@Path("api/dac")
37
public class DacResource extends Resource {
38

39
  private final DacService dacService;
40
  private final UserService userService;
41
  private final DatasetService datasetService;
42

43
  @Inject
44
  public DacResource(DacService dacService, UserService userService, DatasetService datasetService) {
1✔
45
    this.dacService = dacService;
1✔
46
    this.userService = userService;
1✔
47
    this.datasetService = datasetService;
1✔
48
  }
1✔
49

50
  @GET
51
  @Produces("application/json")
52
  @RolesAllowed({ADMIN, MEMBER, CHAIRPERSON, RESEARCHER})
53
  public Response findAll(@Auth AuthUser authUser,
54
      @QueryParam("withUsers") Optional<Boolean> withUsers) {
55
    try {
56
      final Boolean includeUsers = withUsers.orElse(true);
1✔
57
      List<Dac> dacs = dacService.findDacsWithMembersOption(includeUsers);
1✔
58
      return Response.ok().entity(unmarshal(dacs)).build();
1✔
NEW
59
    } catch (Exception e) {
×
NEW
60
      return createExceptionResponse(e);
×
61
    }
62
  }
63

64
  @POST
65
  @Produces("application/json")
66
  @RolesAllowed({ADMIN})
67
  public Response createDac(@Auth AuthUser authUser, String json) {
68
    try {
69
      Dac dac = GsonUtil.buildGson().fromJson(json, Dac.class);
1✔
70
      if (dac == null) {
1✔
71
        throw new BadRequestException("DAC is required");
1✔
72
      }
73
      if (dac.getName() == null) {
1✔
74
        throw new BadRequestException("DAC Name is required");
1✔
75
      }
76
      if (dac.getDescription() == null) {
1✔
77
        throw new BadRequestException("DAC Description is required");
1✔
78
      }
79
      Integer dacId;
80
      if (Objects.isNull(dac.getEmail())) {
1✔
81
        dacId = dacService.createDac(dac.getName(), dac.getDescription());
1✔
82
      } else {
83
        dacId = dacService.createDac(dac.getName(), dac.getDescription(), dac.getEmail());
1✔
84
      }
85
      if (dacId == null) {
1✔
NEW
86
        throw new ServerErrorException("Unable to create DAC with name: " + dac.getName() + " and description: "
×
NEW
87
            + dac.getDescription(), HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
×
88
      }
89
      Dac savedDac = dacService.findById(dacId);
1✔
90
      return Response.ok().entity(unmarshal(savedDac)).build();
1✔
91
    } catch (Exception e) {
1✔
92
      return createExceptionResponse(e);
1✔
93
    }
94
  }
95

96
  @PUT
97
  @Produces("application/json")
98
  @RolesAllowed({ADMIN, CHAIRPERSON})
99
  public Response updateDac(@Auth AuthUser authUser, String json) {
100
    try {
101
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
102
      Dac dac = GsonUtil.buildGson().fromJson(json, Dac.class);
1✔
103
      if (dac == null) {
1✔
104
        throw new BadRequestException("DAC is required");
1✔
105
      }
106
      if (dac.getDacId() == null) {
1✔
107
        throw new BadRequestException("DAC ID is required");
1✔
108
      }
109
      // Ensure that the user can only update a DAC they're a chairperson for.
110
      if (!user.hasUserRole(UserRoles.ADMIN)) {
1✔
111
        if (!user.verifyDACRole(UserRoles.CHAIRPERSON.getRoleName(), dac.getDacId())) {
1✔
112
          throw new NotAuthorizedException(
1✔
113
              "You do not have the required permissions to update DAC");
114
        }
115
      }
116
      if (dac.getName() == null) {
1✔
117
        throw new BadRequestException("DAC Name is required");
1✔
118
      }
119
      if (dac.getDescription() == null) {
1✔
120
        throw new BadRequestException("DAC Description is required");
1✔
121
      }
122
      if (Objects.isNull(dac.getEmail())) {
1✔
123
        dacService.updateDac(dac.getName(), dac.getDescription(), dac.getDacId());
1✔
124
      } else {
125
        dacService.updateDac(dac.getName(), dac.getDescription(), dac.getEmail(), dac.getDacId());
1✔
126
      }
127
      Dac savedDac = dacService.findById(dac.getDacId());
1✔
128
      return Response.ok().entity(unmarshal(savedDac)).build();
1✔
129
    } catch (Exception e) {
1✔
130
      return createExceptionResponse(e);
1✔
131
    }
132
  }
133

134
  @GET
135
  @Path("{dacId}")
136
  @Produces("application/json")
137
  @RolesAllowed({ADMIN, MEMBER, CHAIRPERSON})
138
  public Response findById(@Auth AuthUser authUser, @PathParam("dacId") Integer dacId) {
139
    try {
140
      Dac dac = findDacById(dacId);
1✔
141
      return Response.ok().entity(unmarshal(dac)).build();
1✔
142
    } catch (Exception e) {
1✔
143
      return createExceptionResponse(e);
1✔
144
    }
145
  }
146

147
  @DELETE
148
  @Path("{dacId}")
149
  @Produces("application/json")
150
  @RolesAllowed({ADMIN})
151
  public Response deleteDac(@Auth AuthUser authUser, @PathParam("dacId") Integer dacId) {
152
    try {
153
      findDacById(dacId);
1✔
154
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
155
      dacService.deleteDac(user, dacId);
1✔
156
      return Response.ok().build();
1✔
157
    } catch (Exception e) {
1✔
158
      return createExceptionResponse(e);
1✔
159
    }
160
  }
161

162
  @POST
163
  @Path("{dacId}/member/{userId}")
164
  @RolesAllowed({ADMIN, CHAIRPERSON})
165
  public Response addDacMember(@Auth AuthUser authUser, @PathParam("dacId") Integer dacId,
166
      @PathParam("userId") Integer userId) {
167
    try {
168
      checkUserExistsInDac(dacId, userId);
1✔
169
      Role role = dacService.getMemberRole();
1✔
170
      User user = findDacUser(userId);
1✔
171
      Dac dac = findDacById(dacId);
1✔
172
      checkUserRoleInDac(dac, authUser);
1✔
173
      User member = dacService.addDacMember(role, user, dac);
1✔
174
      return Response.ok().entity(member).build();
1✔
175
    } catch (Exception e) {
1✔
176
      return createExceptionResponse(e);
1✔
177
    }
178
  }
179

180
  @DELETE
181
  @Path("{dacId}/member/{userId}")
182
  @RolesAllowed({ADMIN, CHAIRPERSON})
183
  public Response removeDacMember(@Auth AuthUser authUser, @PathParam("dacId") Integer dacId,
184
      @PathParam("userId") Integer userId) {
185
    try {
186
      Role role = dacService.getMemberRole();
1✔
187
      User user = findDacUser(userId);
1✔
188
      Dac dac = findDacById(dacId);
1✔
189
      checkUserRoleInDac(dac, authUser);
1✔
190
      User auditUser = userService.findUserByEmail(authUser.getEmail());
1✔
191
      dacService.removeDacMember(role, user, dac, auditUser.getUserId());
1✔
192
      return Response.ok().build();
1✔
193
    } catch (Exception e) {
1✔
194
      return createExceptionResponse(e);
1✔
195
    }
196
  }
197

198
  @POST
199
  @Path("{dacId}/chair/{userId}")
200
  @RolesAllowed({ADMIN, CHAIRPERSON})
201
  public Response addDacChair(@Auth AuthUser authUser, @PathParam("dacId") Integer dacId,
202
      @PathParam("userId") Integer userId) {
203
    try {
204
      checkUserExistsInDac(dacId, userId);
1✔
205
      Role role = dacService.getChairpersonRole();
1✔
206
      User user = findDacUser(userId);
1✔
207
      Dac dac = findDacById(dacId);
1✔
208
      checkUserRoleInDac(dac, authUser);
1✔
209
      User member = dacService.addDacMember(role, user, dac);
1✔
210
      return Response.ok().entity(member).build();
1✔
211
    } catch (Exception e) {
1✔
212
      return createExceptionResponse(e);
1✔
213
    }
214
  }
215

216
  @DELETE
217
  @Path("{dacId}/chair/{userId}")
218
  @RolesAllowed({ADMIN, CHAIRPERSON})
219
  public Response removeDacChair(@Auth AuthUser authUser, @PathParam("dacId") Integer dacId,
220
      @PathParam("userId") Integer userId) {
221
    try {
222
      Role role = dacService.getChairpersonRole();
1✔
223
      User user = findDacUser(userId);
1✔
224
      Dac dac = findDacById(dacId);
1✔
225
      checkUserRoleInDac(dac, authUser);
1✔
226
      User auditUser = userService.findUserByEmail(authUser.getEmail());
1✔
227
      dacService.removeDacMember(role, user, dac, auditUser.getUserId());
1✔
228
      return Response.ok().build();
1✔
229
    } catch (Exception e) {
1✔
230
      return createExceptionResponse(e);
1✔
231
    }
232
  }
233

234
  @GET
235
  @Path("{dacId}/datasets")
236
  @Produces("application/json")
237
  @RolesAllowed({ADMIN, MEMBER, CHAIRPERSON})
238
  public Response findAllDacDatasets(@Auth AuthUser user, @PathParam("dacId") Integer dacId) {
239
    try {
240
      Dac dac = findDacById(dacId);
1✔
241
      checkUserRoleInDac(dac, user);
1✔
242
      List<Dataset> datasets = dacService.findDatasetsByDacId(dacId);
1✔
243
      return Response.ok().entity(unmarshal(datasets)).build();
1✔
244
    } catch (Exception e) {
1✔
245
      return createExceptionResponse(e);
1✔
246
    }
247
  }
248

249
  @GET
250
  @Path("users/{term}")
251
  @Produces("application/json")
252
  @RolesAllowed({ADMIN, MEMBER, CHAIRPERSON})
253
  public Response filterUsers(@Auth AuthUser authUser, @PathParam("term") String term) {
254
    try {
NEW
255
      List<User> users = dacService.findAllDACUsersBySearchString(term);
×
NEW
256
      return Response.ok().entity(users).build();
×
NEW
257
    } catch (Exception e) {
×
NEW
258
      return createExceptionResponse(e);
×
259
    }
260
  }
261

262
  @PUT
263
  @Consumes("application/json")
264
  @Produces("application/json")
265
  @Path("{dacId}/dataset/{datasetId}")
266
  @RolesAllowed({CHAIRPERSON})
267
  public Response approveDataset(@Auth AuthUser authUser, @PathParam("dacId") Integer dacId,
268
      @PathParam("datasetId") Integer datasetId, String json) {
269
    try {
270
      User user = userService.findUserByEmail(authUser.getEmail());
1✔
271
      Dataset dataset = datasetService.findDatasetWithoutFSOInformation(datasetId);
1✔
272
      if (Objects.isNull(dataset) || !Objects.equals(dataset.getDacId(), dacId)) {
1✔
273
        //Vague message is intentional, don't want to reveal too much info
274
        throw new NotFoundException("Dataset not found");
1✔
275
      }
276
      boolean userHasRole = user.verifyDACRole(UserRoles.CHAIRPERSON.getRoleName(), dacId);
1✔
277
      if (!userHasRole) {
1✔
278
        throw new NotFoundException("User role not found");
1✔
279
      }
280
      if (Objects.isNull(json) || json.isBlank()) {
1✔
281
        throw new BadRequestException("Request body is empty");
×
282
      }
283
      DatasetApproval payload = GsonUtil.buildGson().fromJson(json, DatasetApproval.class);
1✔
284
      if (Objects.isNull(payload.getApproval())) {
1✔
285
        throw new BadRequestException("Invalid request payload");
1✔
286
      }
287
      Dataset updatedDataset = datasetService.approveDataset(dataset, user, payload.getApproval());
1✔
288
      return Response.ok().entity(unmarshal(updatedDataset)).build();
1✔
289
    } catch (Exception e) {
1✔
290
      return createExceptionResponse(e);
1✔
291
    }
292
  }
293

294
  private User findDacUser(Integer userId) {
295
    User user = dacService.findUserById(userId);
1✔
296
    if (user == null) {
1✔
297
      throw new NotFoundException("Unable to find User with the provided id: " + userId);
×
298
    }
299
    return user;
1✔
300
  }
301

302
  private Dac findDacById(Integer dacId) {
303
    Dac dac = dacService.findById(dacId);
1✔
304
    if (dac == null) {
1✔
305
      throw new NotFoundException(
1✔
306
          "Unable to find Data Access Committee with the provided id: " + dacId);
307
    }
308
    return dac;
1✔
309
  }
310

311
  /**
312
   * Validate that a user is not already a member of a DAC. If they are, throw a conflict
313
   * exception.
314
   *
315
   * @param dacId  The DAC Id
316
   * @param userId The User Id
317
   * @throws UnsupportedOperationException Conflicts
318
   */
319
  private void checkUserExistsInDac(Integer dacId, Integer userId)
320
      throws UnsupportedOperationException {
321
    List<User> currentMembers = dacService.findMembersByDacId(dacId);
1✔
322
    Optional<User> isMember = currentMembers.
1✔
323
        stream().
1✔
324
        filter(u -> u.getUserId().equals(userId)).
1✔
325
        findFirst();
1✔
326
    if (isMember.isPresent()) {
1✔
327
      // This is handled as a 409 Conflict
328
      throw new UnsupportedOperationException(
×
329
          "User with id " + userId + " is already a member of this DAC");
330
    }
331
  }
1✔
332

333
  /**
334
   * - Admins can make any modifications to any Dac chairs or members - Chairpersons can only make
335
   * modifications to chairs and members in a DAC that they are a chairperson in.
336
   *
337
   * @param dac      The Dac
338
   * @param authUser The AuthUser
339
   * @throws NotAuthorizedException Not authorized
340
   */
341
  private void checkUserRoleInDac(Dac dac, AuthUser authUser) throws NotAuthorizedException {
342
    User user = userService.findUserByEmail(authUser.getEmail());
1✔
343
    if (user.getRoles().stream()
1✔
344
        .anyMatch(ur -> ur.getRoleId().equals(UserRoles.ADMIN.getRoleId()))) {
1✔
345
      return;
1✔
346
    }
347

348
    NotAuthorizedException e = new NotAuthorizedException("User not authorized");
1✔
349
    if (Objects.isNull(dac.getChairpersons()) || dac.getChairpersons().isEmpty()) {
1✔
350
      throw e;
1✔
351
    }
352

353
    Optional<User> chair = dac.getChairpersons().stream()
1✔
354
        .filter(u -> u.getUserId().equals(user.getUserId()))
1✔
355
        .findFirst();
1✔
356
    if (chair.isEmpty()) {
1✔
357
      throw e;
×
358
    }
359
  }
1✔
360
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc