diff --git a/README.md b/README.md
index bbd4d2f9..7085ebcd 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,7 @@ This repository contains a shim server, a shim server UI, and shims for third-pa
* [Jawbone UP](https://jawbone.com/up/developer)
* [RunKeeper](http://developer.runkeeper.com/healthgraph) ([application management portal](http://runkeeper.com/partner))
* [Withings](http://oauth.withings.com/api)
+* [Strava](http://strava.github.io/api/)
The above links point to the developer website of each API. You'll need to visit these websites to register your
application and obtain authentication credentials for each of the shims you want to enable.
@@ -238,6 +239,9 @@ The currently supported shims are
* [omh:step-count](http://www.openmhealth.org/developers/schemas/#step-count)
* sleep
* [omh:sleep-duration](http://www.openmhealth.org/developers/schemas/#sleep-duration)
+* strava
+ * activity
+ * [omh:physical-activity](http://www.openmhealth.org/developers/schemas/#physical-activity)
You can learn more about these shims and endpoints on the Open mHealth [developer site](http://www.openmhealth.org/developers/getting-started/).
diff --git a/shim-server-ui/app/styles/main.css b/shim-server-ui/app/styles/main.css
index 8211e88b..b9e48ceb 100644
--- a/shim-server-ui/app/styles/main.css
+++ b/shim-server-ui/app/styles/main.css
@@ -122,6 +122,10 @@ body {
padding-left: 10px;
}
+.connected-row .glyphicon-calendar {
+ padding-left: 0px;
+}
+
.sublink {
margin-right: 30px;
margin-bottom: 10px;
diff --git a/shim-server-ui/app/views/main.html b/shim-server-ui/app/views/main.html
index 863e6f9d..f6652d8f 100644
--- a/shim-server-ui/app/views/main.html
+++ b/shim-server-ui/app/views/main.html
@@ -79,117 +79,119 @@
-
-
-
-
- {{shim.label}}
-
-
-
-
-
+
+
-
+
+
{{shim.label}}
-
-
-
-
+
-
-
-
-
-
-
-
-
diff --git a/shim-server/src/main/java/org/openmhealth/shim/ShimRegistryImpl.java b/shim-server/src/main/java/org/openmhealth/shim/ShimRegistryImpl.java
index ed7d732b..ced3ee3a 100644
--- a/shim-server/src/main/java/org/openmhealth/shim/ShimRegistryImpl.java
+++ b/shim-server/src/main/java/org/openmhealth/shim/ShimRegistryImpl.java
@@ -28,6 +28,8 @@
import org.openmhealth.shim.runkeeper.RunkeeperShim;
import org.openmhealth.shim.withings.WithingsConfig;
import org.openmhealth.shim.withings.WithingsShim;
+import org.openmhealth.shim.strava.StravaConfig;
+import org.openmhealth.shim.strava.StravaShim;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@@ -71,6 +73,9 @@ public class ShimRegistryImpl implements ShimRegistry {
@Autowired
private WithingsConfig withingsConfig;
+ @Autowired
+ private StravaConfig stravaConfig;
+
private LinkedHashMap registryMap;
public ShimRegistryImpl() {
@@ -112,6 +117,12 @@ public void init() {
new HealthvaultShim(
authParametersRepo, shimServerConfig, healthvaultConfig));
}
+
+ if (stravaConfig.getClientId() != null) {
+ registryMap.put(StravaShim.SHIM_KEY,
+ new StravaShim(
+ authParametersRepo, accessParametersRepo, shimServerConfig, stravaConfig));
+ }
}
@Override
@@ -135,7 +146,8 @@ public List getAvailableShims() {
new RunkeeperShim(null, null, null, runkeeperConfig),
new WithingsShim(null, null, withingsConfig),
new HealthvaultShim(null, null, healthvaultConfig),
- new FitbitShim(null, null, fitbitConfig)
+ new FitbitShim(null, null, fitbitConfig),
+ new StravaShim(null, null, null, stravaConfig)
);
}
diff --git a/shim-server/src/main/java/org/openmhealth/shim/strava/StravaConfig.java b/shim-server/src/main/java/org/openmhealth/shim/strava/StravaConfig.java
new file mode 100644
index 00000000..f189aac2
--- /dev/null
+++ b/shim-server/src/main/java/org/openmhealth/shim/strava/StravaConfig.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 Open mHealth
+ *
+ * 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 org.openmhealth.shim.strava;
+
+import org.openmhealth.shim.ApplicationAccessParameters;
+import org.openmhealth.shim.ApplicationAccessParametersRepo;
+import org.openmhealth.shim.ShimConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Pedro Sampaio
+ */
+@Component
+@ConfigurationProperties(prefix = "openmhealth.shim.strava")
+public class StravaConfig implements ShimConfig {
+
+ private String clientId;
+
+ private String clientSecret;
+
+ @Autowired
+ private ApplicationAccessParametersRepo applicationParametersRepo;
+
+ public String getClientId() {
+ ApplicationAccessParameters parameters =
+ applicationParametersRepo.findByShimKey(StravaShim.SHIM_KEY);
+ return parameters != null ? parameters.getClientId() : clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getClientSecret() {
+ ApplicationAccessParameters parameters =
+ applicationParametersRepo.findByShimKey(StravaShim.SHIM_KEY);
+ return parameters != null ? parameters.getClientSecret() : clientSecret;
+ }
+
+ public void setClientSecret(String clientSecret) {
+ this.clientSecret = clientSecret;
+ }
+}
diff --git a/shim-server/src/main/java/org/openmhealth/shim/strava/StravaShim.java b/shim-server/src/main/java/org/openmhealth/shim/strava/StravaShim.java
new file mode 100644
index 00000000..460b0888
--- /dev/null
+++ b/shim-server/src/main/java/org/openmhealth/shim/strava/StravaShim.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2014 Open mHealth
+ *
+ * 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 org.openmhealth.shim.strava;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.jayway.jsonpath.JsonPath;
+import net.minidev.json.JSONObject;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.openmhealth.schema.pojos.Activity;
+import org.openmhealth.schema.pojos.BodyWeight;
+import org.openmhealth.schema.pojos.build.ActivityBuilder;
+import org.openmhealth.schema.pojos.build.BodyWeightBuilder;
+import org.openmhealth.schema.pojos.generic.DurationUnitValue;
+import org.openmhealth.schema.pojos.generic.MassUnitValue;
+import org.openmhealth.shim.*;
+import org.springframework.http.*;
+import org.springframework.security.oauth2.client.OAuth2RestOperations;
+import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
+import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
+import org.springframework.security.oauth2.client.token.AccessTokenRequest;
+import org.springframework.security.oauth2.client.token.RequestEnhancer;
+import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+import java.io.IOException;
+import java.util.*;
+
+import static org.openmhealth.schema.pojos.generic.LengthUnitValue.LengthUnit.m;
+
+/**
+ * Encapsulates parameters specific to strava api.
+ *
+ * @author Pedro Sampaio
+ */
+public class StravaShim extends OAuth2ShimBase {
+
+ public static final String SHIM_KEY = "strava";
+
+ private static final String DATA_URL = "https://www.strava.com/api/v3/";
+
+ private static final String AUTHORIZE_URL = "https://www.strava.com/oauth/authorize";
+
+ private static final String TOKEN_URL = "https://www.strava.com/oauth/token";
+
+ private StravaConfig config;
+
+ public static final ArrayList STRAVA_SCOPES =
+ new ArrayList(Arrays.asList("public"));
+
+ public StravaShim(AuthorizationRequestParametersRepo authorizationRequestParametersRepo,
+ AccessParametersRepo accessParametersRepo,
+ ShimServerConfig shimServerConfig1,
+ StravaConfig stravaConfig) {
+ super(authorizationRequestParametersRepo, accessParametersRepo, shimServerConfig1);
+ this.config = stravaConfig;
+ }
+
+ @Override
+ public String getLabel() {
+ return "Strava";
+ }
+
+ @Override
+ public String getShimKey() {
+ return SHIM_KEY;
+ }
+
+ @Override
+ public String getClientSecret() {
+ return config.getClientSecret();
+ }
+
+ @Override
+ public String getClientId() {
+ return config.getClientId();
+ }
+
+ @Override
+ public String getBaseAuthorizeUrl() {
+ return AUTHORIZE_URL;
+ }
+
+ @Override
+ public String getBaseTokenUrl() {
+ return TOKEN_URL;
+ }
+
+ @Override
+ public List getScopes() {
+ return STRAVA_SCOPES;
+ }
+
+ public AuthorizationCodeAccessTokenProvider getAuthorizationCodeAccessTokenProvider() {
+ AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider();
+ provider.setTokenRequestEnhancer(new StravaTokenRequestEnhancer());
+ return provider;
+ }
+
+ @Override
+ public ShimDataRequest getTriggerDataRequest() {
+ ShimDataRequest shimDataRequest = new ShimDataRequest();
+ shimDataRequest.setDataTypeKey(StravaDataTypes.ACTIVITY.toString());
+ shimDataRequest.setNumToReturn(1l);
+ return shimDataRequest;
+ }
+
+ @Override
+ public ShimDataType[] getShimDataTypes() {
+ return new StravaDataTypes[]{
+ StravaDataTypes.ACTIVITY};
+ }
+
+ //Example: 2014-11-22T22:11:00Z
+ private static DateTimeFormatter dateFormatter = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z");
+
+ public enum StravaDataTypes implements ShimDataType {
+
+ ACTIVITY("athlete/activities", new JsonDeserializer() {
+ @Override
+ public ShimDataResponse deserialize(JsonParser jsonParser,
+ DeserializationContext deserializationContext)
+ throws IOException {
+ JsonNode responseNode = jsonParser.getCodec().readTree(jsonParser);
+ String rawJson = responseNode.toString();
+
+ List activities = new ArrayList<>();
+
+ JsonPath workoutsPath = JsonPath.compile("$.[*]");
+ List