More ActivityPub
This commit is contained in:
parent
fe5bc54e92
commit
1901daf5ce
17 changed files with 593 additions and 78 deletions
|
|
@ -7,11 +7,14 @@ import org.operaton.fitpub.model.dto.ActivityDTO;
|
|||
import org.operaton.fitpub.model.dto.ActivityUpdateRequest;
|
||||
import org.operaton.fitpub.model.dto.ActivityUploadRequest;
|
||||
import org.operaton.fitpub.model.entity.Activity;
|
||||
import org.operaton.fitpub.model.entity.User;
|
||||
import org.operaton.fitpub.repository.UserRepository;
|
||||
import org.operaton.fitpub.service.FitFileService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
|
|
@ -30,6 +33,20 @@ import java.util.stream.Collectors;
|
|||
public class ActivityController {
|
||||
|
||||
private final FitFileService fitFileService;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
/**
|
||||
* Helper method to get user ID from authenticated UserDetails.
|
||||
*
|
||||
* @param userDetails the authenticated user details
|
||||
* @return the user's UUID
|
||||
* @throws UsernameNotFoundException if user not found
|
||||
*/
|
||||
private UUID getUserId(UserDetails userDetails) {
|
||||
User user = userRepository.findByUsername(userDetails.getUsername())
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + userDetails.getUsername()));
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a FIT file and creates a new activity.
|
||||
|
|
@ -47,8 +64,7 @@ public class ActivityController {
|
|||
) {
|
||||
log.info("User {} uploading FIT file: {}", userDetails.getUsername(), file.getOriginalFilename());
|
||||
|
||||
// TODO: Get actual user ID from UserDetails
|
||||
UUID userId = UUID.randomUUID(); // Temporary - will be replaced with actual user lookup
|
||||
UUID userId = getUserId(userDetails);
|
||||
|
||||
Activity activity = fitFileService.processFitFile(
|
||||
file,
|
||||
|
|
@ -74,8 +90,7 @@ public class ActivityController {
|
|||
@PathVariable UUID id,
|
||||
@AuthenticationPrincipal UserDetails userDetails
|
||||
) {
|
||||
// TODO: Get actual user ID from UserDetails
|
||||
UUID userId = UUID.randomUUID(); // Temporary
|
||||
UUID userId = getUserId(userDetails);
|
||||
|
||||
Activity activity = fitFileService.getActivity(id, userId);
|
||||
if (activity == null) {
|
||||
|
|
@ -98,8 +113,7 @@ public class ActivityController {
|
|||
) {
|
||||
log.info("User {} retrieving activities", userDetails.getUsername());
|
||||
|
||||
// TODO: Get actual user ID from UserDetails
|
||||
UUID userId = UUID.randomUUID(); // Temporary
|
||||
UUID userId = getUserId(userDetails);
|
||||
|
||||
List<Activity> activities = fitFileService.getUserActivities(userId);
|
||||
List<ActivityDTO> dtos = activities.stream()
|
||||
|
|
@ -125,8 +139,7 @@ public class ActivityController {
|
|||
) {
|
||||
log.info("User {} updating activity {}", userDetails.getUsername(), id);
|
||||
|
||||
// TODO: Get actual user ID from UserDetails
|
||||
UUID userId = UUID.randomUUID(); // Temporary
|
||||
UUID userId = getUserId(userDetails);
|
||||
|
||||
Activity activity = fitFileService.getActivity(id, userId);
|
||||
if (activity == null) {
|
||||
|
|
@ -138,10 +151,9 @@ public class ActivityController {
|
|||
activity.setDescription(request.getDescription());
|
||||
activity.setVisibility(request.getVisibility());
|
||||
|
||||
// TODO: Add update method to FitFileService
|
||||
// Activity updated = fitFileService.updateActivity(activity);
|
||||
Activity updated = fitFileService.updateActivity(activity);
|
||||
|
||||
ActivityDTO dto = ActivityDTO.fromEntity(activity);
|
||||
ActivityDTO dto = ActivityDTO.fromEntity(updated);
|
||||
return ResponseEntity.ok(dto);
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +171,7 @@ public class ActivityController {
|
|||
) {
|
||||
log.info("User {} deleting activity {}", userDetails.getUsername(), id);
|
||||
|
||||
// TODO: Get actual user ID from UserDetails
|
||||
UUID userId = UUID.randomUUID(); // Temporary
|
||||
UUID userId = getUserId(userDetails);
|
||||
|
||||
boolean deleted = fitFileService.deleteActivity(id, userId);
|
||||
if (!deleted) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
package org.operaton.fitpub.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.operaton.fitpub.model.dto.AuthResponse;
|
||||
import org.operaton.fitpub.model.dto.LoginRequest;
|
||||
import org.operaton.fitpub.model.dto.RegisterRequest;
|
||||
import org.operaton.fitpub.service.UserService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* REST controller for authentication endpoints.
|
||||
* Handles user registration and login.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class AuthController {
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
/**
|
||||
* Register a new user account.
|
||||
*
|
||||
* @param request Registration details
|
||||
* @return Authentication response with JWT token
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<AuthResponse> register(@Valid @RequestBody RegisterRequest request) {
|
||||
log.info("Registration request received for username: {}", request.getUsername());
|
||||
|
||||
try {
|
||||
AuthResponse response = userService.registerUser(request);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(response);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("Registration failed: {}", e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate user and generate JWT token.
|
||||
*
|
||||
* @param request Login credentials
|
||||
* @return Authentication response with JWT token
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<AuthResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||
log.info("Login request received for: {}", request.getUsernameOrEmail());
|
||||
|
||||
try {
|
||||
AuthResponse response = userService.login(request);
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (BadCredentialsException e) {
|
||||
log.warn("Login failed: {}", e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception handler for IllegalArgumentException (e.g., duplicate username/email).
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<ErrorResponse> handleIllegalArgument(IllegalArgumentException e) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(new ErrorResponse("BAD_REQUEST", e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception handler for BadCredentialsException.
|
||||
*/
|
||||
@ExceptionHandler(BadCredentialsException.class)
|
||||
public ResponseEntity<ErrorResponse> handleBadCredentials(BadCredentialsException e) {
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(new ErrorResponse("UNAUTHORIZED", e.getMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Error response DTO.
|
||||
*/
|
||||
record ErrorResponse(String error, String message) {}
|
||||
}
|
||||
|
|
@ -55,8 +55,8 @@ public class ActivityDTO {
|
|||
.createdAt(activity.getCreatedAt())
|
||||
.updatedAt(activity.getUpdatedAt());
|
||||
|
||||
if (activity.getTotalDuration() != null) {
|
||||
builder.totalDurationSeconds(activity.getTotalDuration().getSeconds());
|
||||
if (activity.getTotalDurationSeconds() != null) {
|
||||
builder.totalDurationSeconds(activity.getTotalDurationSeconds());
|
||||
}
|
||||
|
||||
if (activity.getMetrics() != null) {
|
||||
|
|
|
|||
|
|
@ -61,16 +61,16 @@ public class ActivityMetricsDTO {
|
|||
.totalSteps(metrics.getTotalSteps())
|
||||
.trainingStressScore(metrics.getTrainingStressScore());
|
||||
|
||||
if (metrics.getAveragePace() != null) {
|
||||
builder.averagePaceSeconds(metrics.getAveragePace().getSeconds());
|
||||
if (metrics.getAveragePaceSeconds() != null) {
|
||||
builder.averagePaceSeconds(metrics.getAveragePaceSeconds());
|
||||
}
|
||||
|
||||
if (metrics.getMovingTime() != null) {
|
||||
builder.movingTimeSeconds(metrics.getMovingTime().getSeconds());
|
||||
if (metrics.getMovingTimeSeconds() != null) {
|
||||
builder.movingTimeSeconds(metrics.getMovingTimeSeconds());
|
||||
}
|
||||
|
||||
if (metrics.getStoppedTime() != null) {
|
||||
builder.stoppedTimeSeconds(metrics.getStoppedTime().getSeconds());
|
||||
if (metrics.getStoppedTimeSeconds() != null) {
|
||||
builder.stoppedTimeSeconds(metrics.getStoppedTimeSeconds());
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
package org.operaton.fitpub.model.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* DTO for authentication response containing JWT token.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AuthResponse {
|
||||
|
||||
private String token;
|
||||
|
||||
@Builder.Default
|
||||
private String tokenType = "Bearer";
|
||||
|
||||
private String username;
|
||||
private String email;
|
||||
private String displayName;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package org.operaton.fitpub.model.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* DTO for user login request.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class LoginRequest {
|
||||
|
||||
@NotBlank(message = "Username or email is required")
|
||||
private String usernameOrEmail;
|
||||
|
||||
@NotBlank(message = "Password is required")
|
||||
private String password;
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package org.operaton.fitpub.model.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* DTO for user registration request.
|
||||
* Contains validation constraints for all required fields.
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RegisterRequest {
|
||||
|
||||
@NotBlank(message = "Username is required")
|
||||
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_-]+$", message = "Username can only contain letters, numbers, underscores and hyphens")
|
||||
private String username;
|
||||
|
||||
@NotBlank(message = "Email is required")
|
||||
@Email(message = "Email must be valid")
|
||||
@Size(max = 255, message = "Email must not exceed 255 characters")
|
||||
private String email;
|
||||
|
||||
@NotBlank(message = "Password is required")
|
||||
@Size(min = 8, max = 100, message = "Password must be between 8 and 100 characters")
|
||||
private String password;
|
||||
|
||||
@Size(max = 100, message = "Display name must not exceed 100 characters")
|
||||
private String displayName;
|
||||
|
||||
@Size(max = 500, message = "Bio must not exceed 500 characters")
|
||||
private String bio;
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import org.hibernate.annotations.UpdateTimestamp;
|
|||
import org.locationtech.jts.geom.LineString;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
|
|
@ -77,8 +76,8 @@ public class Activity {
|
|||
@Column(name = "total_distance", precision = 10, scale = 2)
|
||||
private BigDecimal totalDistance;
|
||||
|
||||
@Column(name = "total_duration")
|
||||
private Duration totalDuration;
|
||||
@Column(name = "total_duration_seconds")
|
||||
private Long totalDurationSeconds;
|
||||
|
||||
@Column(name = "elevation_gain", precision = 8, scale = 2)
|
||||
private BigDecimal elevationGain;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import jakarta.persistence.*;
|
|||
import lombok.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
|
|
@ -34,8 +33,8 @@ public class ActivityMetrics {
|
|||
@Column(name = "max_speed", precision = 8, scale = 2)
|
||||
private BigDecimal maxSpeed;
|
||||
|
||||
@Column(name = "average_pace")
|
||||
private Duration averagePace;
|
||||
@Column(name = "average_pace_seconds")
|
||||
private Long averagePaceSeconds;
|
||||
|
||||
@Column(name = "average_heart_rate")
|
||||
private Integer averageHeartRate;
|
||||
|
|
@ -76,11 +75,11 @@ public class ActivityMetrics {
|
|||
@Column(name = "total_descent", precision = 8, scale = 2)
|
||||
private BigDecimal totalDescent;
|
||||
|
||||
@Column(name = "moving_time")
|
||||
private Duration movingTime;
|
||||
@Column(name = "moving_time_seconds")
|
||||
private Long movingTimeSeconds;
|
||||
|
||||
@Column(name = "stopped_time")
|
||||
private Duration stoppedTime;
|
||||
@Column(name = "stopped_time_seconds")
|
||||
private Long stoppedTimeSeconds;
|
||||
|
||||
@Column(name = "total_steps")
|
||||
private Integer totalSteps;
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ public class FitFileService {
|
|||
.endedAt(parsedData.getEndTime())
|
||||
.visibility(visibility)
|
||||
.totalDistance(parsedData.getTotalDistance())
|
||||
.totalDuration(parsedData.getTotalDuration())
|
||||
.totalDurationSeconds(parsedData.getTotalDuration() != null ? parsedData.getTotalDuration().getSeconds() : null)
|
||||
.elevationGain(parsedData.getElevationGain())
|
||||
.elevationLoss(parsedData.getElevationLoss())
|
||||
.rawFitFile(rawFile)
|
||||
|
|
@ -346,4 +346,29 @@ public class FitFileService {
|
|||
public List<Activity> getUserActivities(UUID userId) {
|
||||
return activityRepository.findByUserIdOrderByStartedAtDesc(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing activity's metadata.
|
||||
*
|
||||
* @param activity the activity with updated fields
|
||||
* @return the updated activity
|
||||
* @throws IllegalArgumentException if activity doesn't exist or user doesn't own it
|
||||
*/
|
||||
@Transactional
|
||||
public Activity updateActivity(Activity activity) {
|
||||
// Verify activity exists and belongs to the user
|
||||
Activity existing = activityRepository.findById(activity.getId())
|
||||
.orElseThrow(() -> new IllegalArgumentException("Activity not found: " + activity.getId()));
|
||||
|
||||
if (!existing.getUserId().equals(activity.getUserId())) {
|
||||
throw new IllegalArgumentException("User does not own this activity");
|
||||
}
|
||||
|
||||
// Update allowed fields
|
||||
existing.setTitle(activity.getTitle());
|
||||
existing.setDescription(activity.getDescription());
|
||||
existing.setVisibility(activity.getVisibility());
|
||||
|
||||
return activityRepository.save(existing);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
168
src/main/java/org/operaton/fitpub/service/UserService.java
Normal file
168
src/main/java/org/operaton/fitpub/service/UserService.java
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
package org.operaton.fitpub.service;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.operaton.fitpub.model.dto.AuthResponse;
|
||||
import org.operaton.fitpub.model.dto.LoginRequest;
|
||||
import org.operaton.fitpub.model.dto.RegisterRequest;
|
||||
import org.operaton.fitpub.model.entity.User;
|
||||
import org.operaton.fitpub.repository.UserRepository;
|
||||
import org.operaton.fitpub.security.JwtTokenProvider;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Service for user management operations including registration and authentication.
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class UserService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
/**
|
||||
* Register a new user account with RSA key pair for ActivityPub.
|
||||
*
|
||||
* @param request Registration details
|
||||
* @return Authentication response with JWT token
|
||||
* @throws IllegalArgumentException if username or email already exists
|
||||
*/
|
||||
@Transactional
|
||||
public AuthResponse registerUser(RegisterRequest request) {
|
||||
log.info("Registering new user: {}", request.getUsername());
|
||||
|
||||
// Check if username already exists
|
||||
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
||||
throw new IllegalArgumentException("Username already exists: " + request.getUsername());
|
||||
}
|
||||
|
||||
// Check if email already exists
|
||||
if (userRepository.findByEmail(request.getEmail()).isPresent()) {
|
||||
throw new IllegalArgumentException("Email already exists: " + request.getEmail());
|
||||
}
|
||||
|
||||
// Generate RSA key pair for ActivityPub signatures
|
||||
KeyPair keyPair = generateRsaKeyPair();
|
||||
String publicKey = encodePublicKey(keyPair.getPublic().getEncoded());
|
||||
String privateKey = encodePrivateKey(keyPair.getPrivate().getEncoded());
|
||||
|
||||
// Create user entity
|
||||
User user = User.builder()
|
||||
.username(request.getUsername())
|
||||
.email(request.getEmail())
|
||||
.passwordHash(passwordEncoder.encode(request.getPassword()))
|
||||
.displayName(request.getDisplayName() != null ? request.getDisplayName() : request.getUsername())
|
||||
.bio(request.getBio())
|
||||
.publicKey(publicKey)
|
||||
.privateKey(privateKey)
|
||||
.enabled(true)
|
||||
.locked(false)
|
||||
.build();
|
||||
|
||||
user = userRepository.save(user);
|
||||
log.info("User registered successfully: {}", user.getUsername());
|
||||
|
||||
// Generate JWT token
|
||||
String token = jwtTokenProvider.createToken(user.getUsername());
|
||||
|
||||
return AuthResponse.builder()
|
||||
.token(token)
|
||||
.tokenType("Bearer")
|
||||
.username(user.getUsername())
|
||||
.email(user.getEmail())
|
||||
.displayName(user.getDisplayName())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate user and generate JWT token.
|
||||
*
|
||||
* @param request Login credentials
|
||||
* @return Authentication response with JWT token
|
||||
* @throws BadCredentialsException if credentials are invalid
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public AuthResponse login(LoginRequest request) {
|
||||
log.info("Login attempt for: {}", request.getUsernameOrEmail());
|
||||
|
||||
// Find user by username or email
|
||||
User user = userRepository.findByUsername(request.getUsernameOrEmail())
|
||||
.or(() -> userRepository.findByEmail(request.getUsernameOrEmail()))
|
||||
.orElseThrow(() -> new BadCredentialsException("Invalid username/email or password"));
|
||||
|
||||
// Verify password
|
||||
if (!passwordEncoder.matches(request.getPassword(), user.getPasswordHash())) {
|
||||
throw new BadCredentialsException("Invalid username/email or password");
|
||||
}
|
||||
|
||||
// Check if account is enabled
|
||||
if (!user.isEnabled()) {
|
||||
throw new BadCredentialsException("Account is disabled");
|
||||
}
|
||||
|
||||
// Check if account is locked
|
||||
if (user.isLocked()) {
|
||||
throw new BadCredentialsException("Account is locked");
|
||||
}
|
||||
|
||||
log.info("User logged in successfully: {}", user.getUsername());
|
||||
|
||||
// Generate JWT token
|
||||
String token = jwtTokenProvider.createToken(user.getUsername());
|
||||
|
||||
return AuthResponse.builder()
|
||||
.token(token)
|
||||
.tokenType("Bearer")
|
||||
.username(user.getUsername())
|
||||
.email(user.getEmail())
|
||||
.displayName(user.getDisplayName())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate RSA key pair for ActivityPub HTTP signatures.
|
||||
*
|
||||
* @return Generated key pair
|
||||
*/
|
||||
private KeyPair generateRsaKeyPair() {
|
||||
try {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(2048);
|
||||
return keyGen.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Failed to generate RSA key pair", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode public key to PEM format.
|
||||
*
|
||||
* @param keyBytes Raw key bytes
|
||||
* @return PEM-formatted public key
|
||||
*/
|
||||
private String encodePublicKey(byte[] keyBytes) {
|
||||
String base64 = Base64.getEncoder().encodeToString(keyBytes);
|
||||
return "-----BEGIN PUBLIC KEY-----\n" + base64 + "\n-----END PUBLIC KEY-----";
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode private key to PEM format.
|
||||
*
|
||||
* @param keyBytes Raw key bytes
|
||||
* @return PEM-formatted private key
|
||||
*/
|
||||
private String encodePrivateKey(byte[] keyBytes) {
|
||||
String base64 = Base64.getEncoder().encodeToString(keyBytes);
|
||||
return "-----BEGIN PRIVATE KEY-----\n" + base64 + "\n-----END PRIVATE KEY-----";
|
||||
}
|
||||
}
|
||||
|
|
@ -361,7 +361,7 @@ public class FitParser {
|
|||
.activity(activity)
|
||||
.averageSpeed(averageSpeed)
|
||||
.maxSpeed(maxSpeed)
|
||||
.averagePace(averagePace)
|
||||
.averagePaceSeconds(averagePace != null ? averagePace.getSeconds() : null)
|
||||
.averageHeartRate(averageHeartRate)
|
||||
.maxHeartRate(maxHeartRate)
|
||||
.averageCadence(averageCadence)
|
||||
|
|
@ -375,8 +375,8 @@ public class FitParser {
|
|||
.minElevation(minElevation)
|
||||
.totalAscent(totalAscent)
|
||||
.totalDescent(totalDescent)
|
||||
.movingTime(movingTime)
|
||||
.stoppedTime(stoppedTime)
|
||||
.movingTimeSeconds(movingTime != null ? movingTime.getSeconds() : null)
|
||||
.stoppedTimeSeconds(stoppedTime != null ? stoppedTime.getSeconds() : null)
|
||||
.totalSteps(totalSteps)
|
||||
.build();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue