Activities
This commit is contained in:
@@ -33,6 +33,48 @@ public class ActivityStreamDAO {
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
DataSource dataSource;
|
||||
|
||||
public List<ActivityStreamPoint> getByActivityId(final long activityId) throws SQLException {
|
||||
try (final Connection conn = dataSource.getConnection();
|
||||
final PreparedStatement ps = conn.prepareStatement(
|
||||
"SELECT time, elapsed_s, distance_m, altitude_m, heartrate_bpm, watts, torque_nm, " +
|
||||
"cadence_rpm, velocity_mps, lat, lng, temp_c, grade_smooth, moving, lr_balance, " +
|
||||
"hrv_ms, respiration_brpm, fixed_altitude_m, " +
|
||||
"left_pedal_smoothness_pct, right_pedal_smoothness_pct, " +
|
||||
"left_torque_effectiveness_pct, right_torque_effectiveness_pct " +
|
||||
"FROM activity_stream_point WHERE activity_id = ? ORDER BY time ASC")) {
|
||||
ps.setLong(1, activityId);
|
||||
try (final ResultSet rs = ps.executeQuery()) {
|
||||
final List<ActivityStreamPoint> points = new java.util.ArrayList<>();
|
||||
while (rs.next()) {
|
||||
final ActivityStreamPoint point = new ActivityStreamPoint(
|
||||
rs.getTimestamp(1).toInstant(), activityId, rs.getInt(2));
|
||||
point.setDistanceM(rs.getObject(3) != null ? rs.getDouble(3) : null);
|
||||
point.setAltitudeM(rs.getObject(4) != null ? rs.getDouble(4) : null);
|
||||
point.setHeartrateBpm(rs.getObject(5) != null ? rs.getInt(5) : null);
|
||||
point.setWatts(rs.getObject(6) != null ? rs.getInt(6) : null);
|
||||
point.setTorqueNm(rs.getObject(7) != null ? rs.getInt(7) : null);
|
||||
point.setCadenceRpm(rs.getObject(8) != null ? rs.getDouble(8) : null);
|
||||
point.setVelocityMps(rs.getObject(9) != null ? rs.getDouble(9) : null);
|
||||
point.setLat(rs.getObject(10) != null ? rs.getDouble(10) : null);
|
||||
point.setLng(rs.getObject(11) != null ? rs.getDouble(11) : null);
|
||||
point.setTempC(rs.getObject(12) != null ? rs.getDouble(12) : null);
|
||||
point.setGradeSmooth(rs.getObject(13) != null ? rs.getDouble(13) : null);
|
||||
point.setMoving(rs.getObject(14) != null ? rs.getBoolean(14) : null);
|
||||
point.setLrBalance(rs.getObject(15) != null ? rs.getDouble(15) : null);
|
||||
point.setHrvMs(rs.getObject(16) != null ? rs.getDouble(16) : null);
|
||||
point.setRespirationBrpm(rs.getObject(17) != null ? rs.getDouble(17) : null);
|
||||
point.setFixedAltitudeM(rs.getObject(18) != null ? rs.getInt(18) : null);
|
||||
point.setLeftPedalSmoothnessPct(rs.getObject(19) != null ? rs.getDouble(19) : null);
|
||||
point.setRightPedalSmoothnessPct(rs.getObject(20) != null ? rs.getDouble(20) : null);
|
||||
point.setLeftTorqueEffectivenessPct(rs.getObject(21) != null ? rs.getDouble(21) : null);
|
||||
point.setRightTorqueEffectivenessPct(rs.getObject(22) != null ? rs.getDouble(22) : null);
|
||||
points.add(point);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasStreams(final long activityId) throws SQLException {
|
||||
try (final Connection conn = dataSource.getConnection();
|
||||
final PreparedStatement ps = conn.prepareStatement(
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.peakup.activity.model;
|
||||
|
||||
import com.peakup.common.model.AbstractTimescaleEntity;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class AbstractStream extends AbstractTimescaleEntity {
|
||||
|
||||
public AbstractStream(final Instant time) {
|
||||
super(time);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
public AbstractStream() {
|
||||
// Hibernate
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.peakup.activity.model;
|
||||
|
||||
import com.peakup.common.model.AbstractTimescaleEntity;
|
||||
import com.peakup.common.model.DependentEntity;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
@@ -20,7 +19,7 @@ import java.time.Instant;
|
||||
@IdClass(ActivityMapStreamPoint.Key.class)
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ActivityMapStreamPoint extends AbstractTimescaleEntity implements DependentEntity {
|
||||
public class ActivityMapStreamPoint extends AbstractStream implements DependentEntity {
|
||||
|
||||
@Id
|
||||
@Column(nullable = false)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.peakup.activity.model;
|
||||
|
||||
import com.peakup.common.model.AbstractTimescaleEntity;
|
||||
import com.peakup.common.model.DependentEntity;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.Column;
|
||||
@@ -21,7 +20,7 @@ import java.time.Instant;
|
||||
@IdClass(ActivityStreamPoint.Key.class)
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ActivityStreamPoint extends AbstractTimescaleEntity implements DependentEntity {
|
||||
public class ActivityStreamPoint extends AbstractStream implements DependentEntity {
|
||||
|
||||
@Id
|
||||
@Column(nullable = false)
|
||||
|
||||
@@ -8,7 +8,20 @@ public enum ActivityTypeEnum implements PersistableEnum<ActivityTypeEnum> {
|
||||
RUN(2),
|
||||
SWIM(3),
|
||||
OPENWATER(4),
|
||||
;
|
||||
STRENGTH(5),
|
||||
WORKOUT(6),
|
||||
VIRTUAL_SKI(7),
|
||||
VIRTUAL_RIDE(8),
|
||||
HIKE(9),
|
||||
WALK(10),
|
||||
WEIGHT_TRAINING(11),
|
||||
PILATES(12),
|
||||
NORDIC_SKI(13),
|
||||
VIRTUAL_ROW(14),
|
||||
ROWING(15),
|
||||
YOGA(16),
|
||||
TRAIL_RUN(17),
|
||||
OTHER(100);
|
||||
|
||||
private final int key;
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.peakup.activity.service;
|
||||
|
||||
import com.peakup.activity.dao.ActivityStreamDAO;
|
||||
import com.peakup.activity.model.ActivityStreamPoint;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ActivityStreamService {
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
ActivityStreamDAO activityStreamDAO;
|
||||
|
||||
public List<ActivityStreamPoint> getByActivityId(final long activityId) {
|
||||
try {
|
||||
return activityStreamDAO.getByActivityId(activityId);
|
||||
} catch (final SQLException sqlException) {
|
||||
throw new IllegalStateException("Failed to load stream for activity " + activityId, sqlException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package com.peakup.dashboard.model;
|
||||
|
||||
import com.peakup.activity.model.ActivityMapStreamPoint;
|
||||
import com.peakup.activity.model.ActivityStreamPoint;
|
||||
import com.peakup.activity.model.IntervalsActivity;
|
||||
import com.peakup.common.model.AbstractDataObject;
|
||||
import com.peakup.goal.model.Goal;
|
||||
import com.peakup.goal.model.GoalMetricPeriodTotal;
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@@ -22,5 +23,6 @@ public class Dashboard extends AbstractDataObject {
|
||||
private List<IntervalsActivity> activities;
|
||||
private List<Goal> goals;
|
||||
private List<GoalMetricPeriodTotal> goalMetricPeriodTotals;
|
||||
private @Nullable List<ActivityMapStreamPoint> lastActivityMapStream;
|
||||
private Map<Long, List<ActivityMapStreamPoint>> weekActivityMapStreams;
|
||||
private Map<Long, List<ActivityStreamPoint>> weekActivityStreams;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
package com.peakup.dashboard.service;
|
||||
|
||||
import com.peakup.activity.model.ActivityMapStreamPoint;
|
||||
import com.peakup.activity.model.ActivityStreamPoint;
|
||||
import com.peakup.activity.model.IntervalsActivity;
|
||||
import com.peakup.activity.service.ActivityMapStreamService;
|
||||
import com.peakup.activity.service.ActivityStreamService;
|
||||
import com.peakup.activity.service.IntervalsActivityService;
|
||||
import com.peakup.dashboard.model.Dashboard;
|
||||
import com.peakup.goal.service.GoalMetricPeriodTotalService;
|
||||
import com.peakup.goal.service.GoalService;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApplicationScoped
|
||||
public class DashboardService {
|
||||
@@ -30,6 +34,10 @@ public class DashboardService {
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
ActivityMapStreamService activityMapStreamService;
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
ActivityStreamService activityStreamService;
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("NullAway.Init")
|
||||
GoalService goalService;
|
||||
@@ -52,7 +60,10 @@ public class DashboardService {
|
||||
final String to = rangeTo.format(DateTimeFormatter.ISO_LOCAL_DATE);
|
||||
|
||||
final List<IntervalsActivity> activities = intervalsActivityService.getByAthleteIdBetween(athleteId, from, to);
|
||||
final @Nullable List<ActivityMapStreamPoint> lastActivityMapStream = resolveLastActivityMapStream(activities);
|
||||
final Map<Long, List<ActivityMapStreamPoint>> weekActivityMapStreams =
|
||||
resolveWeekActivityMapStreams(activities, selectedWeekMonday);
|
||||
final Map<Long, List<ActivityStreamPoint>> weekActivityStreams =
|
||||
resolveWeekActivityStreams(activities, selectedWeekMonday);
|
||||
|
||||
return new Dashboard(
|
||||
calendarWeek,
|
||||
@@ -60,20 +71,41 @@ public class DashboardService {
|
||||
activities,
|
||||
goalService.getByAthleteId(athleteId),
|
||||
goalMetricPeriodTotalService.getByAthleteAndWeek(athleteId, year, calendarWeek),
|
||||
lastActivityMapStream
|
||||
weekActivityMapStreams,
|
||||
weekActivityStreams
|
||||
);
|
||||
}
|
||||
|
||||
private @Nullable List<ActivityMapStreamPoint> resolveLastActivityMapStream(final List<IntervalsActivity> activities) {
|
||||
private List<IntervalsActivity> filterToWeek(final List<IntervalsActivity> activities, final LocalDate weekMonday) {
|
||||
final LocalDate weekSunday = weekMonday.plusDays(6);
|
||||
return activities.stream()
|
||||
.max(Comparator.comparing(IntervalsActivity::getStartTime))
|
||||
.map(lastActivity -> {
|
||||
final Long activityId = lastActivity.getId();
|
||||
if (activityId == null) {
|
||||
return null;
|
||||
}
|
||||
return activityMapStreamService.getByActivityId(activityId);
|
||||
.filter(activity -> {
|
||||
final LocalDate activityDate = activity.getStartTime().atZone(ZoneOffset.UTC).toLocalDate();
|
||||
return !activityDate.isBefore(weekMonday) && !activityDate.isAfter(weekSunday);
|
||||
})
|
||||
.orElse(null);
|
||||
.filter(activity -> activity.getId() != null)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Map<Long, List<ActivityMapStreamPoint>> resolveWeekActivityMapStreams(
|
||||
final List<IntervalsActivity> activities,
|
||||
final LocalDate weekMonday) {
|
||||
return filterToWeek(activities, weekMonday).stream()
|
||||
.collect(Collectors.toMap(
|
||||
activity -> Objects.requireNonNull(activity.getId()),
|
||||
activity -> activityMapStreamService.getByActivityId(
|
||||
Objects.requireNonNull(activity.getId()))
|
||||
));
|
||||
}
|
||||
|
||||
private Map<Long, List<ActivityStreamPoint>> resolveWeekActivityStreams(
|
||||
final List<IntervalsActivity> activities,
|
||||
final LocalDate weekMonday) {
|
||||
return filterToWeek(activities, weekMonday).stream()
|
||||
.collect(Collectors.toMap(
|
||||
activity -> Objects.requireNonNull(activity.getId()),
|
||||
activity -> activityStreamService.getByActivityId(
|
||||
Objects.requireNonNull(activity.getId()))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
7
peakup-frontend/src/types/AbstractStreamDo.ts
Normal file
7
peakup-frontend/src/types/AbstractStreamDo.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
// GENERATED — do not edit manually.
|
||||
|
||||
import type { AbstractTimescaleEntityDo } from './AbstractTimescaleEntityDo';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
export interface AbstractStreamDo extends AbstractTimescaleEntityDo {
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// GENERATED — do not edit manually.
|
||||
|
||||
import type { AbstractTimescaleEntityDo } from './AbstractTimescaleEntityDo';
|
||||
import type { AbstractStreamDo } from './AbstractStreamDo';
|
||||
|
||||
export interface ActivityMapStreamPointDo extends AbstractTimescaleEntityDo {
|
||||
export interface ActivityMapStreamPointDo extends AbstractStreamDo {
|
||||
activityId: number;
|
||||
lat: number;
|
||||
lng: number;
|
||||
|
||||
28
peakup-frontend/src/types/ActivityStreamPointDo.ts
Normal file
28
peakup-frontend/src/types/ActivityStreamPointDo.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// GENERATED — do not edit manually.
|
||||
|
||||
import type { AbstractStreamDo } from './AbstractStreamDo';
|
||||
|
||||
export interface ActivityStreamPointDo extends AbstractStreamDo {
|
||||
activityId: number;
|
||||
elapsedS: number;
|
||||
distanceM: number | null;
|
||||
altitudeM: number | null;
|
||||
heartrateBpm: number | null;
|
||||
watts: number | null;
|
||||
torqueNm: number | null;
|
||||
cadenceRpm: number | null;
|
||||
velocityMps: number | null;
|
||||
lat: number | null;
|
||||
lng: number | null;
|
||||
tempC: number | null;
|
||||
gradeSmooth: number | null;
|
||||
moving: boolean | null;
|
||||
lrBalance: number | null;
|
||||
hrvMs: number | null;
|
||||
respirationBrpm: number | null;
|
||||
fixedAltitudeM: number | null;
|
||||
leftPedalSmoothnessPct: number | null;
|
||||
rightPedalSmoothnessPct: number | null;
|
||||
leftTorqueEffectivenessPct: number | null;
|
||||
rightTorqueEffectivenessPct: number | null;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import type { IntervalsActivityDo } from './IntervalsActivityDo';
|
||||
import type { GoalDo } from './GoalDo';
|
||||
import type { GoalMetricPeriodTotalDo } from './GoalMetricPeriodTotalDo';
|
||||
import type { ActivityMapStreamPointDo } from './ActivityMapStreamPointDo';
|
||||
import type { ActivityStreamPointDo } from './ActivityStreamPointDo';
|
||||
|
||||
export interface DashboardDo extends AbstractDataObjectDo {
|
||||
calendarWeek: number;
|
||||
@@ -12,5 +13,6 @@ export interface DashboardDo extends AbstractDataObjectDo {
|
||||
activities: IntervalsActivityDo[];
|
||||
goals: GoalDo[];
|
||||
goalMetricPeriodTotals: GoalMetricPeriodTotalDo[];
|
||||
lastActivityMapStream: ActivityMapStreamPointDo[] | null;
|
||||
weekActivityMapStreams: unknown;
|
||||
weekActivityStreams: unknown;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user