This commit is contained in:
2026-02-15 17:13:59 +01:00
parent 0d46da5d76
commit 38e36e1340
20 changed files with 277 additions and 114 deletions

81
.idea/compiler.xml generated
View File

@@ -35,6 +35,85 @@
<entry name="$USER_HOME$/.m2/peak-repo/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/net/bytebuddy/byte-buddy/1.17.8/byte-buddy-1.17.8.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/jboss/logging/jboss-logging/3.6.1.Final/jboss-logging-3.6.1.Final.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_core/2.28.0/error_prone_core-2.28.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_annotation/2.28.0/error_prone_annotation-2.28.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_type_annotations/2.28.0/error_prone_type_annotations-2.28.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_check_api/2.28.0/error_prone_check_api-2.28.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/github/kevinstern/software-and-algorithms/1.0/software-and-algorithms-1.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/github/ben-manes/caffeine/caffeine/3.0.5/caffeine-3.0.5.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/inject/guice/5.1.0/guice-5.1.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/aopalliance/aopalliance/1.0/aopalliance-1.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/pcollections/pcollections/4.0.1/pcollections-4.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/guava/32.1.1-jre/guava-32.1.1-jre.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/checkerframework/checker-qual/3.33.0/checker-qual-3.33.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/auto-common/1.2.2/auto-common-1.2.2.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/io/github/eisop/dataflow-errorprone/3.41.0-eisop1/dataflow-errorprone-3.41.0-eisop1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/value/auto-value-annotations/1.9/auto-value-annotations-1.9.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_annotations/2.28.0/error_prone_annotations-2.28.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/protobuf/protobuf-java/3.19.6/protobuf-java-3.19.6.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/service/auto-service-annotations/1.0.1/auto-service-annotations-1.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/uber/nullaway/nullaway/0.11.0/nullaway-0.11.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/checkerframework/dataflow-nullaway/3.43.0/dataflow-nullaway-3.43.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_core/2.37.0/error_prone_core-2.37.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_annotation/2.37.0/error_prone_annotation-2.37.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_check_api/2.37.0/error_prone_check_api-2.37.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/googlejavaformat/google-java-format/1.24.0/google-java-format-1.24.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_annotations/2.37.0/error_prone_annotations-2.37.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/protobuf/protobuf-java/3.25.5/protobuf-java-3.25.5.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/uber/nullaway/nullaway/0.13.1/nullaway-0.13.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/checkerframework/dataflow-nullaway/3.53.0/dataflow-nullaway-3.53.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/projectlombok/lombok/1.18.42/lombok-1.18.42.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_core/2.37.0/error_prone_core-2.37.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_annotation/2.37.0/error_prone_annotation-2.37.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_check_api/2.37.0/error_prone_check_api-2.37.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/github/kevinstern/software-and-algorithms/1.0/software-and-algorithms-1.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/github/ben-manes/caffeine/caffeine/3.0.5/caffeine-3.0.5.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/googlejavaformat/google-java-format/1.24.0/google-java-format-1.24.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/pcollections/pcollections/4.0.1/pcollections-4.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/guava/32.1.1-jre/guava-32.1.1-jre.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/checkerframework/checker-qual/3.33.0/checker-qual-3.33.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/j2objc/j2objc-annotations/2.8/j2objc-annotations-2.8.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/auto-common/1.2.2/auto-common-1.2.2.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/io/github/eisop/dataflow-errorprone/3.41.0-eisop1/dataflow-errorprone-3.41.0-eisop1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/value/auto-value-annotations/1.9/auto-value-annotations-1.9.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/errorprone/error_prone_annotations/2.37.0/error_prone_annotations-2.37.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/protobuf/protobuf-java/3.25.5/protobuf-java-3.25.5.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/google/auto/service/auto-service-annotations/1.0.1/auto-service-annotations-1.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/jspecify/jspecify/1.0.0/jspecify-1.0.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/javax/inject/javax.inject/1/javax.inject-1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/uber/nullaway/nullaway/0.13.1/nullaway-0.13.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/checkerframework/dataflow-nullaway/3.53.0/dataflow-nullaway-3.53.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/hibernate/orm/hibernate-processor/7.2.4.Final/hibernate-processor-7.2.4.Final.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/hibernate/orm/hibernate-core/7.2.4.Final/hibernate-core-7.2.4.Final.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/persistence/jakarta.persistence-api/3.2.0/jakarta.persistence-api-3.2.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/transaction/jakarta.transaction-api/2.0.1/jakarta.transaction-api-2.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/fasterxml/classmate/1.7.1/classmate-1.7.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/inject/jakarta.inject-api/2.0.1/jakarta.inject-api-2.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/hibernate/models/hibernate-models/1.0.1/hibernate-models-1.0.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/io/smallrye/jandex/3.3.2/jandex-3.3.2.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/xml/bind/jakarta.xml.bind-api/4.0.4/jakarta.xml.bind-api-4.0.4.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/activation/jakarta.activation-api/2.1.4/jakarta.activation-api-2.1.4.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/glassfish/jaxb/jaxb-runtime/4.0.6/jaxb-runtime-4.0.6.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/glassfish/jaxb/jaxb-core/4.0.6/jaxb-core-4.0.6.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/eclipse/angus/angus-activation/2.0.3/angus-activation-2.0.3.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/glassfish/jaxb/txw2/4.0.6/txw2-4.0.6.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/com/sun/istack/istack-commons-runtime/4.1.2/istack-commons-runtime-4.1.2.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/validation/jakarta.validation-api/3.1.1/jakarta.validation-api-3.1.1.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/jakarta/annotation/jakarta.annotation-api/3.0.0/jakarta.annotation-api-3.0.0.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/net/bytebuddy/byte-buddy/1.17.8/byte-buddy-1.17.8.jar" />
<entry name="$USER_HOME$/.m2/peak-repo/org/jboss/logging/jboss-logging/3.6.1.Final/jboss-logging-3.6.1.Final.jar" />
</processorPath>
<module name="peakup-backend" />
</profile>
@@ -45,7 +124,7 @@
</component>
<component name="JavacSettings">
<option name="ADDITIONAL_OPTIONS_OVERRIDE">
<module name="peakup-backend" options="" />
<module name="peakup-backend" options="-XDcompilePolicy=simple -XDshould-stop.ifError=FLOW &quot;-Xplugin:ErrorProne -Xep:NullAway:ERROR -XepOpt:NullAway:AnnotatedPackages=com.peakup&#10; -XepOpt:NullAway:TreatGeneratedAsUnannotated=true&quot; -Xpkginfo:always" />
</option>
</component>
</project>

