Skip to content

Commit

Permalink
Merge pull request #266 from ibi-group/feature/OTP-1372-plan-trip-for…
Browse files Browse the repository at this point in the history
…-others

Get dependent's mobility profile
  • Loading branch information
binh-dam-ibigroup authored Nov 18, 2024
2 parents 120d920 + e186857 commit 5a7b382
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 13 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@
<version>2.7</version>
</dependency>

<!-- Use for better object state checking. -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>

<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.eclipse.jetty.http.HttpStatus;
import org.opentripplanner.middleware.auth.Auth0Connection;
import org.opentripplanner.middleware.auth.RequestingUser;
import org.opentripplanner.middleware.models.MobilityProfileLite;
import org.opentripplanner.middleware.models.OtpUser;
import org.opentripplanner.middleware.persistence.Persistence;
import org.opentripplanner.middleware.tripmonitor.TrustedCompanion;
Expand All @@ -25,6 +26,7 @@

import static io.github.manusant.ss.descriptor.MethodDescriptor.path;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.ACCEPT_KEY;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.DEPENDENT_USER_IDS;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.USER_LOCALE;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.ensureRelatedUserIntegrity;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.manageAcceptDependentEmail;
Expand Down Expand Up @@ -100,6 +102,13 @@ protected void buildEndpoint(ApiEndpoint baseEndpoint) {
.withResponseType(OtpUser.class),
TrustedCompanion::acceptDependent
)
.get(path(ROOT_ROUTE + "/getdependentmobilityprofile")
.withDescription("Retrieve the mobility profile for each valid dependent user id provided.")
.withResponses(SwaggerUtils.createStandardResponses(MobilityProfileLite.class))
.withPathParam().withName(DEPENDENT_USER_IDS).withRequired(true).withDescription("A comma separated list of dependent user ids.").and()
.withResponseAsCollection(MobilityProfileLite.class),
TrustedCompanion::getDependentMobilityProfile, JsonUtils::toJson
)
.get(path(ROOT_ROUTE + String.format(VERIFY_ROUTE_TEMPLATE, ID_PARAM, VERIFY_PATH, PHONE_PARAM))
.withDescription("Request an SMS verification to be sent to an OtpUser's phone number.")
.withPathParam().withName(ID_PARAM).withRequired(true).withDescription("The id of the OtpUser.").and()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.opentripplanner.middleware.models;

import java.util.Objects;

import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;

public class MobilityProfileLite {
public String userId;
public String mobilityMode;
public String email;
public String name;

/** This no-arg constructor exists to make MongoDB happy. */
public MobilityProfileLite() {
}

public MobilityProfileLite(OtpUser user) {
this.userId = user.id;
this.mobilityMode = isNotEmpty(user.mobilityProfile) ? user.mobilityProfile.mobilityMode : null;
this.email = user.email;
this.name = user.name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MobilityProfileLite that = (MobilityProfileLite) o;
return
Objects.equals(userId, that.userId) &&
Objects.equals(mobilityMode, that.mobilityMode) &&
Objects.equals(email, that.email) &&
Objects.equals(name, that.name);
}

@Override
public int hashCode() {
return Objects.hash(userId, mobilityMode, email, name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public enum Notification {
/** Companions and observers of this user. */
public List<RelatedUser> relatedUsers = new ArrayList<>();

/** Users that are dependent on this user. */
/** A list of users (their ids only) that are dependent on this user. */
public List<String> dependents = new ArrayList<>();

/** This user's name */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package org.opentripplanner.middleware.tripmonitor;

import com.mongodb.client.FindIterable;
import com.mongodb.client.model.Filters;
import org.apache.logging.log4j.util.Strings;
import org.eclipse.jetty.http.HttpStatus;
import org.opentripplanner.middleware.OtpMiddlewareMain;
import org.opentripplanner.middleware.auth.Auth0Connection;
import org.opentripplanner.middleware.i18n.Message;
import org.opentripplanner.middleware.models.MobilityProfileLite;
import org.opentripplanner.middleware.models.OtpUser;
import org.opentripplanner.middleware.models.RelatedUser;
import org.opentripplanner.middleware.persistence.Persistence;
Expand All @@ -15,19 +19,26 @@
import spark.Response;

import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import static com.mongodb.client.model.Filters.eq;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang3.ObjectUtils.isEmpty;
import static org.apache.commons.lang3.ObjectUtils.isNotEmpty;
import static org.opentripplanner.middleware.tripmonitor.jobs.CheckMonitoredTrip.SETTINGS_PATH;
import static org.opentripplanner.middleware.utils.I18nUtils.getLocaleFromString;
import static org.opentripplanner.middleware.utils.I18nUtils.label;
import static org.opentripplanner.middleware.utils.JsonUtils.logMessageAndHalt;

public class TrustedCompanion {

Expand All @@ -43,6 +54,7 @@ private TrustedCompanion() {
public static final String ACCEPT_KEY = "acceptKey";
public static final String USER_LOCALE = "userLocale";
public static final String EMAIL_FIELD_NAME = "email";
public static final String DEPENDENT_USER_IDS = "dependentuserids";

/** Note: This path is excluded from security checks, see {@link OtpMiddlewareMain#initializeHttpEndpoints()}. */
public static final String ACCEPT_DEPENDENT_PATH = "api/secure/user/acceptdependent";
Expand Down Expand Up @@ -235,4 +247,58 @@ public static void removeDependent(OtpUser dependent, RelatedUser relatedUser) {
Persistence.otpUsers.replace(user.id, user);
}
}
}

/**
* Retrieve the mobility profile for a dependent providing the requesting user is a trusted companion.
*/
public static List<MobilityProfileLite> getDependentMobilityProfile(Request request, Response response) {
var relatedUser = Auth0Connection.getUserFromRequest(request).otpUser;

if (isEmpty(relatedUser)) {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Related user not provided or unknown.");
}

var dependentUserIds = HttpUtils.getQueryParamFromRequest(request, DEPENDENT_USER_IDS, false);
if (isEmpty(dependentUserIds)) {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Required list of dependent user ids not provided.");
}

var validDependentUserIds = getValidDependents(relatedUser, dependentUserIds);
if (validDependentUserIds.isEmpty()) {
logMessageAndHalt(
request,
HttpStatus.FORBIDDEN_403,
"Related user is not a trusted companion of any provided dependents!"
);
}

if (isNotEmpty(relatedUser) && !validDependentUserIds.isEmpty()) {
List<MobilityProfileLite> profiles = new ArrayList<>();
FindIterable<OtpUser> validDependentUsers = Persistence
.otpUsers
.getFiltered(Filters.in("_id", validDependentUserIds));
validDependentUsers.forEach(user -> profiles.add(new MobilityProfileLite(user)));
return profiles;
}
return Collections.emptyList();
}

/**
* From the list of dependent user ids, extract all that have the related user as their trusted companion.
*/
private static Set<String> getValidDependents(OtpUser relatedUser, String dependentUserIds) {
// In case only one user id is provided with no comma.
String[] userIds = dependentUserIds.contains(",")
? dependentUserIds.split(",")
: new String[] { dependentUserIds };

if (isEmpty(userIds)) {
return Collections.emptySet();
}

return Arrays
.stream(userIds)
.filter(userId -> relatedUser.dependents.contains(userId))
.collect(Collectors.toSet());
}
}
35 changes: 35 additions & 0 deletions src/main/resources/latest-spark-swagger-output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,41 @@ paths:
description: "An error occurred while performing the request. Contact an\
\ API administrator for more information."
examples: {}
/api/secure/user/getdependentmobilityprofile:
get:
tags:
- "api/secure/user"
description: "Retrieve the mobility profile for each valid dependent user id\
\ provided."
parameters: []
responses:
"200":
description: "Successful operation"
examples: {}
schema:
$ref: "#/definitions/MobilityProfileLite"
responseSchema:
$ref: "#/definitions/MobilityProfileLite"
"400":
description: "The request was not formed properly (e.g., some required parameters\
\ may be missing). See the details of the returned response to determine\
\ the exact issue."
examples: {}
"401":
description: "The server was not able to authenticate the request. This\
\ can happen if authentication headers are missing or malformed, or the\
\ authentication server cannot be reached."
examples: {}
"403":
description: "The requesting user is not allowed to perform the request."
examples: {}
"404":
description: "The requested item was not found."
examples: {}
"500":
description: "An error occurred while performing the request. Contact an\
\ API administrator for more information."
examples: {}
/api/secure/user/{id}/verify_sms/{phoneNumber}:
get:
tags:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.opentripplanner.middleware.controllers.api;

import com.auth0.json.mgmt.users.User;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.junit.jupiter.api.AfterAll;
Expand All @@ -9,6 +10,8 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.middleware.models.MobilityProfile;
import org.opentripplanner.middleware.models.MobilityProfileLite;
import org.opentripplanner.middleware.models.OtpUser;
import org.opentripplanner.middleware.models.RelatedUser;
import org.opentripplanner.middleware.persistence.Persistence;
Expand All @@ -22,21 +25,24 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import static org.opentripplanner.middleware.auth.Auth0Connection.restoreDefaultAuthDisabled;
import static org.opentripplanner.middleware.auth.Auth0Connection.setAuthDisabled;
import static org.opentripplanner.middleware.auth.Auth0Users.createAuth0UserForEmail;
import static org.opentripplanner.middleware.testutils.ApiTestUtils.TEMP_AUTH0_USER_PASSWORD;
import static org.opentripplanner.middleware.testutils.ApiTestUtils.getMockHeaders;
import static org.opentripplanner.middleware.testutils.ApiTestUtils.makeGetRequest;
import static org.opentripplanner.middleware.testutils.ApiTestUtils.makeRequest;
import static org.opentripplanner.middleware.testutils.ApiTestUtils.mockAuthenticatedGet;
import static org.opentripplanner.middleware.auth.Auth0Connection.restoreDefaultAuthDisabled;
import static org.opentripplanner.middleware.auth.Auth0Connection.setAuthDisabled;
import static org.opentripplanner.middleware.testutils.PersistenceTestUtils.deleteOtpUser;
import static org.opentripplanner.middleware.tripmonitor.TrustedCompanion.DEPENDENT_USER_IDS;

public class OtpUserControllerTest extends OtpMiddlewareTestEnvironment {
private static final String INITIAL_PHONE_NUMBER = "+15555550222"; // Fake US 555 number.
Expand Down Expand Up @@ -72,7 +78,13 @@ public static void setUp() throws Exception {
dependentUserTwo = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("dependent-two"));
relatedUserThree = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("related-user-three"));
dependentUserThree = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("dependent-three"));

relatedUserFour = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("related-user-four"));

User auth0User = createAuth0UserForEmail(relatedUserFour.email, TEMP_AUTH0_USER_PASSWORD);
relatedUserFour.auth0UserId = auth0User.getId();
Persistence.otpUsers.replace(relatedUserFour.id, relatedUserFour);

dependentUserFour = PersistenceTestUtils.createUser(ApiTestUtils.generateEmailAddress("dependent-four"));
}

Expand Down Expand Up @@ -241,14 +253,8 @@ void canRemoveRelatedUserOnDelete() {
@Test
void canRemoveUserFromRelatedUsersList() throws Exception {
setAuthDisabled(true);
relatedUserFour.dependents.add(dependentUserFour.id);
Persistence.otpUsers.replace(relatedUserFour.id, relatedUserFour);
dependentUserFour.relatedUsers.add(new RelatedUser(
relatedUserFour.email,
RelatedUser.RelatedUserStatus.CONFIRMED,
nickname
));
Persistence.otpUsers.replace(dependentUserFour.id, dependentUserFour);

createTrustedCompanionship(relatedUserFour, dependentUserFour);

// Remove the first related user.
dependentUserFour.relatedUsers.clear();
Expand All @@ -275,4 +281,45 @@ void canRemoveUserFromRelatedUsersList() throws Exception {

setAuthDisabled(false);
}

@Test
void canGetDependentMobilityProfile() throws Exception {
String path = String.format(
"api/secure/user/getdependentmobilityprofile?%s=%s,%s",
DEPENDENT_USER_IDS,
dependentUserThree.id,
dependentUserFour.id
);

HttpResponseValues responseValues = makeGetRequest(path, getMockHeaders(relatedUserFour));
assertEquals(HttpStatus.FORBIDDEN_403, responseValues.status);

var mobilityProfile = new MobilityProfile();
mobilityProfile.mobilityDevices = Set.of("service animal", "electric wheelchair", "white cane");
mobilityProfile.updateMobilityMode();
dependentUserFour.mobilityProfile = mobilityProfile;
dependentUserFour.name = "dependent-user-four-name";

createTrustedCompanionship(relatedUserFour, dependentUserFour);

responseValues = makeGetRequest(path, getMockHeaders(relatedUserFour));
assertEquals(HttpStatus.OK_200, responseValues.status);
List<MobilityProfileLite> mobilityProfileLites = JsonUtils.getPOJOFromJSONAsList(responseValues.responseBody, MobilityProfileLite.class);
assert mobilityProfileLites != null;
assertEquals(new MobilityProfileLite(dependentUserFour), mobilityProfileLites.get(0));
}

/**
* Create trusted companion relationship.
*/
private static void createTrustedCompanionship(OtpUser relatedUser, OtpUser dependentUser) {
relatedUser.dependents.add(dependentUser.id);
Persistence.otpUsers.replace(relatedUser.id, relatedUser);
dependentUser.relatedUsers.add(new RelatedUser(
relatedUser.email,
RelatedUser.RelatedUserStatus.CONFIRMED,
nickname
));
Persistence.otpUsers.replace(dependentUser.id, dependentUser);
}
}

0 comments on commit 5a7b382

Please sign in to comment.