/* * Copyright (C) 2014 SCVNGR, Inc. d/b/a LevelUp * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.scvngr.levelup.core.net.request.factory; import android.content.Context; import android.location.Location; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.scvngr.levelup.core.annotation.AccessTokenRequired; import com.scvngr.levelup.core.annotation.LevelUpApi; import com.scvngr.levelup.core.annotation.LevelUpApi.Contract; import com.scvngr.levelup.core.annotation.RequiresPermission; import com.scvngr.levelup.core.annotation.VisibleForTesting; import com.scvngr.levelup.core.annotation.VisibleForTesting.Visibility; import com.scvngr.levelup.core.net.AbstractRequest; import com.scvngr.levelup.core.net.AccessTokenRetriever; import com.scvngr.levelup.core.net.HttpMethod; import com.scvngr.levelup.core.net.JSONObjectRequestBody; import com.scvngr.levelup.core.net.JsonElementRequestBody; import com.scvngr.levelup.core.net.LevelUpRequest; import com.scvngr.levelup.core.net.Permissions; import com.scvngr.levelup.core.net.RequestUtils; import com.scvngr.levelup.core.util.LogManager; import com.scvngr.levelup.core.util.NullUtils; import com.scvngr.levelup.core.util.PreconditionUtil; import com.google.gson.JsonObject; import net.jcip.annotations.Immutable; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.List; /** * <p> * Class to build requests to interact with the endpoints that deal with * {@link com.scvngr.levelup.core.model.User}s. * </p> * <p> * If you're looking to login, see {@link AccessTokenRequestFactory}. * </p> */ @Immutable @LevelUpApi(contract = Contract.PUBLIC) public final class UserRequestFactory extends AbstractRequestFactory { @LevelUpApi(contract = Contract.INTERNAL) @NonNull private static final String FACEBOOK_CONNECTION_ENDPOINT = "facebook_connection"; @LevelUpApi(contract = Contract.INTERNAL) @NonNull private static final String USERS_ENDPOINT = "users"; /** * Outer parameter name for user parameters. */ @NonNull public static final String OUTER_PARAM_USER = "user"; /** * Outer parameter name for permission keynames parameters. */ @NonNull public static final String OUTER_PARAM_PERMISSION_KEYNAMES = "permission_keynames"; /** * User Parameter for longitude, represented as a floating point number. */ @NonNull public static final String PARAM_LONGITUDE = "lng"; /** * User Parameter for latitude, represented as a floating point number. */ @NonNull public static final String PARAM_LATITUDE = "lat"; /** * User Parameter for password. */ @NonNull public static final String PARAM_PASSWORD = "password"; /** * User Parameter for new password. */ @NonNull public static final String PARAM_NEW_PASSWORD = "new_password"; /** * User Parameter for new password confirmation. */ @NonNull public static final String PARAM_NEW_PASSWORD_CONFIRMATION = "new_password_confirmation"; /** * User Parameter for email. */ @NonNull public static final String PARAM_EMAIL = "email"; /** * User Parameter for last name. */ @NonNull public static final String PARAM_LAST_NAME = "last_name"; /** * User Parameter for first name. */ @NonNull public static final String PARAM_FIRST_NAME = "first_name"; /** * User Parameter for if they have accepted the terms of service. */ @NonNull public static final String PARAM_TERMS_ACCEPTED = "terms_accepted"; /** * User Parameter for birth date. */ @NonNull public static final String PARAM_BORN_AT = "born_at"; /** * User Parameter for gender. */ @NonNull public static final String PARAM_GENDER = "gender"; /** * User Parameter for custom attributes. */ @NonNull public static final String PARAM_CUSTOM_ATTRIBUTES = "custom_attributes"; /** * Parameter for Facebook access token during Facebook connect. */ @LevelUpApi(contract = Contract.INTERNAL) @NonNull @VisibleForTesting(visibility = Visibility.PRIVATE) /* package */static final String PARAM_FACEBOOK_ACCESS_TOKEN = "facebook_access_token"; /** * @param context the Application context. * @param retriever the implementation of {@link AccessTokenRetriever} to use to get the User's * {@link com.scvngr.levelup.core.model.AccessToken} if needed. */ public UserRequestFactory(@NonNull final Context context, @Nullable final AccessTokenRetriever retriever) { super(context, retriever); } /** * Get a request that will allow the user to reset their password. * * @param email the email address they signed up with. * @return {@link AbstractRequest} to use to reset the password for the user. */ @NonNull @LevelUpApi(contract = Contract.ENTERPRISE) public AbstractRequest buildForgotPasswordRequest(@NonNull final String email) { final JsonObject resetRequest = new JsonObject(); resetRequest.addProperty("email", email); return new LevelUpRequest(getContext(), HttpMethod.POST, LevelUpRequest.API_VERSION_CODE_V14, "passwords", null, new JsonElementRequestBody(resetRequest)); } /** * Builds a request to connect a user to Facebook. * * @param facebookAccessToken the Facebook access token to use to connect the user. * @return {@link AbstractRequest} to use to connect the user to Facebook. */ @NonNull @LevelUpApi(contract = Contract.INTERNAL) @AccessTokenRequired public AbstractRequest buildFacebookConnectRequest(@NonNull final String facebookAccessToken) { PreconditionUtil.assertNotNull(facebookAccessToken, "facebookAccessToken"); final JSONObject object = new JSONObject(); final JSONObject userObject = new JSONObject(); try { userObject.put(PARAM_FACEBOOK_ACCESS_TOKEN, facebookAccessToken); object.put(OUTER_PARAM_USER, userObject); } catch (final JSONException e) { LogManager.e("JSONException building register request", e); } return new LevelUpRequest(getContext(), HttpMethod.POST, LevelUpRequest.API_VERSION_CODE_V14, FACEBOOK_CONNECTION_ENDPOINT, null, new JSONObjectRequestBody(object), getAccessTokenRetriever()); } /** * Builds a request to disconnect a user from Facebook. * * @return {@link AbstractRequest} to use to disconnect the user from Facebook. */ @NonNull @LevelUpApi(contract = Contract.INTERNAL) @AccessTokenRequired public AbstractRequest buildFacebookDisconnectRequest() { return new LevelUpRequest(getContext(), HttpMethod.DELETE, LevelUpRequest.API_VERSION_CODE_V14, FACEBOOK_CONNECTION_ENDPOINT, null, null, getAccessTokenRetriever()); } /** * Build a request to create a new user. Will also get the device UUID and append it to the * request. * * @param firstName user's first name. * @param lastName user's last name. * @param email user's email address. * @param password user's new password. * @param location Android Location for the user (or null if none was found). * @return {@link AbstractRequest} representing the register request. */ @NonNull @LevelUpApi(contract = Contract.ENTERPRISE) public final AbstractRequest buildRegisterRequest(@NonNull final String firstName, @NonNull final String lastName, @NonNull final String email, @NonNull final String password, @Nullable final Location location) { PreconditionUtil.assertNotNull(firstName, "firstName"); PreconditionUtil.assertNotNull(lastName, "lastName"); PreconditionUtil.assertNotNull(email, "email"); PreconditionUtil.assertNotNull(password, "password"); final JSONObject object = new JSONObject(); final JSONObject userObject = new JSONObject(); try { final Context context = getContext(); RequestUtils.addApiKeyToRequestBody(context, object); userObject.put(PARAM_FIRST_NAME, firstName); userObject.put(PARAM_LAST_NAME, lastName); userObject.put(PARAM_EMAIL, email); userObject.put(PARAM_TERMS_ACCEPTED, true); userObject.put(PARAM_PASSWORD, password); RequestUtils.addDeviceIdToRequestBody(context, userObject); if (null != location) { userObject.put(PARAM_LATITUDE, location.getLatitude()); userObject.put(PARAM_LONGITUDE, location.getLongitude()); } object.put(OUTER_PARAM_USER, userObject); } catch (final JSONException e) { LogManager.e("JSONException building register request", e); } return new LevelUpRequest(getContext(), HttpMethod.POST, LevelUpRequest.API_VERSION_CODE_V14, USERS_ENDPOINT, null, new JSONObjectRequestBody(object)); } /** * Build a request to create a new user. * * @param firstName user's first name. * @param lastName user's last name. * @param email user's email address. * @param permissions array of permissions to grant the new user * @return {@link AbstractRequest} representing the register request. */ @NonNull @LevelUpApi(contract = Contract.PUBLIC) public final AbstractRequest buildRegisterRequest(@NonNull final String firstName, @NonNull final String lastName, @NonNull final String email, @NonNull List<String> permissions) { PreconditionUtil.assertNotNull(firstName, "firstName"); PreconditionUtil.assertNotNull(lastName, "lastName"); PreconditionUtil.assertNotNull(email, "email"); PreconditionUtil.assertNotNull(permissions, "permissions"); final JSONObject object = new JSONObject(); final JSONObject userObject = new JSONObject(); final JSONArray permissionArray = new JSONArray(); try { RequestUtils.addApiKeyToRequestBody(getContext(), object); userObject.put(PARAM_FIRST_NAME, firstName); userObject.put(PARAM_LAST_NAME, lastName); userObject.put(PARAM_EMAIL, email); userObject.put(PARAM_TERMS_ACCEPTED, true); for (final String permission : permissions) { permissionArray.put(permission); } object.put(OUTER_PARAM_USER, userObject); object.put(OUTER_PARAM_PERMISSION_KEYNAMES, permissionArray); } catch (final JSONException e) { LogManager.e("JSONException building register request", e); } return new LevelUpRequest(getContext(), HttpMethod.POST, LevelUpRequest.API_VERSION_CODE_V15, USERS_ENDPOINT, null, new JSONObjectRequestBody(object)); } /** * Build a request to register a new User via a facebook access token. * * @param facebookAccessToken the facebook access token to use to register the new user. * @return the {@link AbstractRequest} to use to register the new user. */ @NonNull @LevelUpApi(contract = Contract.INTERNAL) public AbstractRequest buildFacebookRegisterRequest(@NonNull final String facebookAccessToken) { final JSONObject object = new JSONObject(); final JSONObject userObject = new JSONObject(); try { RequestUtils.addApiKeyToRequestBody(getContext(), object); userObject.put(PARAM_FACEBOOK_ACCESS_TOKEN, facebookAccessToken); object.put(OUTER_PARAM_USER, userObject); } catch (final JSONException e) { LogManager.e("JSONException building facebook register request", e); } return new LevelUpRequest(getContext(), HttpMethod.POST, LevelUpRequest.API_VERSION_CODE_V14, USERS_ENDPOINT, null, new JSONObjectRequestBody(object)); } /** * Build a request to retrieve the User information. * * @return the request to retrieve the user information. */ @NonNull @AccessTokenRequired @LevelUpApi(contract = Contract.PUBLIC) @RequiresPermission({Permissions.PERMISSION_READ_USER_BASIC_INFO}) public AbstractRequest buildGetUserInfoRequest() { return new LevelUpRequest(getContext(), HttpMethod.GET, LevelUpRequest.API_VERSION_CODE_V15, "users", null, null, NullUtils.nonNullContract(getAccessTokenRetriever())); } /** * Builder to create a request that updates the User's information. */ @LevelUpApi(contract = Contract.ENTERPRISE) public static final class UserInfoRequestBuilder { @NonNull private static final String USERS_ID_ENDPOINT = "users"; /** * The Application context. A context is required to build any {@link LevelUpRequest}. */ @NonNull private final Context mContext; /** * The implementation of {@link AccessTokenRetriever} to use to get the User's * {@link com.scvngr.levelup.core.model.AccessToken} if needed. */ @NonNull private final AccessTokenRetriever mAccessTokenRetriever; /** * The request JSON body. */ @NonNull private final JSONObject mParams = new JSONObject(); /** * The JSON object for custom attributes. */ @NonNull private final JSONObject mCustomAttributes = new JSONObject(); /** * Creates a new builder for a {@link UserRequestFactory} request. * * @param context The Application context. * @param retriever the implementation of {@link AccessTokenRetriever} to use to get the * User's {@link com.scvngr.levelup.core.model.AccessToken} if needed. */ public UserInfoRequestBuilder(@NonNull final Context context, @NonNull final AccessTokenRetriever retriever) { mContext = context; mAccessTokenRetriever = retriever; } /** * Builds the request which updates the User's information. * * @return The {@link AbstractRequest} which updates the User's information. */ @NonNull @AccessTokenRequired @LevelUpApi(contract = Contract.ENTERPRISE) public AbstractRequest build() { return new LevelUpRequest(mContext, HttpMethod.PUT, LevelUpRequest.API_VERSION_CODE_V15, USERS_ID_ENDPOINT, null, new JSONObjectRequestBody(getParams()), mAccessTokenRetriever); } /** * Helper method to get the full JSON object to post. * * @return the full {@link JSONObject} to post to the server. */ @NonNull private JSONObject getParams() { if (0 < mCustomAttributes.length()) { try { mParams.put(PARAM_CUSTOM_ATTRIBUTES, mCustomAttributes); } catch (final JSONException e) { LogManager.e("JSONException when adding custom attributes to body", e); } } // Nest the parameters in the outer 'user' field. final JSONObject requestParameters = new JSONObject(); try { requestParameters.put(OUTER_PARAM_USER, mParams); } catch (final JSONException e) { LogManager.e("JSONException when building the user update request", e); } LogManager.v("Building user update request with parameters %s", requestParameters); return requestParameters; } /** * Adds the User's born at date information. An empty or null attribute value removes the * attribute's key from the pending update request. * * @param bornAt The born at date as an ISO date time. * @return The {@link UserInfoRequestBuilder} for chaining convenience. */ @NonNull public UserInfoRequestBuilder withBornAt(@Nullable final String bornAt) { if (null != bornAt) { setParam(PARAM_BORN_AT, bornAt); } return this; } /** * Adds the User's custom attributes. An empty or null custom attribute value removes the * custom attribute's key from the pending update request. The request is not modified if * the custom attribute set is null. * * @param key The custom attribute key. * @param value The custom attribute value. * @return The {@link UserInfoRequestBuilder} for chaining convenience. */ @NonNull public UserInfoRequestBuilder withCustomAttribute(@NonNull final String key, @Nullable final String value) { try { mCustomAttributes.put(key, value); } catch (final JSONException e) { LogManager.e("JSONException when adding custom attribute", e); } return this; } /** * Adds the User's email address. An empty or null attribute value removes the attribute's * key from the pending update request. * * @param email The User's email address. * @return The {@link UserInfoRequestBuilder} for chaining convenience. */ @NonNull public UserInfoRequestBuilder withEmail(@Nullable final String email) { setParam(PARAM_EMAIL, email); return this; } /** * Adds the User's first name. An empty or null attribute value removes the attribute's key * from the pending update request. * * @param firstName The User's first name. * @return The {@link UserInfoRequestBuilder} for chaining convenience. */ @NonNull public UserInfoRequestBuilder withFirstName(@Nullable final String firstName) { setParam(PARAM_FIRST_NAME, firstName); return this; } /** * Adds the User's gender information. An empty or null attribute value removes the * attribute's key from the pending update request. * * @param gender The gender string generated by * {@link com.scvngr.levelup.core.model.User.Gender#toString()} . * @return The {@link UserInfoRequestBuilder} for chaining convenience. */ @NonNull public UserInfoRequestBuilder withGender(@Nullable final String gender) { setParam(PARAM_GENDER, gender); return this; } /** * Adds the User's last name. An empty or null attribute value removes the attribute's key * from the pending update request. * * @param lastName The User's last name. * @return The {@link UserInfoRequestBuilder} for chaining convenience. */ @NonNull public UserInfoRequestBuilder withLastName(@Nullable final String lastName) { setParam(PARAM_LAST_NAME, lastName); return this; } /** * Adds the User's new password. An empty or null attribute value removes the attribute's * key from the pending update request. * * @param newPassword The User's new password or null if the password is not being modified. * @return The {@link UserInfoRequestBuilder} for chaining convenience. */ @NonNull public UserInfoRequestBuilder withNewPassword(@Nullable final String newPassword) { setParam(PARAM_NEW_PASSWORD, newPassword); return this; } /** * Sets a pending request parameter. * * @param key The key of the parameter. * @param value The value of the parameter. */ private void setParam(@NonNull final String key, @Nullable final String value) { // We do not allow nulls, but allow empty strings. if (null != value) { try { mParams.put(key, value); } catch (final JSONException e) { LogManager.e("JSONException when adding key(%s) to body", key, e); } } } } }