Refactoring

This commit is contained in:
2026-03-23 10:20:39 +02:00
parent 1d7a5b2b5c
commit 38eaa49986
52 changed files with 231 additions and 208 deletions

9
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/peakup-backend/peakup-backend.iml" filepath="$PROJECT_DIR$/peakup-backend/peakup-backend.iml" />
<module fileurl="file://$PROJECT_DIR$/peakup-frontend/peakup-frontend.iml" filepath="$PROJECT_DIR$/peakup-frontend/peakup-frontend.iml" />
</modules>
</component>
</project>

View File

@@ -1,6 +1,6 @@
package com.peakup.activity.connector;
import com.peakup.connector.intervals.IntervalsClient;
import com.peakup.connector.intervals.client.IntervalsClient;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.rest.client.inject.RestClient;

View File

@@ -1,7 +1,7 @@
package com.peakup.activity.connector;
import com.peakup.connector.strava.StravaClient;
import com.peakup.connector.strava.StravaContext;
import com.peakup.connector.strava.client.StravaClient;
import com.peakup.connector.strava.client.StravaContext;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

View File

@@ -4,7 +4,7 @@ import com.peakup.activity.model.AbstractActivity_;
import com.peakup.activity.model.IntervalsActivity;
import com.peakup.activity.model.IntervalsActivity_;
import com.peakup.activity.model.StreamStatusEnum;
import com.peakup.dao.AbstractDAO;
import com.peakup.common.dao.AbstractDAO;
import com.peakup.model.AbstractEntity_;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;

View File

@@ -4,7 +4,7 @@ import com.peakup.activity.model.AbstractActivity_;
import com.peakup.activity.model.IntervalsEnrichmentStatusEnum;
import com.peakup.activity.model.StravaActivity;
import com.peakup.activity.model.StravaActivity_;
import com.peakup.dao.AbstractDAO;
import com.peakup.common.dao.AbstractDAO;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.criteria.CriteriaBuilder;

View File

@@ -1,7 +1,7 @@
package com.peakup.activity.importer;
import com.peakup.activity.model.AbstractActivity;
import com.peakup.dao.AbstractDAO;
import com.peakup.common.dao.AbstractDAO;
import jakarta.annotation.Nullable;
import jakarta.transaction.Transactional;

View File

@@ -1,7 +0,0 @@
package com.peakup.activity.importer;
import com.peakup.common.PeakUpException;
public class ImportException extends PeakUpException {
public ImportException(String msg) { super(msg); }
}

View File

@@ -7,8 +7,9 @@ import com.peakup.activity.dao.ActivityMapStreamDAO;
import com.peakup.activity.dao.ActivityStreamDAO;
import com.peakup.activity.dao.IntervalsActivityDAO;
import com.peakup.activity.model.*;
import com.peakup.connector.intervals.IntervalsClient;
import com.peakup.dao.AbstractDAO;
import com.peakup.common.ImportException;
import com.peakup.common.dao.AbstractDAO;
import com.peakup.connector.intervals.client.IntervalsClient;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@@ -331,7 +332,7 @@ public class IntervalsActivityImporter
return true;
}
private void tryImportStreams(IntervalsActivity activity) {
private void importStreams(IntervalsActivity activity) {
if (activity.getId() == null) return;
try {
IntervalsClient.StreamsData streams = intervalsActivityConnector.getStreams(activity.originId);
@@ -365,7 +366,7 @@ public class IntervalsActivityImporter
@Transactional
public void importStreamForActivity(Long activityId) {
IntervalsActivity activity = intervalsActivityDAO.getById(activityId);
tryImportStreams(activity);
importStreams(activity);
intervalsActivityDAO.update(activity);
}
@@ -472,12 +473,12 @@ public class IntervalsActivityImporter
p.setHrvMs(avg);
}
}
if (streams.respiration != null) p.setRespirationBrpm(streams.respiration.get(i));
if (streams.fixed_altitude != null) p.setFixedAltitudeM(streams.fixed_altitude.get(i));
if (streams.left_pedal_smoothness != null) p.setLeftPedalSmoothnessPct(streams.left_pedal_smoothness.get(i));
if (streams.right_pedal_smoothness != null) p.setRightPedalSmoothnessPct(streams.right_pedal_smoothness.get(i));
if (streams.respiration != null) p.setRespirationBrpm(streams.respiration.get(i));
if (streams.fixed_altitude != null) p.setFixedAltitudeM(streams.fixed_altitude.get(i));
if (streams.left_pedal_smoothness != null) p.setLeftPedalSmoothnessPct(streams.left_pedal_smoothness.get(i));
if (streams.right_pedal_smoothness != null) p.setRightPedalSmoothnessPct(streams.right_pedal_smoothness.get(i));
if (streams.left_torque_effectiveness != null) p.setLeftTorqueEffectivenessPct(streams.left_torque_effectiveness.get(i));
if (streams.right_torque_effectiveness != null)p.setRightTorqueEffectivenessPct(streams.right_torque_effectiveness.get(i));
if (streams.right_torque_effectiveness != null) p.setRightTorqueEffectivenessPct(streams.right_torque_effectiveness.get(i));
points.add(p);
}