7
.idea/misc.xml generated
View File

@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="1">
<item index="0" class="java.lang.String" itemvalue="jakarta.enterprise.context.ApplicationScoped" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="Quarkus" url="file://$PROJECT_DIR$" />
@@ -13,5 +18,5 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" project-jdk-name="temurin-25" project-jdk-type="JavaSDK" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="temurin-21" project-jdk-type="JavaSDK" />
</project>

View File

@@ -0,0 +1,10 @@
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED

View File

@@ -0,0 +1,7 @@
config.stopBubbling = true
lombok.addLombokGeneratedAnnotation = true
lombok.copyableAnnotations += com.peakup.common.annotation.Nullable
lombok.copyableAnnotations += com.peakup.common.annotation.NonNull

View File

@@ -8,9 +8,9 @@
<properties>
<compiler-plugin.version>3.15.0</compiler-plugin.version>
<maven.compiler.release>25</maven.compiler.release>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<lombok.version>1.18.42</lombok.version>
<jsr305.version>3.0.2</jsr305.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -113,6 +113,7 @@
<version>${jsr305.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
@@ -148,12 +149,31 @@
<version>${compiler-plugin.version}</version>
<configuration>
<release>${maven.compiler.release}</release>
<showWarnings>true</showWarnings>
<compilerArgs>
<arg>-XDcompilePolicy=simple</arg>
<arg>-XDshould-stop.ifError=FLOW</arg>
<arg>-Xplugin:ErrorProne -Xep:NullAway:ERROR -XepOpt:NullAway:AnnotatedPackages=com.peakup
-XepOpt:NullAway:TreatGeneratedAsUnannotated=true
</arg>
<arg>-Xpkginfo:always</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.37.0</version>
</path>
<path>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
<version>0.13.1</version>
</path>
<path>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
@@ -175,7 +195,7 @@
<configuration>
<rules>
<requireJavaVersion>
<version>[25,26)</version>
<version>[21,22)</version>
</requireJavaVersion>
</rules>
<fail>true</fail>

View File

@@ -4,6 +4,7 @@ import com.peakup.activity.model.Activity;
import com.peakup.activity.model.Activity_;
import com.peakup.activity.model.OriginEnum;
import com.peakup.dao.AbstractDAO;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
@@ -17,7 +18,7 @@ public class ActivityDAO extends AbstractDAO<Activity> {
return Activity.class;
}
public Activity getLatestActivity(Long athleteId) {
public @Nullable Activity getLatestActivity(Long athleteId) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
// Wir definieren, dass das Ergebnis eine Activity-Entität sein soll
CriteriaQuery<Activity> cq = cb.createQuery(Activity.class);
@@ -35,7 +36,7 @@ public class ActivityDAO extends AbstractDAO<Activity> {
.orElse(null); // Gibt null zurück, wenn der Athlet noch keine Aktivitäten hat
}
public Activity getActivity(String originId, OriginEnum originEnum) {
public @Nullable Activity getActivity(String originId, OriginEnum originEnum) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Activity> cq = cb.createQuery(Activity.class);
Root<Activity> root = cq.from(Activity.class);

View File

@@ -8,14 +8,12 @@ import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.Instant;
@Entity
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class Activity extends AbstractTimestampedEntity {
@@ -88,4 +86,10 @@ public class Activity extends AbstractTimestampedEntity {
@Column(name = "calories")
public Double calories;
@SuppressWarnings("NullAway.Init")
public Activity() {
// hibernate
}
}

View File

@@ -6,6 +6,7 @@ import com.peakup.activity.model.OriginEnum;
import com.peakup.connector.strava.StravaClient;
import com.peakup.connector.strava.StravaContext;
import com.peakup.service.AbstractService;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
@@ -15,6 +16,8 @@ import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import static com.peakup.common.NullableUtils.defaultValue;
@lombok.extern.slf4j.Slf4j
@ApplicationScoped
public class ActivityService extends AbstractService<Activity, ActivityDAO> {
@@ -28,6 +31,7 @@ public class ActivityService extends AbstractService<Activity, ActivityDAO> {
StravaContext stravaContext;
@RestClient
@SuppressWarnings("NullAway.Init")
private StravaClient stravaClient;
@Override
@@ -37,7 +41,7 @@ public class ActivityService extends AbstractService<Activity, ActivityDAO> {
@Transactional
protected Activity getLatestActivity(Long athleteId) {
protected @Nullable Activity getLatestActivity(Long athleteId) {
return activityDAO.getLatestActivity(athleteId);
}
@@ -90,11 +94,11 @@ public class ActivityService extends AbstractService<Activity, ActivityDAO> {
activity.originEnum = origin;
// Basis Infos
activity.name = stravaActivity.name;
activity.type = stravaActivity.type;
activity.sportType = stravaActivity.sport_type;
activity.deviceName = stravaActivity.device_name;
activity.timezone = stravaActivity.timezone;
activity.name = defaultValue(stravaActivity.name);
activity.type = defaultValue(stravaActivity.type);
activity.sportType = defaultValue(stravaActivity.sport_type);
activity.deviceName = defaultValue(stravaActivity.device_name);
activity.timezone = defaultValue(stravaActivity.timezone);
// Zeit-Handling (ISO-8601 String zu Instant)
if (stravaActivity.start_date != null) {
@@ -102,26 +106,26 @@ public class ActivityService extends AbstractService<Activity, ActivityDAO> {
}
// Metriken (Distanz & Zeit)
activity.distanceM = stravaActivity.distance;
activity.movingTimeS = stravaActivity.moving_time;
activity.elapsedTimeS = stravaActivity.elapsed_time;
activity.totalElevationGainM = stravaActivity.total_elevation_gain;
activity.distanceM = defaultValue(stravaActivity.distance);
activity.movingTimeS = defaultValue(stravaActivity.moving_time);
activity.elapsedTimeS = defaultValue(stravaActivity.elapsed_time);
activity.totalElevationGainM = defaultValue(stravaActivity.total_elevation_gain);
// Geschwindigkeit
activity.averageSpeedMps = stravaActivity.average_speed;
activity.maxSpeedMps = stravaActivity.max_speed;
activity.averageSpeedMps = defaultValue(stravaActivity.average_speed);
activity.maxSpeedMps = defaultValue(stravaActivity.max_speed);
// Herzfrequenz
activity.averageHeartrateBpm = stravaActivity.average_heartrate;
activity.maxHeartrateBPM = stravaActivity.max_heartrate;
activity.averageHeartrateBpm = defaultValue(stravaActivity.average_heartrate);
activity.maxHeartrateBPM = defaultValue(stravaActivity.max_heartrate);
// Power & Arbeit
activity.averagePowerWatt = stravaActivity.average_watts;
activity.maxPowerWatt = stravaActivity.max_watts;
activity.normalizedPowerWatt = stravaActivity.weighted_average_watts; // Strava's NP
activity.totalWorkKJ = stravaActivity.kilojoules;
activity.averagePowerWatt = defaultValue(stravaActivity.average_watts);
activity.maxPowerWatt = defaultValue(stravaActivity.max_watts);
activity.normalizedPowerWatt = defaultValue(stravaActivity.weighted_average_watts); // Strava's NP
activity.totalWorkKJ = defaultValue(stravaActivity.kilojoules);
activity.averageCadenceRPM = stravaActivity.average_cadence;
activity.calories = stravaActivity.calories;
activity.averageCadenceRPM = defaultValue(stravaActivity.average_cadence);
activity.calories = defaultValue(stravaActivity.calories);
}
}

View File

@@ -6,24 +6,27 @@ import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Entity
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
public class Athlete extends AbstractTimestampedEntity {
@Column
@Column(nullable = false)
private String firstName;
@Column
@Column(nullable = false)
private String lastName;
@Column
@Column(nullable = false)
private Double weight;
@Column
@Column(nullable = false)
private SexEnum sex;
@SuppressWarnings("NullAway.Init")
public Athlete() {
// hibernate
}
}

View File

@@ -1,19 +1,23 @@
package com.peakup.common;
public class Assert {
private Assert() {}
import jakarta.annotation.Nullable;
public static void isTrue(boolean expression, String message) {
public class Assert {
private Assert() {
}
public static void isTrue(boolean expression, @Nullable String message) {
if (!expression) {
throw new AssertionError(message);
}
}
@SuppressWarnings("unused")
public static void isTrue(boolean expression) {
isTrue(expression, null);
}
public static void notNull(Object object, String message) {
public static void notNull(@Nullable Object object, @Nullable String message) {
if (object == null) {
throw new AssertionError(message);
}
@@ -23,15 +27,15 @@ public class Assert {
notNull(object, null);
}
public static void isEqual(Object objectA, Object objectB) {
public static void isEqual(@Nullable Object objectA, @Nullable Object objectB) {
if (objectA == objectB) {
return;
}
if (objectA == null || objectB == null ) {
throw new AssertionError("ObjectA is "+objectA +" and ObjectB is "+objectB);
if (objectA == null || objectB == null) {
throw new AssertionError("ObjectA is " + objectA + " and ObjectB is " + objectB);
}
if (!objectA.equals(objectB)) {
throw new AssertionError("ObjectA is "+objectA +" and ObjectB is "+objectB);
throw new AssertionError("ObjectA is " + objectA + " and ObjectB is " + objectB);
}
}
}

View File

@@ -0,0 +1,24 @@
package com.peakup.common;
import jakarta.annotation.Nullable;
public class NullableUtils {
private NullableUtils() {
}
public static String defaultValue(@Nullable String value) {
return value == null ? "" : value;
}
public static double defaultValue(@Nullable Double value) {
return value == null ? 0 : value;
}
public static int defaultValue(@Nullable Integer value) {
return value == null ? 0 : value;
}
public static long defaultValue(@Nullable Long value) {
return value == null ? 0L : value;
}
}

View File

@@ -2,26 +2,23 @@ package com.peakup.common;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import org.apache.commons.lang3.StringUtils;
import org.jspecify.annotations.Nullable;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("unused")
@ApplicationScoped
public class TokenCache {
private Cache<Long, String> cache = Caffeine.newBuilder()
private final Cache<Long, String> cache = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(100)
.<Long, String>build();
.build();
public String getBearerToken(Long athleteId) {
String token = cache.getIfPresent(athleteId);
if (StringUtils.isEmpty(token) ) {
}
return token;
public @Nullable String getBearerToken(Long athleteId) {
String token = cache.getIfPresent(athleteId);
return token;
}

View File

@@ -1,5 +1,6 @@
package com.peakup.connector.strava;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
@@ -21,35 +22,41 @@ public interface StravaClient {
@Path("/athlete/activities")
List<Activity> getAthleteActivities(
@QueryParam("before") Long before,
@QueryParam("before") @Nullable Long before,
@QueryParam("after") Long after,
@QueryParam("page") Integer page,
@QueryParam("per_page") Integer perPage
);
class Activity {
public Long id;
public String name;
public String type;
public String sport_type;
public String start_date; // UTC ISO-8601
public String start_date_local;
public String timezone;
public Double distance;
public Long moving_time;
public Long elapsed_time;
public Double total_elevation_gain;
public Double average_speed;
public Double max_speed;
public Double average_heartrate;
public Double max_heartrate;
public Double average_watts;
public Double max_watts;
public Double weighted_average_watts;// Normalized Power
public Double average_cadence;
public Double kilojoules;
public Double calories;
public String device_name;
public @Nullable Long id;
public @Nullable String name;
public @Nullable String type;
public @Nullable String sport_type;
public @Nullable String start_date; // UTC ISO-8601
@SuppressWarnings("unused")
public @Nullable String start_date_local;
public @Nullable String timezone;
public @Nullable Double distance;
public @Nullable Long moving_time;
public @Nullable Long elapsed_time;
public @Nullable Double total_elevation_gain;
public @Nullable Double average_speed;
public @Nullable Double max_speed;
public @Nullable Double average_heartrate;
public @Nullable Double max_heartrate;
public @Nullable Double average_watts;
public @Nullable Double max_watts;
public @Nullable Double weighted_average_watts;// Normalized Power
public @Nullable Double average_cadence;
public @Nullable Double kilojoules;
public @Nullable Double calories;
public @Nullable String device_name;
@SuppressWarnings({"NullAway.Init", "unused"})
private Activity() {
// json
}
}
}

View File

@@ -1,7 +1,6 @@
package com.peakup.connector.strava;
import com.peakup.connector.strava.service.StravaConnectorService;
import jakarta.annotation.Nullable;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@@ -9,7 +8,6 @@ import jakarta.inject.Inject;
@ApplicationScoped
public class StravaContext {
private static final ThreadLocal<Long> ATHLETE_ID = new ThreadLocal<>();
@Inject
@@ -25,19 +23,7 @@ public class StravaContext {
return stravaConnectorService.getToken(ATHLETE_ID.get(), currentTimeInSeconds);
}
public String getTokenNull() {
final String token = getTokenNull(null);
return token;
}
public @Nullable String getTokenNull(Long athleteId) {
if (athleteId == null) {
return null;
}
return getToken();
}
@SuppressWarnings("unused")
public void clear() {
ATHLETE_ID.remove();
}

View File

@@ -6,34 +6,37 @@ import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Entity
@Data
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("NullAway.Init")
public class StravaConnector extends AbstractTimestampedEntity {
@Column
@Column(nullable = false)
private Long athleteId; // loose coupling
@Column
@Column(nullable = false)
private String stravaAthleteId;
@Column
@Column(nullable = false)
private String accessToken;
@Column
@Column(nullable = false)
private String refreshToken;
@Column
@Column(nullable = false)
private long expiresAt;
@Column
@Column(nullable = false)
private String clientId;
@Column
@Column(nullable = false)
private String clientSecret;
@SuppressWarnings("NullAway.Init")
public StravaConnector() {
// hibernate
}
}

View File

@@ -12,6 +12,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@lombok.extern.slf4j.Slf4j
@ApplicationScoped
@@ -19,10 +20,12 @@ public class StravaConnectorService extends AbstractService<StravaConnector, Str
@RestClient
@SuppressWarnings("NullAway.Init")
StravaAuthClient stravaAuthClient;
@Inject
@SuppressWarnings("NullAway.Init")
StravaConnectorDAO stravaConnectorDAO;
@@ -54,7 +57,10 @@ public class StravaConnectorService extends AbstractService<StravaConnector, Str
log.info("The token for athlete {} has been expired for {} seconds.", stravaConnector.getAthleteId(), (currentTimeInSeconds - stravaConnector.getExpiresAt()));
Map<String, Object> result = stravaAuthClient.refreshToken(stravaConnector.getClientId(), stravaConnector.getClientSecret(), "refresh_token", stravaConnector.getRefreshToken());
Assert.isEqual(result.get("token_type"), "Bearer");
update(stravaConnector.getId(), result.get("access_token").toString(), result.get("refresh_token").toString(), (Integer) result.get("expires_at"));
update(Objects.requireNonNull(stravaConnector.getId()),
Objects.requireNonNull(result.get("access_token")).toString(),
Objects.requireNonNull(result.get("refresh_token")).toString(),
Objects.requireNonNull((Integer) result.get("expires_at")));
}
return stravaConnector.getAccessToken();
}

View File

@@ -3,8 +3,8 @@ package com.peakup.dao;
import com.peakup.common.PersistenceException;
import com.peakup.model.AbstractEntity;
import com.peakup.model.AbstractEntity_;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
@@ -13,7 +13,8 @@ import java.util.List;
public abstract class AbstractDAO<T extends AbstractEntity> {
@PersistenceContext
@Inject
@SuppressWarnings("NullAway.Init")
protected EntityManager em;
public T getById(long id) {
@@ -38,6 +39,7 @@ public abstract class AbstractDAO<T extends AbstractEntity> {
protected abstract Class<T> getEntityClass();
@SuppressWarnings("unused")
protected EntityManager getEntityManager() {
return em;
}
@@ -50,6 +52,7 @@ public abstract class AbstractDAO<T extends AbstractEntity> {
}
em.persist(entity); // nach persist ist entity attached
}
public void update(T entity) {
if (!em.contains(entity)) {
throw new PersistenceException(
@@ -58,6 +61,7 @@ public abstract class AbstractDAO<T extends AbstractEntity> {
}
// Änderungen werden automatisch von JPA übernommen
}
public void delete(T entity) {
if (!em.contains(entity)) {
entity = em.merge(entity); // sichergehen, dass attached

View File

@@ -4,6 +4,7 @@ package com.peakup.dao;
import com.peakup.activity.model.ActivityTypeEnum;
import com.peakup.activity.model.OriginEnum;
import com.peakup.athlete.model.SexEnum;
import jakarta.annotation.Nullable;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@@ -16,12 +17,12 @@ public abstract class AbstractEnumConverter<V extends PersistableEnum> implement
}
@Override
public Integer convertToDatabaseColumn(V attribute) {
public @Nullable Integer convertToDatabaseColumn(@Nullable V attribute) {
return (attribute != null) ? attribute.getKey() : null;
}
@Override
public V convertToEntityAttribute(Integer dbData) {
public @Nullable V convertToEntityAttribute(@Nullable Integer dbData) {
if (dbData == null) return null;
return getEnumByKey(dbData);
}

View File

@@ -1,5 +1,6 @@
package com.peakup.model;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -15,6 +16,6 @@ public abstract class AbstractEntity {
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "global_seq")
@SequenceGenerator(name = "global_seq", sequenceName = "global_id_seq", allocationSize = 1)
@Column(name = "ID")
private Long id;
private @Nullable Long id;
}

View File

@@ -18,18 +18,15 @@ import java.time.Instant;
@AllArgsConstructor
public abstract class AbstractTimestampedEntity extends AbstractEntity {
@Column
private Instant modifiedAt;
@Column(nullable = false)
private Instant modifiedAt = Instant.now();
@Column
private Instant createdAt;
@Column(nullable = false)
private Instant createdAt = Instant.now();
@PrePersist
@PreUpdate
public void updateModifiedAt() {
modifiedAt = Instant.now();
if (createdAt == null) {
createdAt = Instant.now();
}
}
}