Conversation
…o be able to resolve dependencies at github.
| public Species getById(UUID speciesId) throws DoesNotExistException{ | ||
|
|
||
| SpeciesEntity speciesEntity = dao.fetchOneById(speciesId); | ||
|
|
||
| if (speciesEntity == null){ | ||
| throw new DoesNotExistException("Species does not exist"); | ||
| } | ||
|
|
||
| Species species = new Species(speciesEntity); | ||
| return species; | ||
| } | ||
|
|
||
| public Optional<SpeciesEntity> getByIdOptional(UUID speciesId) { | ||
|
|
||
| SpeciesEntity species = dao.fetchOneById(speciesId); | ||
|
|
||
| if (species == null) { | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| return Optional.of(species); | ||
| } |
There was a problem hiding this comment.
I'd suggest combining these methods and returning Optional<Species>
| public User getById(UUID userId) throws DoesNotExistException { | ||
|
|
||
| Optional<User> user = getByIdOptional(userId); | ||
|
|
||
| if (user.isEmpty()) { | ||
| throw new DoesNotExistException("UUID for user does not exist"); | ||
| } | ||
|
|
||
| return user.get(); | ||
| } | ||
|
|
||
| public Optional<User> getByIdOptional(UUID userId) { | ||
|
|
||
| // User has been authenticated against orcid, check they have a bi account. | ||
| BiUser biUser = dao.fetchOneById(userId); | ||
| BiUserEntity biUser = dao.fetchOneById(userId); | ||
|
|
||
| if (biUser == null) { | ||
| throw new DoesNotExistException("UUID for user does not exist"); | ||
| return Optional.empty(); | ||
| } | ||
|
|
||
| return new User(biUser); | ||
| return Optional.of(new User(biUser)); | ||
| } |
There was a problem hiding this comment.
I'd suggest combining these methods and returning Optional<User>
| return created.get(); | ||
| } | ||
|
|
||
| public Optional<User> createOptional(UserRequest user) { |
There was a problem hiding this comment.
I'd suggest making this method private given that the create method throws an AlreadyExistsException if the user is not created, otherwise it always returns the created user.
| return species; | ||
| } | ||
|
|
||
| public Optional<SpeciesEntity> getByIdOptional(UUID speciesId) { |
There was a problem hiding this comment.
Should we have this return an Optional<Species> for consistency at the service interface? That was the approach I took with the ProgramUser service.
There was a problem hiding this comment.
I think this was fixed in the commit from this morning
| @Secured(SecurityRule.IS_AUTHENTICATED) | ||
| public HttpResponse<Response<ProgramUser>> updateProgramUser(Principal principal, @PathVariable UUID programId, @PathVariable UUID userId, | ||
| @Valid @Body ProgramUserRequest programUserRequest) { | ||
| /* Add a user to a program. Create the user if they don't exist. */ |
There was a problem hiding this comment.
Agree, will update or remove and indicate when changes have been pushed.
| import static org.breedinginsight.dao.db.Tables.*; | ||
|
|
||
| @Singleton | ||
| public class ProgramDao extends org.breedinginsight.dao.db.tables.daos.ProgramDao { |
There was a problem hiding this comment.
ok, don't have the same problem with the ProgramUserDao but I can rename for consistency. Will indicate when changes are pushed.
| @Inject | ||
| DSLContext dsl; | ||
| @Inject | ||
| public ProgramDao(Configuration config) { | ||
| super(config); | ||
| } |
There was a problem hiding this comment.
ok, I'll update and indicate when changes have been pushed.
| SelectOnConditionStep<Record> query = dsl.select() | ||
| .from(PROGRAM) | ||
| .join(SPECIES).on(PROGRAM.SPECIES_ID.eq(SPECIES.ID)) | ||
| .leftJoin(createdByUser).on(PROGRAM.CREATED_BY.eq(createdByUser.ID)) |
There was a problem hiding this comment.
@ctucker3 should be fine, let me know when the db is updated and I can update mine.
| user.setUser(User.parseSQLRecord(records.get(0))); | ||
|
|
||
| // populate roles | ||
| for (Record record : records) { |
There was a problem hiding this comment.
@timparsons What is the rationale for splitting into two database calls?
| programEntity.setAbbreviation(programRequest.getAbbreviation()); | ||
| programEntity.setObjective(programRequest.getObjective()); | ||
| programEntity.setDocumentationUrl(programRequest.getDocumentationUrl()); | ||
| programEntity.setUpdatedAt(OffsetDateTime.now()); |
| optUser = userService.createOptional(userRequest); | ||
| if (optUser.isEmpty()) { | ||
| throw new AlreadyExistsException("Cannot create new user, email already exists"); | ||
| } else { | ||
| user = optUser.get(); | ||
| } |
There was a problem hiding this comment.
Was trying to avoid having exceptions thrown that would make the code harder to follow but in this case it's not bad since there's no need for any flow control based on the exception generation, just not as explicit where the exception is coming from without looking at the called function.
| List<UUID> roleIds = programUserRequest.getRoles().stream().map(role -> role.getId()).collect(Collectors.toList()); | ||
|
|
||
| if (hasDuplicatesRoleIds(roleIds)) { | ||
| throw new AlreadyExistsException("Duplicate roles specified, must be unique"); | ||
| } |
There was a problem hiding this comment.
So you don't think we should send an error status back when the client makes a request with duplicate roles, just silently handle that as ok? If that is the case, I agree about the set, but thought that should be an error condition.
| String orcid = principal.getName(); | ||
| User user = userService.getByOrcid(orcid); | ||
| Program program = programService.create(programRequest, user); | ||
| Response<Program> response = new Response(program); | ||
| return HttpResponse.ok(response); | ||
| } catch (DoesNotExistException e){ | ||
| log.info(e.getMessage()); | ||
| return HttpResponse.status(HttpStatus.NOT_FOUND, e.getMessage()); |
There was a problem hiding this comment.
For the future, it would make the code cleaner to split out the try/catch blocks for service calls from different services. In this example, having the try/catch for fetching the User by their ORCID iD as a separate block from creating the program would make it clearer that the DoesNotExistException is thrown by UserService#getByOrcid and not by ProgramService#create
There was a problem hiding this comment.
1 suggested change (having
UserService#fetchByOrcidreturnOptional<User>) that should be addressed before merge. Left a few other comments for general discussion, but not blocking of approval of the merge. I'm going to approve now even with the suggested change.
Added card as discussed.
| private UserDao dao; | ||
| private UserDAO dao; | ||
|
|
||
| public User getByOrcid(String orcid) throws DoesNotExistException { |
There was a problem hiding this comment.
I think this should return Optional<User> just as getById does
| public List<Role> getRolesByIds(List<UUID> roleIds) { | ||
| List<RoleEntity> roleEntities = dao.fetchById(roleIds.stream().toArray(UUID[]::new)); | ||
|
|
||
| List<Role> roles = new ArrayList<>(); | ||
| for (RoleEntity roleEntity: roleEntities){ | ||
| roles.add(new Role(roleEntity)); | ||
| } | ||
| return roles; | ||
| } |
There was a problem hiding this comment.
What if a role id doesn't exist? Should the service verify that and notify the caller if the list of roles returned from the DB doesn't contain all the roles requested by ID?
There was a problem hiding this comment.
This is currently checked by the only caller in ProgramUserService#validateAndGetRoles. In the future if this method is used by other callers we could evaluate whether the logic should be moved into the RoleService or by the callers.
| insert into bi_user (orcid, name, email) values ('0000-0002-7156-4503', 'Nick Palladino', 'nicksandbox@mailinator.com'); | ||
| insert into bi_user (orcid, name) values ('0000-0003-0437-8310', 'BI-DEV Admin'); | ||
| insert into bi_user (id, orcid, name, email) values ('74a6ebfe-d114-419b-8bdc-2f7b52d26172', '1111-2222-3333-4444', 'Test User', 'test@test.com'); No newline at end of file | ||
| insert into bi_user (id, orcid, name, created_by, updated_by) |
There was a problem hiding this comment.
Is it worth having a "system user" that's not a real person? I.e. "BI-Admin"
There was a problem hiding this comment.
Yeah good question. For now it is a placeholder, we haven't really discussed what the start up of the application looks like for other groups, and I imagine that this starting user is going to be in that discussion.
I could see an admin user that is the first user in any system that gets spun up. In the test isolation task this user is made configurable by making the admin orcid an environmental variable. We will need at least one user in the system with system admin permissions starting out to get your programs and users created.
timparsons
left a comment
There was a problem hiding this comment.
1 suggested change (having UserService#fetchByOrcid return Optional<User>) that should be addressed before merge. Left a few other comments for general discussion, but not blocking of approval of the merge. I'm going to approve now even with the suggested change.
PRO-31 Log in via ORCID + table lookup Approved-by: Liz Woods
Includes cards PRO-42 Create program endpoints, INF-85 Create GET Species endpoints, and INF-83 Create program user endpoints. The project should build, the API documentation should be verified to be updated and in sync with the implemented endpoints, and unit tests should exist and pass that cover the implemented endpoints.
Here are the added endpoints:
GET /programs
POST /programs
GET /programs/{id}
PUT /programs/{id}
DELETE /programs/{id}
GET /species
GET /species/{id}
GET /programs/{id}/users
POST /programs/{id}/users
GET /programs/{id}/users/{userId}
PUT /programs/{id}/users/{userId}
DELETE /programs/{id}/users/{userId}