View File

@@ -4,9 +4,9 @@ import com.peakup.activity.connector.StravaActivityConnector;
import com.peakup.activity.dao.StravaActivityDAO;
import com.peakup.activity.model.IntervalsEnrichmentStatusEnum;
import com.peakup.activity.model.StravaActivity;
import com.peakup.connector.strava.StravaClient;
import com.peakup.connector.strava.StravaContext;
import com.peakup.dao.AbstractDAO;
import com.peakup.common.dao.AbstractDAO;
import com.peakup.connector.strava.client.StravaClient;
import com.peakup.connector.strava.client.StravaContext;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

View File

@@ -1,6 +1,6 @@
package com.peakup.activity.model;
import com.peakup.model.AbstractTimestampedEntity;
import com.peakup.common.model.AbstractTimestampedEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Inheritance;

View File

@@ -1,12 +1,12 @@
package com.peakup.activity.model;
import com.peakup.common.model.AbstractTimescaleEntity;
import com.peakup.common.model.DependentEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import com.peakup.model.AbstractTimescaleEntity;
import com.peakup.model.DependentEntity;
import java.io.Serializable;
import java.time.Instant;

View File

@@ -1,13 +1,13 @@
package com.peakup.activity.model;
import com.peakup.common.model.AbstractTimescaleEntity;
import com.peakup.common.model.DependentEntity;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import com.peakup.model.AbstractTimescaleEntity;
import com.peakup.model.DependentEntity;
import java.io.Serializable;
import java.time.Instant;

View File

