Skip to content

Commit

Permalink
Merge pull request #280 from ibi-group/trip-survey-forward-url
Browse files Browse the repository at this point in the history
Trip survey forward URL
  • Loading branch information
binh-dam-ibigroup authored Dec 17, 2024
2 parents 5369efb + d811757 commit 7d6134f
Show file tree
Hide file tree
Showing 7 changed files with 967 additions and 630 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.opentripplanner.middleware.controllers.api.OtpUserController;
import org.opentripplanner.middleware.controllers.api.TrackedTripController;
import org.opentripplanner.middleware.controllers.api.TripHistoryController;
import org.opentripplanner.middleware.controllers.api.TripSurveyController;
import org.opentripplanner.middleware.docs.PublicApiDocGenerator;
import org.opentripplanner.middleware.models.MonitoredComponent;
import org.opentripplanner.middleware.otp.OtpVersion;
Expand Down Expand Up @@ -120,18 +121,18 @@ private static void initializeHttpEndpoints() throws IOException {
.endpoints(() -> List.of(
new AdminUserController(API_PREFIX),
new ApiUserController(API_PREFIX),
new CDPFilesController(API_PREFIX),
new CDPUserController(API_PREFIX),
new ErrorEventsController(API_PREFIX),
new LogController(API_PREFIX),
new MonitoredComponentController(API_PREFIX),
new MonitoredTripController(API_PREFIX),
new OtpRequestProcessor("/otp", OtpVersion.OTP2),
new OtpRequestProcessor("/otp2", OtpVersion.OTP2),
new OtpUserController(API_PREFIX),
new TrackedTripController(API_PREFIX),
new TripHistoryController(API_PREFIX),
new MonitoredComponentController(API_PREFIX),
new OtpUserController(API_PREFIX),
new LogController(API_PREFIX),
new ErrorEventsController(API_PREFIX),
new CDPFilesController(API_PREFIX),
new OtpRequestProcessor("/otp", OtpVersion.OTP2),
new OtpRequestProcessor("/otp2", OtpVersion.OTP2)
// Add other endpoints as needed.
new TripSurveyController(API_PREFIX)
))
// Spark-swagger auto-generates a swagger document at localhost:4567/doc.yaml.
// (That path is not configurable.)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.opentripplanner.middleware.controllers.api;

import io.github.manusant.ss.SparkSwagger;
import io.github.manusant.ss.rest.Endpoint;
import org.eclipse.jetty.http.HttpStatus;
import org.opentripplanner.middleware.models.MonitoredTrip;
import org.opentripplanner.middleware.models.OtpUser;
import org.opentripplanner.middleware.models.TripSurveyNotification;
import org.opentripplanner.middleware.persistence.Persistence;
import org.opentripplanner.middleware.utils.HttpUtils;
import org.opentripplanner.middleware.utils.JsonUtils;
import spark.Request;
import spark.Response;

import java.time.Instant;
import java.util.Date;
import java.util.Optional;

import static io.github.manusant.ss.descriptor.EndpointDescriptor.endpointPath;
import static io.github.manusant.ss.descriptor.MethodDescriptor.path;
import static org.opentripplanner.middleware.utils.JsonUtils.logMessageAndHalt;
import static org.opentripplanner.middleware.utils.NotificationUtils.TRIP_SURVEY_ID;
import static org.opentripplanner.middleware.utils.NotificationUtils.TRIP_SURVEY_SUBDOMAIN;

public class TripSurveyController implements Endpoint {
private final String ROOT_ROUTE;

private static final String OPEN_PATH = "/open";

public TripSurveyController(String apiPrefix) {
this.ROOT_ROUTE = apiPrefix + "trip-survey";
}

/**
* Register the API endpoint and GET resource that redirects to a survey form.
*/
@Override
public void bind(final SparkSwagger restApi) {
restApi.endpoint(
endpointPath(ROOT_ROUTE).withDescription("Interface for tracking opened trip surveys following a trip survey notification."),
HttpUtils.NO_FILTER
)
.get(
path(ROOT_ROUTE + OPEN_PATH)
.withDescription("Generates a tracking survey link for a specified user, trip, notification ids.")
.withQueryParam().withName("user_id").withRequired(true).withDescription("The id of the OtpUser that this notification applies to.").and()
.withQueryParam().withName("trip_id").withRequired(true).withDescription("The id of the MonitoredTrip that this notification applies to.").and()
.withQueryParam().withName("notification_id").withRequired(true).withDescription("The id of the notification that this notification applies to.").and(),
TripSurveyController::processCall, JsonUtils::toJson
);
}

/**
* Check that the requested survey is valid (user, trip, and notifications point to existing data).
*/
private static OtpUser checkParameters(String userId, String tripId, String notificationId, Request request) {
OtpUser user = Persistence.otpUsers.getById(userId);
if (user == null) {
returnInvalidUrlParametersError(request);
} else {
Optional<TripSurveyNotification> notificationOpt = user.findNotification(notificationId);
if (notificationOpt.isEmpty()) returnInvalidUrlParametersError(request);

MonitoredTrip trip = Persistence.monitoredTrips.getById(tripId);
if (trip == null) returnInvalidUrlParametersError(request);
}

return user;
}

private static void returnInvalidUrlParametersError(Request request) {
logMessageAndHalt(request, HttpStatus.BAD_REQUEST_400, "Invalid URL parameters");
}

/**
* Mark notification as opened, but if an opened date is already populated, do nothing.
*/
private static void updateNotificationStateIfNeeded(OtpUser user, String notificationId) {
TripSurveyNotification notification = user.findNotification(notificationId).orElse(null);
if (notification != null && notification.timeOpened == null) {
notification.timeOpened = Date.from(Instant.now());
Persistence.otpUsers.replace(user.id, user);
}
}

public static String makeTripSurveyUrl(String subdomain, String surveyId, String userId, String tripId, String notificationId) {
// Parameters have been checked before, so there shouldn't be a need to encode parameters.
return String.format(
"https://%s.typeform.com/to/%s#user_id=%s&trip_id=%s&notification_id=%s",
subdomain,
surveyId,
userId,
tripId,
notificationId
);
}

private static boolean processCall(Request req, Response res) {
String userId = req.queryParams("user_id");
String tripId = req.queryParams("trip_id");
String notificationId = req.queryParams("notification_id");

OtpUser user = checkParameters(userId, tripId, notificationId, req);

if (user != null) {
String surveyUrl = makeTripSurveyUrl(
TRIP_SURVEY_SUBDOMAIN,
TRIP_SURVEY_ID,
userId,
tripId,
notificationId
);

// Update notification state
updateNotificationStateIfNeeded(user, notificationId);

// Redirect
res.redirect(surveyUrl);

return true;
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter;
import org.apache.logging.log4j.util.Strings;
import org.opentripplanner.middleware.auth.Auth0Users;
import org.opentripplanner.middleware.auth.RequestingUser;
import org.opentripplanner.middleware.persistence.Persistence;
Expand Down Expand Up @@ -210,4 +211,10 @@ public Optional<TripSurveyNotification> findLastTripSurveyNotificationSent() {
if (tripSurveyNotifications == null) return Optional.empty();
return tripSurveyNotifications.stream().max(Comparator.comparingLong(n -> n.timeSent.getTime()));
}

/** Obtains a notification with the given id, if available. */
public Optional<TripSurveyNotification> findNotification(String id) {
if (tripSurveyNotifications == null || Strings.isBlank(id)) return Optional.empty();
return tripSurveyNotifications.stream().filter(n -> id.equals(n.id)).findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.opentripplanner.middleware.models;

import java.util.Date;
import java.util.UUID;

/** Contains information regarding survey notifications sent after a trip is completed. */
public class TripSurveyNotification {
Expand All @@ -17,6 +16,9 @@ public class TripSurveyNotification {
/** Date/time when the trip survey notification was sent. */
public Date timeSent;

/** Date/time when the trip survey notification was opened. */
public Date timeOpened;

/** The {@link TrackedJourney} (and, indirectly, the {@link MonitoredTrip}) that this notification refers to. */
public String journeyId;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ public class NotificationUtils {
public static final String OTP_ADMIN_DASHBOARD_FROM_EMAIL = getConfigPropertyAsText("OTP_ADMIN_DASHBOARD_FROM_EMAIL");
private static final String PUSH_API_KEY = getConfigPropertyAsText("PUSH_API_KEY");
private static final String PUSH_API_URL = getConfigPropertyAsText("PUSH_API_URL");
private static final String TRIP_SURVEY_ID = getConfigPropertyAsText("TRIP_SURVEY_ID");
private static final String TRIP_SURVEY_SUBDOMAIN = getConfigPropertyAsText("TRIP_SURVEY_SUBDOMAIN");
public static final String TRIP_SURVEY_ID = getConfigPropertyAsText("TRIP_SURVEY_ID");
public static final String TRIP_SURVEY_SUBDOMAIN = getConfigPropertyAsText("TRIP_SURVEY_SUBDOMAIN");
private static final String OTP_UI_NAME = ConfigUtils.getConfigPropertyAsText("OTP_UI_NAME");
private static final String OTP_UI_URL = ConfigUtils.getConfigPropertyAsText("OTP_UI_URL");
private static final String TRIPS_PATH = ACCOUNT_PATH + "/trips";
Expand Down
Loading

0 comments on commit 7d6134f

Please sign in to comment.