Skip to content

Commit

Permalink
Implement basic DSM user admin service and endpoints (#2626)
Browse files Browse the repository at this point in the history
* Add beginnings of register participant admin command

* Fix checkstyle violation

* Add support for multiple users per request

* Add user create and remove support

* Remove participant registration feature

* Initial implementation of DSM user administration

* Updates due to code review, mostly reworked UserDao
  • Loading branch information
denniscunningham authored Jul 26, 2023
1 parent b20e29a commit 91ec798
Show file tree
Hide file tree
Showing 17 changed files with 1,893 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@
import org.broadinstitute.dsm.route.TriggerSurveyRoute;
import org.broadinstitute.dsm.route.UserSettingRoute;
import org.broadinstitute.dsm.route.ViewFilterRoute;
import org.broadinstitute.dsm.route.admin.StudyRoleRoute;
import org.broadinstitute.dsm.route.admin.UserRoleRoute;
import org.broadinstitute.dsm.route.admin.UserRoute;
import org.broadinstitute.dsm.route.dashboard.NewDashboardRoute;
import org.broadinstitute.dsm.route.familymember.AddFamilyMemberRoute;
import org.broadinstitute.dsm.route.kit.KitFinalScanRoute;
Expand Down Expand Up @@ -584,6 +587,7 @@ protected void setupCustomRouting(@NonNull Config cfg) {
if (!cfg.getBoolean("ui.production")) {
get(apiRoot + RoutePath.CREATE_CLINICAL_KIT_ENDPOINT, new CreateClinicalDummyKitRoute(new OncHistoryDetailDaoImpl()),
new JsonTransformer());

get(apiRoot + RoutePath.CREATE_CLINICAL_KIT_ENDPOINT_WITH_PARTICIPANT, new CreateClinicalDummyKitRoute(
new OncHistoryDetailDaoImpl()), new JsonTransformer());
get(apiRoot + RoutePath.DUMMY_ENDPOINT, new CreateBSPDummyKitRoute(), new JsonTransformer());
Expand Down Expand Up @@ -667,6 +671,8 @@ protected void setupCustomRouting(@NonNull Config cfg) {

setupMiscellaneousRoutes();

setupAdminRoutes();

setupSharedRoutes(kitUtil, notificationUtil, patchUtil);

setupCohortTagRoutes();
Expand Down Expand Up @@ -915,9 +921,23 @@ private void setupMiscellaneousRoutes() {

GetParticipantDataRoute getParticipantDataRoute = new GetParticipantDataRoute();
get(uiRoot + RoutePath.GET_PARTICIPANT_DATA, getParticipantDataRoute, new JsonTransformer());
}

private void setupAdminRoutes() {
StudyRoleRoute studyRoleRoute = new StudyRoleRoute();
get(uiRoot + RoutePath.STUDY_ROLE, studyRoleRoute, new JsonTransformer());

UserRoleRoute userRoleRoute = new UserRoleRoute();
get(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer());
post(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer());
delete(uiRoot + RoutePath.USER_ROLE, userRoleRoute, new JsonTransformer());

UserRoute userRoute = new UserRoute();
post(uiRoot + RoutePath.USER, userRoute, new JsonTransformer());
delete(uiRoot + RoutePath.USER, userRoute, new JsonTransformer());
}


private void setupSomaticUploadRoutes(@NonNull Config cfg) {
SomaticResultUploadService somaticResultUploadService = SomaticResultUploadService.fromConfig(cfg);
post(uiRoot + RoutePath.SOMATIC_DOCUMENT_ROUTE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,32 @@
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Optional;

import lombok.NonNull;
import org.broadinstitute.dsm.db.dao.Dao;
import org.broadinstitute.dsm.db.dao.util.DaoUtil;
import org.broadinstitute.dsm.db.dto.user.UserDto;
import org.broadinstitute.dsm.exception.DsmInternalError;
import org.broadinstitute.lddp.db.SimpleResult;
import spark.utils.StringUtils;

public class UserDao implements Dao<UserDto> {

public static final String USER_ID = "user_id";
public static final String NAME = "name";
public static final String EMAIL = "email";
public static final String PHONE_NUMBER = "phone_number";
private static final String SQL_INSERT_USER = "INSERT INTO access_user (name, email) VALUES (?,?)";
public static final String IS_ACTIVE = "is_active";
private static final String SQL_INSERT_USER = "INSERT INTO access_user (name, email, phone_number, is_active) VALUES (?,?,?,?)";
private static final String SQL_DELETE_USER_BY_ID = "DELETE FROM access_user WHERE user_id = ?";
private static final String SQL_SELECT_USER_BY_EMAIL =
"SELECT user.user_id, user.name, user.email, user.phone_number FROM access_user user WHERE user.email = ?";
"SELECT user.user_id, user.name, user.email, user.phone_number, user.is_active FROM access_user user "
+ "WHERE user.email = ?";
private static final String SQL_SELECT_USER_BY_ID =
"SELECT user.user_id, user.name, user.email, user.phone_number FROM access_user user WHERE user.user_id = ?";
"SELECT user.user_id, user.name, user.email, user.phone_number, user.is_active FROM access_user user "
+ "WHERE user.user_id = ?";

public Optional<UserDto> getUserByEmail(@NonNull String email) {
SimpleResult results = inTransaction((conn) -> {
Expand All @@ -35,7 +42,8 @@ public Optional<UserDto> getUserByEmail(@NonNull String email) {
dbVals.resultValue = new UserDto(rs.getInt(USER_ID),
rs.getString(NAME),
rs.getString(EMAIL),
rs.getString(PHONE_NUMBER));
rs.getString(PHONE_NUMBER),
rs.getInt(IS_ACTIVE));
}
}
} catch (SQLException ex) {
Expand All @@ -45,7 +53,7 @@ public Optional<UserDto> getUserByEmail(@NonNull String email) {
});

if (results.resultException != null) {
throw new RuntimeException("Error getting list of realms ", results.resultException);
throw new DsmInternalError("Error getting user by email " + email, results.resultException);
}
return Optional.ofNullable((UserDto) results.resultValue);
}
Expand All @@ -61,7 +69,8 @@ public Optional<UserDto> get(long userId) {
dbVals.resultValue = new UserDto(rs.getInt(USER_ID),
rs.getString(NAME),
rs.getString(EMAIL),
rs.getString(PHONE_NUMBER));
rs.getString(PHONE_NUMBER),
rs.getInt(IS_ACTIVE));
}
}
} catch (SQLException ex) {
Expand All @@ -71,52 +80,43 @@ public Optional<UserDto> get(long userId) {
});

if (results.resultException != null) {
throw new RuntimeException("Error getting list of realms ", results.resultException);
throw new DsmInternalError("Error getting user by id " + userId, results.resultException);
}
return Optional.ofNullable((UserDto) results.resultValue);
}

@Override
public int create(UserDto userDto) {
SimpleResult results = inTransaction((conn) -> {
SimpleResult execResult = new SimpleResult();
try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER, PreparedStatement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, userDto.getName().orElse(""));
stmt.setString(2, userDto.getEmail().orElse(""));
String email = userDto.getEmail().orElse(null);
if (StringUtils.isBlank(email)) {
throw new DsmInternalError("Error inserting user: email is blank");
}
return inTransaction(conn -> {
try (PreparedStatement stmt = conn.prepareStatement(SQL_INSERT_USER, Statement.RETURN_GENERATED_KEYS)) {
stmt.setString(1, userDto.getName().orElse(null));
stmt.setString(2, email);
stmt.setString(3, userDto.getPhoneNumber().orElse(null));
stmt.setInt(4, userDto.getIsActive().orElse(1));
stmt.executeUpdate();
try (ResultSet rs = stmt.getGeneratedKeys()) {
if (rs.next()) {
execResult.resultValue = rs.getInt(1);
if (!rs.next()) {
throw new DsmInternalError("Error inserting user " + email);
}
return rs.getInt(1);
}
} catch (SQLException ex) {
execResult.resultException = ex;
throw new DsmInternalError("Error inserting user " + email, ex);
}
return execResult;
});
if (results.resultException != null) {
throw new RuntimeException("Error inserting user with "
+ userDto.getEmail(), results.resultException);
}
return (int) results.resultValue;
}

@Override
public int delete(int id) {
SimpleResult results = inTransaction((conn) -> {
SimpleResult execResult = new SimpleResult();
try (PreparedStatement stmt = conn.prepareStatement(SQL_DELETE_USER_BY_ID)) {
stmt.setInt(1, id);
execResult.resultValue = stmt.executeUpdate();
} catch (SQLException ex) {
execResult.resultException = ex;
}
return execResult;
});
if (results.resultException != null) {
throw new RuntimeException("Error deleting user with "
+ id, results.resultException);
SimpleResult simpleResult = DaoUtil.deleteById(id, SQL_DELETE_USER_BY_ID);
if (simpleResult.resultException != null) {
throw new DsmInternalError("Error deleting user with ID: " + id,
simpleResult.resultException);
}
return (int) results.resultValue;
return (int) simpleResult.resultValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@

import java.util.Optional;

import lombok.AllArgsConstructor;
import lombok.Setter;

@Setter
@AllArgsConstructor
public class UserDto {

private int id;
private String name;
private String email;
private String phoneNumber;
private Integer isActive;

public UserDto(int id, String name, String email, String phoneNumber) {
this.id = id;
public UserDto(String name, String email, String phoneNumber) {
this.name = name;
this.email = email;
this.phoneNumber = phoneNumber;
}

public UserDto() {

}

public int getId() {
Expand All @@ -38,4 +39,7 @@ public Optional<String> getEmail() {
public Optional<String> getPhoneNumber() {
return Optional.ofNullable(phoneNumber);
}
public Optional<Integer> getIsActive() {
return Optional.ofNullable(isActive);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.broadinstitute.dsm.route.admin;

import lombok.extern.slf4j.Slf4j;
import org.broadinstitute.dsm.exception.DSMBadRequestException;
import org.broadinstitute.dsm.exception.DsmInternalError;
import org.broadinstitute.dsm.security.RequestHandler;
import org.broadinstitute.dsm.service.admin.StudyRoleResponse;
import org.broadinstitute.dsm.service.admin.UserAdminService;
import org.broadinstitute.dsm.statics.RoutePath;
import org.broadinstitute.lddp.handlers.util.Result;
import spark.Request;
import spark.Response;

@Slf4j
public class StudyRoleRoute extends RequestHandler {

@Override
public Object processRequest(Request request, Response response, String userId) {
String studyGroup;
try {
studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap());
} catch (Exception e) {
return UserRoleRoute.handleError(e, "getting study group", response);
}

UserAdminService adminService = new UserAdminService(userId, studyGroup);

if (!request.requestMethod().equals(RoutePath.RequestMethod.GET.toString())) {
String msg = "Invalid HTTP method for UserRoleRoute: " + request.requestMethod();
log.error(msg);
response.status(500);
return msg;
}

try {
return adminService.getStudyRoles();
} catch (Exception e) {
return UserRoleRoute.handleError(e, "getting study roles", response);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.broadinstitute.dsm.route.admin;

import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.broadinstitute.dsm.exception.DSMBadRequestException;
import org.broadinstitute.dsm.exception.DsmInternalError;
import org.broadinstitute.dsm.security.RequestHandler;
import org.broadinstitute.dsm.service.admin.UserRequest;
import org.broadinstitute.dsm.service.admin.UserRoleRequest;
import org.broadinstitute.dsm.service.admin.UserAdminService;
import org.broadinstitute.dsm.statics.RoutePath;
import org.broadinstitute.lddp.handlers.util.Result;
import spark.Request;
import spark.Response;

@Slf4j
public class UserRoleRoute extends RequestHandler {

@Override
public Object processRequest(Request request, Response response, String userId) {
String studyGroup;
try {
studyGroup = UserAdminService.getStudyGroup(request.queryMap().toMap());
} catch (Exception e) {
return handleError(e, "getting study group", response);
}

UserAdminService service = new UserAdminService(userId, studyGroup);

String requestMethod = request.requestMethod();
String body = request.body();
boolean hasBody = !StringUtils.isBlank(body);

if (requestMethod.equals(RoutePath.RequestMethod.GET.toString())) {
UserRequest req = null;
if (hasBody) {
try {
req = new Gson().fromJson(body, UserRequest.class);
} catch (Exception e) {
log.info("Invalid request format for {}", body);
response.status(400);
return "Invalid request format";
}
}
try {
return service.getUserRoles(req);
} catch (Exception e) {
return handleError(e, "getting user roles", response);
}
}

if (!hasBody) {
response.status(400);
return "Request body is blank";
}

UserRoleRequest req;
try {
req = new Gson().fromJson(body, UserRoleRequest.class);
} catch (Exception e) {
log.info("Invalid request format for {}", body);
response.status(400);
return "Invalid request format";
}

if (requestMethod.equals(RoutePath.RequestMethod.POST.toString())) {
try {
service.addUserRoles(req);
} catch (Exception e) {
return handleError(e, "adding user roles", response);
}
} else if (requestMethod.equals(RoutePath.RequestMethod.DELETE.toString())) {
try {
service.removeUserRoles(req);
} catch (Exception e) {
return handleError(e, "removing user roles", response);
}
} else {
String msg = "Invalid HTTP method for UserRoleRoute: " + requestMethod;
log.error(msg);
response.status(500);
return msg;
}

return new Result(200);
}

protected static String handleError(Throwable e, String operation, Response response) {
if (e instanceof DSMBadRequestException) {
response.status(400);
log.info("DSMBadRequestException {}: {}", operation, e.getMessage());
return e.getMessage();
} else if (e instanceof DsmInternalError) {
log.error("Error {}: {}", operation, e.getMessage());
response.status(500);
return "Internal error. Contact development team";
}

// any other exception
log.error("Error {}: {}", operation, e.getMessage());
response.status(500);
return e.getMessage();
}
}
Loading

0 comments on commit 91ec798

Please sign in to comment.