@@ -1,6 +1,6 @@
package com.peakup.activity.model;
import com.peakup.dao.PersistableEnum;
import com.peakup.common.dao.PersistableEnum;
public enum ActivityTypeEnum implements PersistableEnum<ActivityTypeEnum> {
RIDE(1),

View File

@@ -1,6 +1,6 @@
package com.peakup.activity.model;
import com.peakup.dao.PersistableEnum;
import com.peakup.common.dao.PersistableEnum;
public enum IntervalsEnrichmentStatusEnum implements PersistableEnum<IntervalsEnrichmentStatusEnum> {
PENDING(0),

View File

@@ -1,6 +1,6 @@
package com.peakup.activity.model;
import com.peakup.dao.AbstractEnumConverter;
import com.peakup.common.dao.AbstractEnumConverter;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;

View File

@@ -1,14 +1,18 @@
package com.peakup.activity.model;
import com.peakup.dao.PersistableEnum;
import com.peakup.common.dao.PersistableEnum;
public enum StreamStatusEnum implements PersistableEnum<StreamStatusEnum> {
PENDING(0), IMPORTED(1), NO_STREAM(2), ERROR(3);
private final int key;
StreamStatusEnum(int key) { this.key = key; }
StreamStatusEnum(int key) {
this.key = key;
}
@Override
public Integer getKey() { return key; }
public Integer getKey() {
return key;
}
}

View File

@@ -2,7 +2,7 @@ package com.peakup.activity.service;
import com.peakup.activity.dao.IntervalsActivityDAO;
import com.peakup.activity.model.IntervalsActivity;
import com.peakup.service.AbstractService;
import com.peakup.common.service.AbstractService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

View File

@@ -2,7 +2,7 @@ package com.peakup.activity.service;
import com.peakup.activity.dao.StravaActivityDAO;
import com.peakup.activity.model.StravaActivity;
import com.peakup.service.AbstractService;
import com.peakup.common.service.AbstractService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

View File

@@ -1,8 +1,7 @@
package com.peakup.athlete.dao;
import com.peakup.athlete.model.Athlete;
import com.peakup.dao.AbstractDAO;
import com.peakup.dao.AbstractTimestampedDAO;
import com.peakup.common.dao.AbstractTimestampedDAO;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped

View File

@@ -1,6 +1,7 @@
package com.peakup.athlete.model;
import com.peakup.model.AbstractTimestampedEntity;
import com.peakup.common.dao.AbstractEnumConverter;
import com.peakup.common.model.AbstractTimestampedEntity;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
@@ -24,7 +25,7 @@ public class Athlete extends AbstractTimestampedEntity {
@Column(nullable = false)
private Double weight;
@Convert(converter = com.peakup.dao.AbstractEnumConverter.SexEnumConverter.class)
@Convert(converter = AbstractEnumConverter.SexEnumConverter.class)
private @Nullable SexEnum sex;
@SuppressWarnings("NullAway.Init")

View File

@@ -1,6 +1,6 @@
package com.peakup.athlete.model;
import com.peakup.dao.PersistableEnum;
import com.peakup.common.dao.PersistableEnum;
public enum SexEnum implements PersistableEnum<SexEnum> {

View File

@@ -0,0 +1,7 @@
package com.peakup.common;
public class ImportException extends PeakUpException {
public ImportException(String msg) {
super(msg);
}
}

View File

@@ -1,7 +1,7 @@
package com.peakup.dao;
package com.peakup.common.dao;
import com.peakup.common.PersistenceException;
import com.peakup.model.AbstractEntity;
import com.peakup.common.model.AbstractEntity;
import com.peakup.model.AbstractEntity_;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;

View File

@@ -1,4 +1,4 @@
package com.peakup.dao;
package com.peakup.common.dao;
import com.peakup.activity.model.ActivityTypeEnum;

View File

@@ -0,0 +1,8 @@
package com.peakup.common.dao;
import com.peakup.common.model.AbstractTimestampedEntity;
public abstract class AbstractTimestampedDAO<T extends AbstractTimestampedEntity> extends AbstractDAO<T> {
}

View File

@@ -1,11 +1,11 @@
package com.peakup.dao;
package com.peakup.common.dao;
public interface PersistableEnum<V extends PersistableEnum > {
public interface PersistableEnum<V extends PersistableEnum> {
Integer getKey();
static <T extends PersistableEnum> T getEnumByKey(Integer key, T[] values) {
for (T v : values) {
if (v.getKey().equals( key)) return v;
if (v.getKey().equals(key)) return v;
}
throw new IllegalArgumentException("Unknown key: " + key);
}

View File

@@ -1,2 +1,2 @@
@com.peakup.common.annotation.NonNull
package com.peakup.model;
package com.peakup.common.dao;

View File

@@ -1,4 +1,4 @@
package com.peakup.model;
package com.peakup.common.model;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;

View File

@@ -1,6 +1,8 @@
package com.peakup.model;
package com.peakup.common.model;
import jakarta.persistence.*;
import jakarta.persistence.Column;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import lombok.Data;
import java.time.Instant;

View File

@@ -1,4 +1,4 @@
package com.peakup.model;
package com.peakup.common.model;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;

View File

@@ -1,4 +1,4 @@
package com.peakup.model;
package com.peakup.common.model;
public interface DependentEntity {
long getFK();

View File

@@ -1,2 +1,2 @@
@com.peakup.common.annotation.NonNull
package com.peakup.service;
package com.peakup.common.model;

View File

@@ -1,12 +1,12 @@
package com.peakup.service;
package com.peakup.common.service;
import com.peakup.dao.AbstractDAO;
import com.peakup.model.AbstractEntity;
import com.peakup.common.dao.AbstractDAO;
import com.peakup.common.model.AbstractEntity;
import java.util.List;
@SuppressWarnings("unused")
public abstract class AbstractService<E extends AbstractEntity, D extends AbstractDAO<E>> {
public abstract class AbstractService<E extends AbstractEntity, D extends AbstractDAO<E>> {
protected E getById(long id) {
return getDAO().getById(id);
@@ -19,12 +19,13 @@ public abstract class AbstractService<E extends AbstractEntity, D extends Abstra
protected void insert(E entity) {
getDAO().insert(entity);
}
protected void update(E entity) {
getDAO().update(entity);
getDAO().update(entity);
}
protected void delete(E entity) {
getDAO().delete(entity);
getDAO().delete(entity);
}
protected abstract D getDAO();

View File

@@ -1,2 +1,2 @@
@com.peakup.common.annotation.NonNull
package com.peakup.dao;
package com.peakup.common.service;

View File

@@ -1,4 +1,4 @@
package com.peakup.connector.intervals;
package com.peakup.connector.intervals.client;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonParser;
@@ -146,13 +146,13 @@ public interface IntervalsClient {
case "grade_smooth" -> result.grade_smooth = mapper.convertValue(data, DBL_LIST);
case "moving" -> result.moving = mapper.convertValue(data, BOOL_LIST);
case "left_right_balance" -> result.left_right_balance = mapper.convertValue(data, DBL_LIST);
case "hrv" -> result.hrv = mapper.convertValue(data, INT_LIST_LIST);
case "respiration" -> result.respiration = mapper.convertValue(data, DBL_LIST);
case "fixed_altitude" -> result.fixed_altitude = mapper.convertValue(data, INT_LIST);
case "left_pedal_smoothness" -> result.left_pedal_smoothness = mapper.convertValue(data, DBL_LIST);
case "right_pedal_smoothness" -> result.right_pedal_smoothness = mapper.convertValue(data, DBL_LIST);
case "hrv" -> result.hrv = mapper.convertValue(data, INT_LIST_LIST);
case "respiration" -> result.respiration = mapper.convertValue(data, DBL_LIST);
case "fixed_altitude" -> result.fixed_altitude = mapper.convertValue(data, INT_LIST);
case "left_pedal_smoothness" -> result.left_pedal_smoothness = mapper.convertValue(data, DBL_LIST);
case "right_pedal_smoothness" -> result.right_pedal_smoothness = mapper.convertValue(data, DBL_LIST);
case "left_torque_effectiveness" -> result.left_torque_effectiveness = mapper.convertValue(data, DBL_LIST);
case "right_torque_effectiveness"-> result.right_torque_effectiveness= mapper.convertValue(data, DBL_LIST);
case "right_torque_effectiveness" -> result.right_torque_effectiveness = mapper.convertValue(data, DBL_LIST);
default -> {
result.unknownStreams.put(type, mapper.convertValue(data, Object.class));

View File

@@ -1,4 +1,4 @@
package com.peakup.connector.intervals;
package com.peakup.connector.intervals.client;
import com.peakup.connector.intervals.service.IntervalsConnectorService;
import jakarta.enterprise.context.ApplicationScoped;

View File

@@ -1,4 +1,4 @@
package com.peakup.connector.intervals;
package com.peakup.connector.intervals.client;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.MultivaluedHashMap;

View File

@@ -1,8 +1,8 @@
package com.peakup.connector.intervals.dao;
import com.peakup.common.dao.AbstractDAO;
import com.peakup.connector.intervals.model.IntervalsConnector;
import com.peakup.connector.intervals.model.IntervalsConnector_;
import com.peakup.dao.AbstractDAO;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;

View File

@@ -1,6 +1,6 @@
package com.peakup.connector.intervals.model;
import com.peakup.model.AbstractTimestampedEntity;
import com.peakup.common.model.AbstractTimestampedEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;

View File

@@ -1,8 +1,8 @@
package com.peakup.connector.intervals.service;
import com.peakup.common.service.AbstractService;
import com.peakup.connector.intervals.dao.IntervalsConnectorDAO;
import com.peakup.connector.intervals.model.IntervalsConnector;
import com.peakup.service.AbstractService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

View File

@@ -1,4 +1,4 @@
package com.peakup.connector.strava;
package com.peakup.connector.strava.client;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.*;

View File

@@ -1,4 +1,4 @@
package com.peakup.connector.strava;
package com.peakup.connector.strava.client;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;

View File

@@ -1,4 +1,4 @@
package com.peakup.connector.strava;
package com.peakup.connector.strava.client;
import com.peakup.connector.strava.service.StravaConnectorService;
import jakarta.enterprise.context.ApplicationScoped;

View File

@@ -1,4 +1,4 @@
package com.peakup.connector.strava;
package com.peakup.connector.strava.client;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.MultivaluedHashMap;

View File

@@ -1,10 +1,8 @@
package com.peakup.connector.strava.dao;
import com.peakup.athlete.model.Athlete;
import com.peakup.common.dao.AbstractDAO;
import com.peakup.connector.strava.model.StravaConnector;
import com.peakup.connector.strava.model.StravaConnector_;
import com.peakup.dao.AbstractDAO;
import com.peakup.model.AbstractEntity_;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;

View File

@@ -1,6 +1,6 @@
package com.peakup.connector.strava.model;
import com.peakup.model.AbstractTimestampedEntity;
import com.peakup.common.model.AbstractTimestampedEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;

View File

@@ -1,10 +1,10 @@
package com.peakup.connector.strava.service;
import com.peakup.common.Assert;
import com.peakup.connector.strava.StravaAuthClient;
import com.peakup.common.service.AbstractService;
import com.peakup.connector.strava.client.StravaAuthClient;
import com.peakup.connector.strava.dao.StravaConnectorDAO;
import com.peakup.connector.strava.model.StravaConnector;
import com.peakup.service.AbstractService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

View File

@@ -1,20 +0,0 @@
package com.peakup.dao;
import com.peakup.common.PersistenceException;
import com.peakup.model.AbstractEntity;
import com.peakup.model.AbstractEntity_;
import com.peakup.model.AbstractTimestampedEntity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
public abstract class AbstractTimestampedDAO<T extends AbstractTimestampedEntity> extends AbstractDAO<T> {
}

View File

@@ -2,7 +2,7 @@ package com.peakup.scheduler;
import com.peakup.activity.importer.IntervalsActivityImporter;
import com.peakup.activity.importer.StravaActivityImporter;
import com.peakup.connector.intervals.IntervalsContext;
import com.peakup.connector.intervals.client.IntervalsContext;
import com.peakup.connector.intervals.model.IntervalsConnector;
import com.peakup.connector.intervals.service.IntervalsConnectorService;
import com.peakup.connector.strava.model.StravaConnector;

View File

@@ -1,7 +1,7 @@
package com.peakup.activity.importer;
import com.peakup.AbstractPeakUpTest;
import com.peakup.connector.intervals.IntervalsContext;
import com.peakup.connector.intervals.client.IntervalsContext;
import com.peakup.connector.intervals.model.IntervalsConnector;
import com.peakup.connector.intervals.service.IntervalsConnectorService;
import com.peakup.connector.strava.service.StravaConnectorService;

View File

@@ -1,14 +1,8 @@
package com.peakup.activity.importer;
import com.peakup.activity.model.ActivityAttachment;
import com.peakup.activity.model.ActivityMapStreamPoint;
import com.peakup.activity.model.ActivityStreamPoint;
import com.peakup.activity.model.ActivityZone;
import com.peakup.activity.model.IgnorePart;
import com.peakup.activity.model.IntervalsActivity;
import com.peakup.activity.model.StreamStatusEnum;
import com.peakup.connector.intervals.IntervalsClient;
import com.peakup.connector.intervals.IntervalsContext;
import com.peakup.activity.model.*;
import com.peakup.connector.intervals.client.IntervalsClient;
import com.peakup.connector.intervals.client.IntervalsContext;
import com.peakup.connector.intervals.service.IntervalsConnectorService;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
@@ -31,101 +25,103 @@ class IntervalsActivityMappingTest {
// StreamsData field → ActivityStreamPoint field(s)
// "time" covers both: elapsedS (the raw elapsed value) and the inherited `time` (computed Instant)
private static final Map<String, List<String>> STREAMS_DTO_TO_ENTITY;
static {
Map<String, List<String>> m = new LinkedHashMap<>();
m.put("time", List.of("elapsedS", "time"));
m.put("distance", List.of("distanceM"));
m.put("altitude", List.of("altitudeM"));
m.put("heartrate", List.of("heartrateBpm"));
m.put("watts", List.of("watts"));
m.put("torque", List.of("torqueNm"));
m.put("cadence", List.of("cadenceRpm"));
m.put("velocity_smooth", List.of("velocityMps"));
m.put("latlng", List.of("lat"));
m.put("temp", List.of("tempC"));
m.put("grade_smooth", List.of("gradeSmooth"));
m.put("moving", List.of("moving"));
m.put("time", List.of("elapsedS", "time"));
m.put("distance", List.of("distanceM"));
m.put("altitude", List.of("altitudeM"));
m.put("heartrate", List.of("heartrateBpm"));
m.put("watts", List.of("watts"));
m.put("torque", List.of("torqueNm"));
m.put("cadence", List.of("cadenceRpm"));
m.put("velocity_smooth", List.of("velocityMps"));
m.put("latlng", List.of("lat"));
m.put("temp", List.of("tempC"));
m.put("grade_smooth", List.of("gradeSmooth"));
m.put("moving", List.of("moving"));
m.put("left_right_balance", List.of("lrBalance"));
// unknownStreams is not mapped to any entity field — unknown types throw ImportException
STREAMS_DTO_TO_ENTITY = Collections.unmodifiableMap(m);
}
private static final Map<String, List<String>> DTO_TO_ENTITY;
static {
Map<String, List<String>> m = new LinkedHashMap<>();
m.put("id", List.of("originId"));
m.put("external_id", List.of("stravaOriginId"));
m.put("start_date", List.of("startTime"));
m.put("distance", List.of("distanceM"));
m.put("moving_time", List.of("movingTimeS"));
m.put("elapsed_time", List.of("elapsedTimeS"));
m.put("average_speed", List.of("averageSpeedMps"));
m.put("max_speed", List.of("maxSpeedMps"));
m.put("average_heartrate", List.of("averageHeartrateBpm"));
m.put("max_heartrate", List.of("maxHeartrateBPM"));
m.put("average_cadence", List.of("averageCadenceRPM"));
m.put("icu_average_watts", List.of("averagePowerWatt"));
m.put("id", List.of("originId"));
m.put("external_id", List.of("stravaOriginId"));
m.put("start_date", List.of("startTime"));
m.put("distance", List.of("distanceM"));
m.put("moving_time", List.of("movingTimeS"));
m.put("elapsed_time", List.of("elapsedTimeS"));
m.put("average_speed", List.of("averageSpeedMps"));
m.put("max_speed", List.of("maxSpeedMps"));
m.put("average_heartrate", List.of("averageHeartrateBpm"));
m.put("max_heartrate", List.of("maxHeartrateBPM"));
m.put("average_cadence", List.of("averageCadenceRPM"));
m.put("icu_average_watts", List.of("averagePowerWatt"));
m.put("icu_weighted_avg_watts", List.of("normalizedPowerWatt"));
m.put("icu_joules", List.of("totalWorkKJ"));
m.put("total_elevation_gain", List.of("totalElevationGainM", "totalAscentM"));
m.put("total_elevation_loss", List.of("totalDescentM"));
m.put("min_altitude", List.of("minAltitudeM"));
m.put("max_altitude", List.of("maxAltitudeM"));
m.put("icu_training_load", List.of("trainingLoad"));
m.put("icu_intensity", List.of("intensityFactor"));
m.put("icu_efficiency_factor", List.of("efficiencyFactor"));
m.put("icu_ftp", List.of("ftp"));
m.put("icu_atl", List.of("atl"));
m.put("icu_ctl", List.of("ctl"));
m.put("icu_variability_index", List.of("variabilityIndex"));
m.put("average_stride", List.of("averageStrideLengthM"));
m.put("average_temp", List.of("averageTempC"));
m.put("custom_zones", List.of("zones"));
m.put("icu_hrr", List.of("hrrBpmDrop", "hrrStartBpm", "hrrEndBpm",
"hrrStartTimeSec", "hrrEndTimeSec", "hrrAverageWatts"));
m.put("gear", List.of("gearId", "gearName", "gearDistanceM", "gearPrimary"));
m.put("icu_achievements", List.of()); // stored in ActivityAchievement, not on entity
m.put("icu_joules", List.of("totalWorkKJ"));
m.put("total_elevation_gain", List.of("totalElevationGainM", "totalAscentM"));
m.put("total_elevation_loss", List.of("totalDescentM"));
m.put("min_altitude", List.of("minAltitudeM"));
m.put("max_altitude", List.of("maxAltitudeM"));
m.put("icu_training_load", List.of("trainingLoad"));
m.put("icu_intensity", List.of("intensityFactor"));
m.put("icu_efficiency_factor", List.of("efficiencyFactor"));
m.put("icu_ftp", List.of("ftp"));
m.put("icu_atl", List.of("atl"));
m.put("icu_ctl", List.of("ctl"));
m.put("icu_variability_index", List.of("variabilityIndex"));
m.put("average_stride", List.of("averageStrideLengthM"));
m.put("average_temp", List.of("averageTempC"));
m.put("custom_zones", List.of("zones"));
m.put("icu_hrr", List.of("hrrBpmDrop", "hrrStartBpm", "hrrEndBpm",
"hrrStartTimeSec", "hrrEndTimeSec", "hrrAverageWatts"));
m.put("gear", List.of("gearId", "gearName", "gearDistanceM", "gearPrimary"));
m.put("icu_achievements", List.of()); // stored in ActivityAchievement, not on entity
// New fields — unit-suffix renames
// icu_ignore_time → icuIgnoreTime (Boolean flag, direct camelCase match)
m.put("icu_recording_time", List.of("icuRecordingTimeS"));
m.put("coasting_time", List.of("coastingTimeS"));
m.put("icu_warmup_time", List.of("icuWarmupTimeS"));
m.put("icu_cooldown_time", List.of("icuCooldownTimeS"));
m.put("icu_distance", List.of("icuDistanceM"));
m.put("min_temp", List.of("minTempC"));
m.put("max_temp", List.of("maxTempC"));
m.put("gap", List.of("gapMps"));
m.put("crank_length", List.of("crankLengthMm"));
m.put("threshold_pace", List.of("thresholdPaceMps"));
m.put("icu_weight", List.of("icuWeightKg"));
m.put("carbs_used", List.of("carbsUsedG"));
m.put("icu_pm_w_prime", List.of("icuPmWPrimeJ"));
m.put("icu_pm_p_max", List.of("icuPmPMaxWatt"));
m.put("icu_rolling_w_prime", List.of("icuRollingWPrimeJ"));
m.put("icu_rolling_p_max", List.of("icuRollingPMaxWatt"));
m.put("ss_w_prime", List.of("ssWPrimeJ"));
m.put("ss_p_max", List.of("ssPMaxWatt"));
m.put("icu_w_prime", List.of("icuWPrimeJ"));
m.put("p_max", List.of("pMaxWatt"));
m.put("icu_recording_time", List.of("icuRecordingTimeS"));
m.put("coasting_time", List.of("coastingTimeS"));
m.put("icu_warmup_time", List.of("icuWarmupTimeS"));
m.put("icu_cooldown_time", List.of("icuCooldownTimeS"));
m.put("icu_distance", List.of("icuDistanceM"));
m.put("min_temp", List.of("minTempC"));
m.put("max_temp", List.of("maxTempC"));
m.put("gap", List.of("gapMps"));
m.put("crank_length", List.of("crankLengthMm"));
m.put("threshold_pace", List.of("thresholdPaceMps"));
m.put("icu_weight", List.of("icuWeightKg"));
m.put("carbs_used", List.of("carbsUsedG"));
m.put("icu_pm_w_prime", List.of("icuPmWPrimeJ"));
m.put("icu_pm_p_max", List.of("icuPmPMaxWatt"));
m.put("icu_rolling_w_prime", List.of("icuRollingWPrimeJ"));
m.put("icu_rolling_p_max", List.of("icuRollingPMaxWatt"));
m.put("ss_w_prime", List.of("ssWPrimeJ"));
m.put("ss_p_max", List.of("ssPMaxWatt"));
m.put("icu_w_prime", List.of("icuWPrimeJ"));
m.put("p_max", List.of("pMaxWatt"));
// created → icuCreated (to avoid clash with AbstractTimestampedEntity.createdAt)
m.put("created", List.of("icuCreated"));
m.put("created", List.of("icuCreated"));
// Second batch — unit-suffix renames
m.put("pool_length", List.of("poolLengthM"));
m.put("average_altitude", List.of("averageAltitudeM"));
m.put("average_weather_temp", List.of("averageWeatherTempC"));
m.put("min_weather_temp", List.of("minWeatherTempC"));
m.put("max_weather_temp", List.of("maxWeatherTempC"));
m.put("average_feels_like", List.of("averageFeelsLikeC"));
m.put("min_feels_like", List.of("minFeelsLikeC"));
m.put("max_feels_like", List.of("maxFeelsLikeC"));
m.put("average_wind_speed", List.of("averageWindSpeedMps"));
m.put("average_wind_gust", List.of("averageWindGustMps"));
m.put("max_rain", List.of("maxRainMm"));
m.put("max_snow", List.of("maxSnowMm"));
m.put("carbs_ingested", List.of("carbsIngestedG"));
m.put("pace", List.of("paceMps"));
m.put("pool_length", List.of("poolLengthM"));
m.put("average_altitude", List.of("averageAltitudeM"));
m.put("average_weather_temp", List.of("averageWeatherTempC"));
m.put("min_weather_temp", List.of("minWeatherTempC"));
m.put("max_weather_temp", List.of("maxWeatherTempC"));
m.put("average_feels_like", List.of("averageFeelsLikeC"));
m.put("min_feels_like", List.of("minFeelsLikeC"));
m.put("max_feels_like", List.of("maxFeelsLikeC"));
m.put("average_wind_speed", List.of("averageWindSpeedMps"));
m.put("average_wind_gust", List.of("averageWindGustMps"));
m.put("max_rain", List.of("maxRainMm"));
m.put("max_snow", List.of("maxSnowMm"));
m.put("carbs_ingested", List.of("carbsIngestedG"));
m.put("pace", List.of("paceMps"));
// group → activityGroup (SQL reserved word avoidance)
m.put("group", List.of("activityGroup"));
m.put("group", List.of("activityGroup"));
DTO_TO_ENTITY = Collections.unmodifiableMap(m);
}
@@ -204,8 +200,11 @@ class IntervalsActivityMappingTest {
private @jakarta.annotation.Nullable java.lang.reflect.Field findEntityField(String name) {
Class<?> cls = IntervalsActivity.class;
while (cls != null && cls != Object.class) {
try { return cls.getDeclaredField(name); }
catch (NoSuchFieldException ignored) { cls = cls.getSuperclass(); }
try {
return cls.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
cls = cls.getSuperclass();
}
}
return null;
}
@@ -227,10 +226,10 @@ class IntervalsActivityMappingTest {
}
private void assertMapStreamMapping(IntervalsClient.MapData mapData,
List<Integer> timeList,
List<ActivityMapStreamPoint> points,
Instant startTime,
String ctx) {
List<Integer> timeList,
List<ActivityMapStreamPoint> points,
Instant startTime,
String ctx) {
List<List<Double>> latlngs = mapData.latlngs;
assertNotNull(latlngs, ctx + "latlngs null");
int expected = Math.min(latlngs.size(), timeList.size());
@@ -254,8 +253,11 @@ class IntervalsActivityMappingTest {
private @jakarta.annotation.Nullable java.lang.reflect.Field findStreamEntityField(String name) {
Class<?> cls = ActivityStreamPoint.class;
while (cls != null && cls != Object.class) {
try { return cls.getDeclaredField(name); }
catch (NoSuchFieldException ignored) { cls = cls.getSuperclass(); }
try {
return cls.getDeclaredField(name);
} catch (NoSuchFieldException ignored) {
cls = cls.getSuperclass();
}
}
return null;
}
@@ -274,7 +276,9 @@ class IntervalsActivityMappingTest {
}
}
/** Reflection check: every StreamsData field has a corresponding ActivityStreamPoint field. */
/**
* Reflection check: every StreamsData field has a corresponding ActivityStreamPoint field.
*/
private void checkStreamsCoverage(String ctx) {
for (java.lang.reflect.Field dtoField : IntervalsClient.StreamsData.class.getDeclaredFields()) {
if (!java.lang.reflect.Modifier.isPublic(dtoField.getModifiers())) continue;
@@ -288,7 +292,9 @@ class IntervalsActivityMappingTest {
}
}
/** Value assertions for sampled points (first, middle, last). */
/**
* Value assertions for sampled points (first, middle, last).
*/
private void assertStreamsMapping(IntervalsClient.StreamsData streams,
List<ActivityStreamPoint> points,
Instant startTime,
@@ -376,9 +382,14 @@ class IntervalsActivityMappingTest {
boolean upper = false;
for (int i = 0; i < snake.length(); i++) {
char c = snake.charAt(i);
if (c == '_') { upper = true; }
else if (upper) { sb.append(Character.toUpperCase(c)); upper = false; }
else { sb.append(c); }
if (c == '_') {
upper = true;
} else if (upper) {
sb.append(Character.toUpperCase(c));
upper = false;
} else {
sb.append(c);
}
}
return sb.toString();
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>